Skip to content

Commit

Permalink
Add a command to open a stack in your browser (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasmik authored Feb 15, 2023
1 parent 658bc92 commit 9463aaa
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 82 deletions.
34 changes: 34 additions & 0 deletions client/structs/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package structs

import "github.com/shurcooL/graphql"

// SearchInput is the main object provided to any search query.
type SearchInput struct {
First *graphql.Int `json:"first"`
After *graphql.String `json:"after"`
FullTextSearch *graphql.String `json:"fullTextSearch"`
Predicates *[]QueryPredicate `json:"predicates"`
}

// QueryPredicate Field and Constraint pair
// for search input.
type QueryPredicate struct {
Field graphql.String `json:"field"`
Constraint QueryFieldConstraint `json:"constraint"`
}

// QueryFieldConstraint is a constraint used
// in a search query.
type QueryFieldConstraint struct {
BooleanEquals *[]graphql.Boolean `json:"booleanEquals"`
EnumEquals *[]graphql.String `json:"enumEquals"`
StringMatches *[]graphql.String `json:"stringMatches"`
}

// PageInfo is the extra information about
// a searched page.
type PageInfo struct {
EndCursor string `json:"endCursor"`
HasNextPage bool `json:"hasNextPage"`
HasPreviousPage bool `json:"hasPreviousPage"`
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
Expand All @@ -40,6 +41,7 @@ require (
github.com/klauspost/compress v1.15.9 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lithammer/fuzzysearch v1.1.5 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkU
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cheggaaa/pb/v3 v3.1.0 h1:3uouEsl32RL7gTiQsuaXD4Bzbfl5tGztXGUvXbs4O04=
github.com/cheggaaa/pb/v3 v3.1.0/go.mod h1:YjrevcBqadFDaGQKRdmZxTY42pXEqda48Ea3lt0K/BE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
Expand Down Expand Up @@ -89,6 +93,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/marcinwyszynski/graphql v0.0.0-20210505073322-ed22d920d37d h1:9Z8P/yiZQQucF5Yo3bmn0JD7Y4TrtGETsbmZpexIuxc=
github.com/marcinwyszynski/graphql v0.0.0-20210505073322-ed22d920d37d/go.mod h1:arY+KAJGvLcmVrvOSx+bDyXcfKl4+eurb19qg+FYX08=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
Expand Down Expand Up @@ -177,6 +183,7 @@ golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
38 changes: 38 additions & 0 deletions internal/browser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package internal

import (
"os/exec"
"runtime"
"strings"

"github.com/pkg/errors"
)

// OpenWebBrowser will try to open the default browser
// on a the callers machine. It supports windows, darwin and windows.
func OpenWebBrowser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("xdg-open", url)
case "darwin":
cmd = exec.Command("open", url)
case "windows":
r := strings.NewReplacer("&", "^&")
cmd = exec.Command("cmd", "/c", "start", r.Replace(url)) //#nosec
default:
return errors.New("unsupported platform")
}

err := cmd.Start()
if err != nil {
return errors.Wrap(err, "could not open the browser")
}

err = cmd.Wait()
if err != nil {
return errors.Wrap(err, "could not wait for the opening browser")
}

return nil
}
31 changes: 1 addition & 30 deletions internal/cmd/profile/login_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"net/http"
"net/url"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -233,7 +231,7 @@ func loginUsingWebBrowser(creds *session.StoredCredentials) error {

fmt.Printf("\nOpening browser to %s\n\n", browserURL)

if err := openWebBrowser(browserURL); err != nil {
if err := internal.OpenWebBrowser(browserURL); err != nil {
server.Close()
return err
}
Expand Down Expand Up @@ -268,33 +266,6 @@ func buildBrowserURL(endpoint, pubKey string, port int) (string, error) {
return base.String(), nil
}

func openWebBrowser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("xdg-open", url)
case "darwin":
cmd = exec.Command("open", url)
case "windows":
r := strings.NewReplacer("&", "^&")
cmd = exec.Command("cmd", "/c", "start", r.Replace(url)) //#nosec
default:
return errors.New("unsupported platform")
}

err := cmd.Start()
if err != nil {
return errors.Wrap(err, "could not open the browser")
}

err = cmd.Wait()
if err != nil {
return errors.Wrap(err, "could not wait for the opening browser")
}

return nil
}

func persistAccessCredentials(creds *session.StoredCredentials) error {
return manager.Create(&session.Profile{
Alias: profileAlias,
Expand Down
18 changes: 18 additions & 0 deletions internal/cmd/stack/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,21 @@ var flagNoUpload = &cli.BoolFlag{
Usage: "Indicate whether Spacectl should prepare the workspace archive, but skip uploading it. Useful for debugging ignorefiles.",
Value: false,
}

var flagIgnoreSubdir = &cli.BoolFlag{
Name: "ignore-subdir",
Usage: "[Optional] Indicate whetever open command should ignore the current subdir and only use the repository to search for a stack",
Value: false,
}

var flagCurrentBranch = &cli.BoolFlag{
Name: "current-branch",
Usage: "[Optional] Indicate whetever to search a stack by the current branch you're on",
Value: false,
}

var flagSearchCount = &cli.IntFlag{
Name: "count",
Usage: "[Optional] Indicate the maximum count of elements returned when searching stacks",
Value: 30,
}
106 changes: 54 additions & 52 deletions internal/cmd/stack/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,58 +33,7 @@ func listStacks() cli.ActionFunc {

func listStacksJSON(ctx context.Context) error {
var query struct {
Stacks []struct {
ID string `graphql:"id" json:"id,omitempty"`
Administrative bool `graphql:"administrative" json:"administrative,omitempty"`
Autodeploy bool `graphql:"autodeploy" json:"autodeploy,omitempty"`
Autoretry bool `graphql:"autoretry" json:"autoretry,omitempty"`
Blocker struct {
ID string `graphql:"id" json:"id,omitempty"`
} `graphql:"blocker"`
AfterApply []string `graphql:"afterApply" json:"afterApply,omitempty"`
BeforeApply []string `graphql:"beforeApply" json:"beforeApply,omitempty"`
AfterInit []string `graphql:"afterInit" json:"afterInit,omitempty"`
BeforeInit []string `graphql:"beforeInit" json:"beforeInit,omitempty"`
AfterPlan []string `graphql:"afterPlan" json:"afterPlan,omitempty"`
BeforePlan []string `graphql:"beforePlan" json:"beforePlan,omitempty"`
AfterPerform []string `graphql:"afterPerform" json:"afterPerform,omitempty"`
BeforePerform []string `graphql:"beforePerform" json:"beforePerform,omitempty"`
AfterDestroy []string `graphql:"afterDestroy" json:"afterDestroy,omitempty"`
BeforeDestroy []string `graphql:"beforeDestroy" json:"beforeDestroy,omitempty"`
Branch string `graphql:"branch" json:"branch,omitempty"`
CanWrite bool `graphql:"canWrite" json:"canWrite,omitempty"`
CreatedAt int64 `graphql:"createdAt" json:"createdAt,omitempty"`
Deleted bool `graphql:"deleted" json:"deleted,omitempty"`
Deleting bool `graphql:"deleting" json:"deleting,omitempty"`
Description string `graphql:"description" json:"description,omitempty"`
Labels []string `graphql:"labels" json:"labels,omitempty"`
LocalPreviewEnabled bool `graphql:"localPreviewEnabled" json:"localPreviewEnabled,omitempty"`
LockedBy string `graphql:"lockedBy" json:"lockedBy,omitempty"`
ManagesStateFile bool `graphql:"managesStateFile" json:"managesStateFile,omitempty"`
Name string `graphql:"name" json:"name,omitempty"`
Namespace string `graphql:"namespace" json:"namespace,omitempty"`
ProjectRoot string `graphql:"projectRoot" json:"projectRoot,omitempty"`
Provider string `graphql:"provider" json:"provider,omitempty"`
Repository string `graphql:"repository" json:"repository,omitempty"`
RunnerImage string `graphql:"runnerImage" json:"runnerImage,omitempty"`
Starred bool `graphql:"starred" json:"starred,omitempty"`
State string `graphql:"state" json:"state,omitempty"`
StateSetAt int64 `graphql:"stateSetAt" json:"stateSetAt,omitempty"`
TerraformVersion string `graphql:"terraformVersion" json:"terraformVersion,omitempty"`
TrackedCommit struct {
AuthorLogin string `graphql:"authorLogin" json:"authorLogin,omitempty"`
AuthorName string `graphql:"authorName" json:"authorName,omitempty"`
Hash string `graphql:"hash" json:"hash,omitempty"`
Message string `graphql:"message" json:"message,omitempty"`
Timestamp int64 `graphql:"timestamp" json:"timestamp,omitempty"`
URL string `graphql:"url" json:"url,omitempty"`
} `graphql:"trackedCommit" json:"trackedCommit,omitempty"`
TrackedCommitSetBy string `graphql:"trackedCommitSetBy" json:"trackedCommitSetBy,omitempty"`
WorkerPool struct {
ID string `graphql:"id" json:"id,omitempty"`
Name string `graphql:"name" json:"name,omitempty"`
} `graphql:"workerPool" json:"workerPool,omitempty"`
} `graphql:"stacks" json:"stacks,omitempty"`
Stacks []stack `graphql:"stacks" json:"stacks,omitempty"`
}

if err := authenticated.Client.Query(ctx, &query, map[string]interface{}{}); err != nil {
Expand Down Expand Up @@ -133,3 +82,56 @@ func listStacksTable(ctx context.Context) error {

return cmd.OutputTable(tableData, true)
}

type stack struct {
ID string `graphql:"id" json:"id,omitempty"`
Administrative bool `graphql:"administrative" json:"administrative,omitempty"`
Autodeploy bool `graphql:"autodeploy" json:"autodeploy,omitempty"`
Autoretry bool `graphql:"autoretry" json:"autoretry,omitempty"`
Blocker struct {
ID string `graphql:"id" json:"id,omitempty"`
} `graphql:"blocker"`
AfterApply []string `graphql:"afterApply" json:"afterApply,omitempty"`
BeforeApply []string `graphql:"beforeApply" json:"beforeApply,omitempty"`
AfterInit []string `graphql:"afterInit" json:"afterInit,omitempty"`
BeforeInit []string `graphql:"beforeInit" json:"beforeInit,omitempty"`
AfterPlan []string `graphql:"afterPlan" json:"afterPlan,omitempty"`
BeforePlan []string `graphql:"beforePlan" json:"beforePlan,omitempty"`
AfterPerform []string `graphql:"afterPerform" json:"afterPerform,omitempty"`
BeforePerform []string `graphql:"beforePerform" json:"beforePerform,omitempty"`
AfterDestroy []string `graphql:"afterDestroy" json:"afterDestroy,omitempty"`
BeforeDestroy []string `graphql:"beforeDestroy" json:"beforeDestroy,omitempty"`
Branch string `graphql:"branch" json:"branch,omitempty"`
CanWrite bool `graphql:"canWrite" json:"canWrite,omitempty"`
CreatedAt int64 `graphql:"createdAt" json:"createdAt,omitempty"`
Deleted bool `graphql:"deleted" json:"deleted,omitempty"`
Deleting bool `graphql:"deleting" json:"deleting,omitempty"`
Description string `graphql:"description" json:"description,omitempty"`
Labels []string `graphql:"labels" json:"labels,omitempty"`
LocalPreviewEnabled bool `graphql:"localPreviewEnabled" json:"localPreviewEnabled,omitempty"`
LockedBy string `graphql:"lockedBy" json:"lockedBy,omitempty"`
ManagesStateFile bool `graphql:"managesStateFile" json:"managesStateFile,omitempty"`
Name string `graphql:"name" json:"name,omitempty"`
Namespace string `graphql:"namespace" json:"namespace,omitempty"`
ProjectRoot string `graphql:"projectRoot" json:"projectRoot,omitempty"`
Provider string `graphql:"provider" json:"provider,omitempty"`
Repository string `graphql:"repository" json:"repository,omitempty"`
RunnerImage string `graphql:"runnerImage" json:"runnerImage,omitempty"`
Starred bool `graphql:"starred" json:"starred,omitempty"`
State string `graphql:"state" json:"state,omitempty"`
StateSetAt int64 `graphql:"stateSetAt" json:"stateSetAt,omitempty"`
TerraformVersion string `graphql:"terraformVersion" json:"terraformVersion,omitempty"`
TrackedCommit struct {
AuthorLogin string `graphql:"authorLogin" json:"authorLogin,omitempty"`
AuthorName string `graphql:"authorName" json:"authorName,omitempty"`
Hash string `graphql:"hash" json:"hash,omitempty"`
Message string `graphql:"message" json:"message,omitempty"`
Timestamp int64 `graphql:"timestamp" json:"timestamp,omitempty"`
URL string `graphql:"url" json:"url,omitempty"`
} `graphql:"trackedCommit" json:"trackedCommit,omitempty"`
TrackedCommitSetBy string `graphql:"trackedCommitSetBy" json:"trackedCommitSetBy,omitempty"`
WorkerPool struct {
ID string `graphql:"id" json:"id,omitempty"`
Name string `graphql:"name" json:"name,omitempty"`
} `graphql:"workerPool" json:"workerPool,omitempty"`
}
Loading

0 comments on commit 9463aaa

Please sign in to comment.