From 08307175d2aae5042d3205c608539b582763ba6f Mon Sep 17 00:00:00 2001 From: Shekhar Patnaik Date: Tue, 7 Feb 2023 15:05:16 +0000 Subject: [PATCH 1/4] feat(workspace): Added the workspaces and workspace list command - Added ability to call graphql client - Added delivers #https://gitlab.com/gitlab-org/gitlab/-/issues/390055 --- api/workspace.go | 36 ++++++++++ commands/cmdutils/factory.go | 27 ++++++++ commands/root.go | 2 + commands/workspace/list/list.go | 116 ++++++++++++++++++++++++++++++++ commands/workspace/workspace.go | 52 ++++++++++++++ go.mod | 4 ++ go.sum | 33 +++++++++ 7 files changed, 270 insertions(+) create mode 100644 api/workspace.go create mode 100644 commands/workspace/list/list.go create mode 100644 commands/workspace/workspace.go diff --git a/api/workspace.go b/api/workspace.go new file mode 100644 index 000000000..603c768e9 --- /dev/null +++ b/api/workspace.go @@ -0,0 +1,36 @@ +package api + +import ( + "context" + + "github.com/hasura/go-graphql-client" +) + +type Workspace struct { + ID string + Name string + Url string + Editor string + ActualState string +} + +func ListWorkspaces(ctx context.Context, client *graphql.Client, group string) ([]Workspace, error) { + var query struct { + Group struct { + Id string + Name string + Workspaces struct { + Nodes []Workspace + } + } `graphql:"group(fullPath: $group)"` + } + + err := client.Query(ctx, &query, map[string]interface{}{ + "group": graphql.ID(group), + }) + if err != nil { + return nil, err + } + + return query.Group.Workspaces.Nodes, nil +} \ No newline at end of file diff --git a/commands/cmdutils/factory.go b/commands/cmdutils/factory.go index 490975aaa..8feaac589 100644 --- a/commands/cmdutils/factory.go +++ b/commands/cmdutils/factory.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + graphql "github.com/hasura/go-graphql-client" "github.com/xanzy/go-gitlab" "gitlab.com/gitlab-org/cli/api" "gitlab.com/gitlab-org/cli/internal/config" @@ -134,3 +135,29 @@ func initConfig() (config.Config, error) { } return config.Init() } + +func (f *Factory) GraphQLClient() (*graphql.Client, error) { + + cfg, err := configFunc() + if err != nil { + return nil, err + } + + repo, err := baseRepoFunc() + if err != nil { + // use default hostname if remote resolver fails + repo = glrepo.NewWithHost("", "", glinstance.OverridableDefault()) + } + OverrideAPIProtocol(cfg, repo) + + c, err := api.NewClientWithCfg(repo.RepoHost(), cfg, true) + if err != nil { + return nil, err + } + + url := glinstance.GraphQLEndpoint(repo.RepoHost(), c.Protocol) + + client := graphql.NewClient(url, c.HTTPClient()) + + return client, nil +} diff --git a/commands/root.go b/commands/root.go index 51a4f6164..24a3318c6 100644 --- a/commands/root.go +++ b/commands/root.go @@ -27,6 +27,7 @@ import ( userCmd "gitlab.com/gitlab-org/cli/commands/user" variableCmd "gitlab.com/gitlab-org/cli/commands/variable" versionCmd "gitlab.com/gitlab-org/cli/commands/version" + workspaceCmd "gitlab.com/gitlab-org/cli/commands/workspace" "gitlab.com/gitlab-org/cli/internal/glrepo" ) @@ -120,6 +121,7 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command { rootCmd.AddCommand(apiCmd.NewCmdApi(f, nil)) rootCmd.AddCommand(scheduleCmd.NewCmdSchedule(f)) rootCmd.AddCommand(snippetCmd.NewCmdSnippet(f)) + rootCmd.AddCommand(workspaceCmd.NewCmdWorkspace(f)) rootCmd.Flags().BoolP("version", "v", false, "show glab version information") return rootCmd diff --git a/commands/workspace/list/list.go b/commands/workspace/list/list.go new file mode 100644 index 000000000..c2a51fd6f --- /dev/null +++ b/commands/workspace/list/list.go @@ -0,0 +1,116 @@ +package list + +import ( + "fmt" + "time" + + "gitlab.com/gitlab-org/cli/pkg/iostreams" + "gitlab.com/gitlab-org/cli/pkg/tableprinter" + "golang.org/x/net/context" + + "github.com/MakeNowJust/heredoc" + "github.com/hasura/go-graphql-client" + "gitlab.com/gitlab-org/cli/internal/glrepo" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + "gitlab.com/gitlab-org/cli/commands/flag" + "gitlab.com/gitlab-org/cli/pkg/utils" + + "github.com/spf13/cobra" +) + +type ListOptions struct { + Group string + + // Pagination + Page int + PerPage int + + IO *iostreams.IOStreams + BaseRepo func() (glrepo.Interface, error) + GraphQLClient *graphql.Client +} + +func NewCmdList(f *cmdutils.Factory) *cobra.Command { + opts := &ListOptions{ + IO: f.IO, + } + + workspaceListCmd := &cobra.Command{ + Use: "list [flags]", + Short: `List workspaces`, + Long: ``, + Aliases: []string{"ls"}, + Example: heredoc.Doc(` + glab workspace list --group-id="gitlab-org" + `), + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + // supports repo override + opts.BaseRepo = f.BaseRepo + + client, err := f.GraphQLClient() + if err != nil { + return err + } + opts.GraphQLClient = client + + group, err := flag.GroupOverride(cmd) + if err != nil { + return err + } + opts.Group = group + + return listRun(opts) + }, + } + + cmdutils.EnableRepoOverride(workspaceListCmd, f) + workspaceListCmd.PersistentFlags().StringP("group", "g", "", "Select a group/subgroup. This option is ignored if a repo argument is set.") + + return workspaceListCmd +} + +func listRun(opts *ListOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + workspaces, err := api.ListWorkspaces(ctx, opts.GraphQLClient, opts.Group) + if err != nil { + return err + } + + title := utils.NewListTitle("Workspace") + title.Page = 1 + title.CurrentPageTotal = len(workspaces) + title.Total = len(workspaces) + // title.RepoName = repo.FullName() + + // if err = opts.IO.StartPager(); err != nil { + // return err + // } + // defer opts.IO.StopPager() + // // fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(opts.IO, mergeRequests)) + // fmt.Println(workspaces) + + fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), DisplayList(opts.IO, workspaces)) + + return nil +} + +func DisplayList(streams *iostreams.IOStreams, workspaces []api.Workspace) string { + c := streams.Color() + table := tableprinter.NewTablePrinter() + table.SetIsTTY(streams.IsOutputTTY()) + table.AddRow(c.Green("Id"), c.Green("Editor"), c.Green("Actual State"), c.Green("URL")) + for _, workspace := range workspaces { + table.AddCell(workspace.ID) + table.AddCell(workspace.Editor) + table.AddCell(workspace.ActualState) + table.AddCell(workspace.Url) + table.EndRow() + } + + return table.Render() +} diff --git a/commands/workspace/workspace.go b/commands/workspace/workspace.go new file mode 100644 index 000000000..0f7715ada --- /dev/null +++ b/commands/workspace/workspace.go @@ -0,0 +1,52 @@ +package workspace + +import ( + "github.com/MakeNowJust/heredoc" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + "github.com/spf13/cobra" + + "gitlab.com/gitlab-org/cli/commands/workspace/list" +) + +func NewCmdWorkspace(f *cmdutils.Factory) *cobra.Command { + workspaceCmd := &cobra.Command{ + Use: "workspace [flags]", + Short: `Create, view and manage workspaces`, + Long: ``, + Example: heredoc.Doc(` + glab workspace list --group=gitlab-org + glab workspace create --group=gitlab-org --agent=1 -f devfile.yaml --editor=ttyd + glab workspace get --group=gitlab-org --id=1 + `), + Annotations: map[string]string{ + "help:arguments": heredoc.Doc(` + `), + }, + } + + cmdutils.EnableRepoOverride(workspaceCmd, f) + + workspaceCmd.AddCommand(list.NewCmdList(f)) + // workspaceCmd.AddCommand(mrApproversCmd.NewCmdApprovers(f)) + // workspaceCmd.AddCommand(mrCheckoutCmd.NewCmdCheckout(f)) + // workspaceCmd.AddCommand(mrCloseCmd.NewCmdClose(f)) + // workspaceCmd.AddCommand(mrCreateCmd.NewCmdCreate(f, nil)) + // workspaceCmd.AddCommand(mrDeleteCmd.NewCmdDelete(f)) + // workspaceCmd.AddCommand(mrDiffCmd.NewCmdDiff(f, nil)) + // workspaceCmd.AddCommand(mrForCmd.NewCmdFor(f)) + // workspaceCmd.AddCommand(mrIssuesCmd.NewCmdIssues(f)) + // workspaceCmd.AddCommand(mrListCmd.NewCmdList(f, nil)) + // workspaceCmd.AddCommand(mrMergeCmd.NewCmdMerge(f)) + // workspaceCmd.AddCommand(mrNoteCmd.NewCmdNote(f)) + // workspaceCmd.AddCommand(mrRebaseCmd.NewCmdRebase(f)) + // workspaceCmd.AddCommand(mrReopenCmd.NewCmdReopen(f)) + // workspaceCmd.AddCommand(mrRevokeCmd.NewCmdRevoke(f)) + // workspaceCmd.AddCommand(mrSubscribeCmd.NewCmdSubscribe(f)) + // workspaceCmd.AddCommand(mrUnsubscribeCmd.NewCmdUnsubscribe(f)) + // workspaceCmd.AddCommand(mrTodoCmd.NewCmdTodo(f)) + // workspaceCmd.AddCommand(mrUpdateCmd.NewCmdUpdate(f)) + // workspaceCmd.AddCommand(mrViewCmd.NewCmdView(f)) + + return workspaceCmd +} diff --git a/go.mod b/go.mod index d7961c871..8f58f9cfb 100644 --- a/go.mod +++ b/go.mod @@ -56,11 +56,14 @@ require ( github.com/gdamore/encoding v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hasura/go-graphql-client v0.8.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/klauspost/compress v1.15.15 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/microcosm-cc/bluemonday v1.0.16 // indirect @@ -85,4 +88,5 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + nhooyr.io/websocket v1.8.7 // indirect ) diff --git a/go.sum b/go.sum index d61778d49..886e7757e 100644 --- a/go.sum +++ b/go.sum @@ -112,9 +112,21 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM= github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -160,6 +172,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -184,14 +197,19 @@ github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= +github.com/graph-gophers/graphql-go v1.4.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= +github.com/graph-gophers/graphql-transport-ws v0.0.2/go.mod h1:5BVKvFzOd2BalVIBFfnfmHjpJi/MZ5rOj8G55mXvZ8g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -225,6 +243,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hasura/go-graphql-client v0.8.1 h1:yU4888urgkW4L47cs+QQDXl3YfVaNraUqym5qsJ41Ms= +github.com/hasura/go-graphql-client v0.8.1/go.mod h1:NVifIwv+YFIUYGLQ7SM2/vBbzS/9rFP4vmIf/vf/zXM= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -233,6 +253,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -242,12 +263,16 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= @@ -302,6 +327,7 @@ github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -367,6 +393,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xanzy/go-gitlab v0.73.1 h1:UMagqUZLJdjss1SovIC+kJCH4k2AZWXl58gJd38Y/hI= github.com/xanzy/go-gitlab v0.73.1/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -389,6 +417,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= @@ -623,6 +653,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -754,6 +785,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -- GitLab From 5deef59db22c3ad1315b86e2c2143bac1d626d95 Mon Sep 17 00:00:00 2001 From: Shekhar Patnaik Date: Wed, 8 Feb 2023 10:08:46 +0000 Subject: [PATCH 2/4] Added view and create command for workspaces --- api/workspace.go | 58 +++++++++++++- commands/cmdutils/factory.go | 20 ++++- commands/workspace/create.go | 104 ++++++++++++++++++++++++++ commands/workspace/display.go | 50 +++++++++++++ commands/workspace/{list => }/list.go | 37 +-------- commands/workspace/view.go | 81 ++++++++++++++++++++ commands/workspace/workspace.go | 25 +------ 7 files changed, 315 insertions(+), 60 deletions(-) create mode 100644 commands/workspace/create.go create mode 100644 commands/workspace/display.go rename commands/workspace/{list => }/list.go (64%) create mode 100644 commands/workspace/view.go diff --git a/api/workspace.go b/api/workspace.go index 603c768e9..b75d96e00 100644 --- a/api/workspace.go +++ b/api/workspace.go @@ -2,10 +2,13 @@ package api import ( "context" + "errors" "github.com/hasura/go-graphql-client" ) +var ErrWorkspaceNotFound = errors.New("Workspace not found") + type Workspace struct { ID string Name string @@ -14,6 +17,14 @@ type Workspace struct { ActualState string } +type WorkspaceCreateInput struct { + GroupPath string `json:"groupPath"` + Editor string `json:"editor"` + ClusterAgentID string `json:"clusterAgentId"` + Devfile string `json:"devfile"` + DesiredState string `json:"desiredState"` +} + func ListWorkspaces(ctx context.Context, client *graphql.Client, group string) ([]Workspace, error) { var query struct { Group struct { @@ -33,4 +44,49 @@ func ListWorkspaces(ctx context.Context, client *graphql.Client, group string) ( } return query.Group.Workspaces.Nodes, nil -} \ No newline at end of file +} + +type RemoteDevelopmentWorkspaceID string + +func ViewWorkspace(ctx context.Context, client *graphql.Client, group string, workspaceID string) (*Workspace, error) { + var query struct { + Group struct { + Id string + Name string + Workspaces struct { + Nodes []Workspace + } `graphql:"workspaces(id: $workspaceID)"` + } `graphql:"group(fullPath: $group)"` + } + + err := client.Query(ctx, &query, map[string]interface{}{ + "group": graphql.ID(group), + "workspaceID": RemoteDevelopmentWorkspaceID(workspaceID), + }) + if err != nil { + return nil, err + } + + if len(query.Group.Workspaces.Nodes) == 0 { + return nil, ErrWorkspaceNotFound + } + + return &query.Group.Workspaces.Nodes[0], nil +} + +func CreateWorkspace(ctx context.Context, client *graphql.Client, input WorkspaceCreateInput) (*Workspace, error) { + var mutation struct { + WorkspaceCreate struct { + Workspace Workspace + } `graphql:"workspaceCreate(input: $input)"` + } + + err := client.Mutate(ctx, &mutation, map[string]interface{}{ + "input": input, + }) + if err != nil { + return nil, err + } + + return &mutation.WorkspaceCreate.Workspace, nil +} diff --git a/commands/cmdutils/factory.go b/commands/cmdutils/factory.go index 8feaac589..476abb233 100644 --- a/commands/cmdutils/factory.go +++ b/commands/cmdutils/factory.go @@ -3,6 +3,7 @@ package cmdutils import ( "fmt" + "net/http" "strings" graphql "github.com/hasura/go-graphql-client" @@ -155,9 +156,24 @@ func (f *Factory) GraphQLClient() (*graphql.Client, error) { return nil, err } + client := c.HTTPClient() + client.Transport = addTokenTransport{ + T: client.Transport, + Token: c.Token(), + } url := glinstance.GraphQLEndpoint(repo.RepoHost(), c.Protocol) - client := graphql.NewClient(url, c.HTTPClient()) + gqlClient := graphql.NewClient(url, c.HTTPClient()) + + return gqlClient, nil +} + +type addTokenTransport struct { + T http.RoundTripper + Token string +} - return client, nil +func (att addTokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", att.Token)) + return att.T.RoundTrip(req) } diff --git a/commands/workspace/create.go b/commands/workspace/create.go new file mode 100644 index 000000000..7098e3d31 --- /dev/null +++ b/commands/workspace/create.go @@ -0,0 +1,104 @@ +package workspace + +import ( + "fmt" + "os" + "time" + + "gitlab.com/gitlab-org/cli/pkg/iostreams" + "golang.org/x/net/context" + + "github.com/MakeNowJust/heredoc" + "github.com/hasura/go-graphql-client" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + "gitlab.com/gitlab-org/cli/commands/flag" + + "github.com/spf13/cobra" +) + +const DesiredStateRunning = "Running" + +type CreateOptions struct { + Group string + CreateWorkspaceInput api.WorkspaceCreateInput + + IO *iostreams.IOStreams + GraphQLClient *graphql.Client +} + +func NewCmdCreate(f *cmdutils.Factory) *cobra.Command { + opts := &CreateOptions{ + IO: f.IO, + } + + workspaceCreateCmd := &cobra.Command{ + Use: "create [flags]", + Short: `Create a workspace`, + Long: ``, + Example: heredoc.Doc(` + glab workspace create --group="gitlab-org" --editor=ttyd -f devfile.yaml + `), + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + // supports repo override + client, err := f.GraphQLClient() + if err != nil { + return err + } + opts.GraphQLClient = client + + editor, _ := cmd.Flags().GetString("editor") + agent, _ := cmd.Flags().GetString("agent") + devfileLocation, _ := cmd.Flags().GetString("devfile") + + devfileContents, err := os.ReadFile(devfileLocation) + if err != nil { + return err + } + + group, err := flag.GroupOverride(cmd) + if err != nil { + return err + } + opts.Group = group + + opts.CreateWorkspaceInput = api.WorkspaceCreateInput{ + GroupPath: group, + Editor: editor, + ClusterAgentID: fmt.Sprintf("gid://gitlab/Clusters::Agent/%s", agent), + DesiredState: DesiredStateRunning, + Devfile: string(devfileContents), + } + return createRun(opts) + }, + } + + cmdutils.EnableRepoOverride(workspaceCreateCmd, f) + workspaceCreateCmd.PersistentFlags().StringP("group", "g", "", "Select a group/subgroup. This option is ignored if a repo argument is set.") + workspaceCreateCmd.Flags().StringP("editor", "e", "", "The editor to be injected") + workspaceCreateCmd.Flags().StringP("agent", "a", "", "The Id of the agent to use for provisioning") + workspaceCreateCmd.Flags().StringP("devfile", "f", "", "The path of the devfile") + workspaceCreateCmd.MarkFlagRequired("editor") + workspaceCreateCmd.MarkFlagRequired("agent") + workspaceCreateCmd.MarkFlagRequired("devfile") + + return workspaceCreateCmd +} + +func createRun(opts *CreateOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + workspace, err := api.CreateWorkspace(ctx, opts.GraphQLClient, opts.CreateWorkspaceInput) + if err != nil { + return err + } + + DisplayWorkspace(opts.IO, workspace) + + // fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), DisplayList(opts.IO, workspaces)) + + return nil +} \ No newline at end of file diff --git a/commands/workspace/display.go b/commands/workspace/display.go new file mode 100644 index 000000000..fe5cf45c0 --- /dev/null +++ b/commands/workspace/display.go @@ -0,0 +1,50 @@ +package workspace + +import ( + "fmt" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/pkg/iostreams" + "gitlab.com/gitlab-org/cli/pkg/tableprinter" +) + +func DisplayList(streams *iostreams.IOStreams, workspaces []api.Workspace) string { + c := streams.Color() + table := tableprinter.NewTablePrinter() + table.SetIsTTY(streams.IsOutputTTY()) + table.AddRow(c.Green("Id"), c.Green("Editor"), c.Green("Actual State"), c.Green("URL")) + for _, workspace := range workspaces { + table.AddCell(workspace.ID) + table.AddCell(workspace.Editor) + table.AddCell(GetStatusWithColor(c, workspace.ActualState)) + table.AddCell(workspace.Url) + table.EndRow() + } + + return table.Render() +} + +func DisplayWorkspace(streams *iostreams.IOStreams, workspace *api.Workspace) { + c := streams.Color() + + fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("Workspace"), workspace.ID) + fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("Editor"), workspace.Editor) + fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("Actual State"), GetStatusWithColor(c, workspace.ActualState)) + fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("URL"), workspace.Url) +} + +func GetStatusWithColor(cp *iostreams.ColorPalette, status string) string { + + switch status { + case "Running": + return cp.Green(status) + case "Stopped": + return cp.Gray(status) + case "Terminated": + return cp.Gray(status) + case "Failed": + return cp.Red(status) + } + + return status +} diff --git a/commands/workspace/list/list.go b/commands/workspace/list.go similarity index 64% rename from commands/workspace/list/list.go rename to commands/workspace/list.go index c2a51fd6f..9b8624b1d 100644 --- a/commands/workspace/list/list.go +++ b/commands/workspace/list.go @@ -1,16 +1,14 @@ -package list +package workspace import ( "fmt" "time" "gitlab.com/gitlab-org/cli/pkg/iostreams" - "gitlab.com/gitlab-org/cli/pkg/tableprinter" "golang.org/x/net/context" "github.com/MakeNowJust/heredoc" "github.com/hasura/go-graphql-client" - "gitlab.com/gitlab-org/cli/internal/glrepo" "gitlab.com/gitlab-org/cli/api" "gitlab.com/gitlab-org/cli/commands/cmdutils" @@ -23,12 +21,7 @@ import ( type ListOptions struct { Group string - // Pagination - Page int - PerPage int - IO *iostreams.IOStreams - BaseRepo func() (glrepo.Interface, error) GraphQLClient *graphql.Client } @@ -48,8 +41,6 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { // supports repo override - opts.BaseRepo = f.BaseRepo - client, err := f.GraphQLClient() if err != nil { return err @@ -85,32 +76,8 @@ func listRun(opts *ListOptions) error { title.Page = 1 title.CurrentPageTotal = len(workspaces) title.Total = len(workspaces) - // title.RepoName = repo.FullName() - - // if err = opts.IO.StartPager(); err != nil { - // return err - // } - // defer opts.IO.StopPager() - // // fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(opts.IO, mergeRequests)) - // fmt.Println(workspaces) fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), DisplayList(opts.IO, workspaces)) return nil -} - -func DisplayList(streams *iostreams.IOStreams, workspaces []api.Workspace) string { - c := streams.Color() - table := tableprinter.NewTablePrinter() - table.SetIsTTY(streams.IsOutputTTY()) - table.AddRow(c.Green("Id"), c.Green("Editor"), c.Green("Actual State"), c.Green("URL")) - for _, workspace := range workspaces { - table.AddCell(workspace.ID) - table.AddCell(workspace.Editor) - table.AddCell(workspace.ActualState) - table.AddCell(workspace.Url) - table.EndRow() - } - - return table.Render() -} +} \ No newline at end of file diff --git a/commands/workspace/view.go b/commands/workspace/view.go new file mode 100644 index 000000000..a718a4cb5 --- /dev/null +++ b/commands/workspace/view.go @@ -0,0 +1,81 @@ +package workspace + +import ( + "fmt" + "time" + + "gitlab.com/gitlab-org/cli/pkg/iostreams" + "golang.org/x/net/context" + + "github.com/MakeNowJust/heredoc" + "github.com/hasura/go-graphql-client" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + "gitlab.com/gitlab-org/cli/commands/flag" + + "github.com/spf13/cobra" +) + +const gidWorkspaceFormat = "gid://gitlab/RemoteDevelopment::Workspace/%s" + +type ViewOptions struct { + Group string + ID string + + IO *iostreams.IOStreams + GraphQLClient *graphql.Client +} + +func NewCmdView(f *cmdutils.Factory) *cobra.Command { + opts := &ViewOptions{ + IO: f.IO, + } + + workspaceViewCmd := &cobra.Command{ + Use: "view [flags]", + Short: `View details for a workspace`, + Long: ``, + Example: heredoc.Doc(` + glab workspace view --group-id="gitlab-org" 1 + `), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + // supports repo override + client, err := f.GraphQLClient() + if err != nil { + return err + } + opts.GraphQLClient = client + + opts.ID = fmt.Sprintf(gidWorkspaceFormat, args[0]) + + group, err := flag.GroupOverride(cmd) + if err != nil { + return err + } + opts.Group = group + + return viewRun(opts) + }, + } + + cmdutils.EnableRepoOverride(workspaceViewCmd, f) + workspaceViewCmd.PersistentFlags().StringP("group", "g", "", "Select a group/subgroup. This option is ignored if a repo argument is set.") + + return workspaceViewCmd +} + +func viewRun(opts *ViewOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + workspace, err := api.ViewWorkspace(ctx, opts.GraphQLClient, opts.Group, opts.ID) + if err != nil { + return err + } + + DisplayWorkspace(opts.IO, workspace) + + return nil +} \ No newline at end of file diff --git a/commands/workspace/workspace.go b/commands/workspace/workspace.go index 0f7715ada..26d0c0b88 100644 --- a/commands/workspace/workspace.go +++ b/commands/workspace/workspace.go @@ -5,8 +5,6 @@ import ( "gitlab.com/gitlab-org/cli/commands/cmdutils" "github.com/spf13/cobra" - - "gitlab.com/gitlab-org/cli/commands/workspace/list" ) func NewCmdWorkspace(f *cmdutils.Factory) *cobra.Command { @@ -27,26 +25,9 @@ func NewCmdWorkspace(f *cmdutils.Factory) *cobra.Command { cmdutils.EnableRepoOverride(workspaceCmd, f) - workspaceCmd.AddCommand(list.NewCmdList(f)) - // workspaceCmd.AddCommand(mrApproversCmd.NewCmdApprovers(f)) - // workspaceCmd.AddCommand(mrCheckoutCmd.NewCmdCheckout(f)) - // workspaceCmd.AddCommand(mrCloseCmd.NewCmdClose(f)) - // workspaceCmd.AddCommand(mrCreateCmd.NewCmdCreate(f, nil)) - // workspaceCmd.AddCommand(mrDeleteCmd.NewCmdDelete(f)) - // workspaceCmd.AddCommand(mrDiffCmd.NewCmdDiff(f, nil)) - // workspaceCmd.AddCommand(mrForCmd.NewCmdFor(f)) - // workspaceCmd.AddCommand(mrIssuesCmd.NewCmdIssues(f)) - // workspaceCmd.AddCommand(mrListCmd.NewCmdList(f, nil)) - // workspaceCmd.AddCommand(mrMergeCmd.NewCmdMerge(f)) - // workspaceCmd.AddCommand(mrNoteCmd.NewCmdNote(f)) - // workspaceCmd.AddCommand(mrRebaseCmd.NewCmdRebase(f)) - // workspaceCmd.AddCommand(mrReopenCmd.NewCmdReopen(f)) - // workspaceCmd.AddCommand(mrRevokeCmd.NewCmdRevoke(f)) - // workspaceCmd.AddCommand(mrSubscribeCmd.NewCmdSubscribe(f)) - // workspaceCmd.AddCommand(mrUnsubscribeCmd.NewCmdUnsubscribe(f)) - // workspaceCmd.AddCommand(mrTodoCmd.NewCmdTodo(f)) - // workspaceCmd.AddCommand(mrUpdateCmd.NewCmdUpdate(f)) - // workspaceCmd.AddCommand(mrViewCmd.NewCmdView(f)) + workspaceCmd.AddCommand(NewCmdList(f)) + workspaceCmd.AddCommand(NewCmdCreate(f)) + workspaceCmd.AddCommand(NewCmdView(f)) return workspaceCmd } -- GitLab From 47dfce22dcf8d619656f613873f8513662ee2ec0 Mon Sep 17 00:00:00 2001 From: Shekhar Patnaik Date: Wed, 8 Feb 2023 15:53:14 +0000 Subject: [PATCH 3/4] Renamed get to view command. Added Devfile to the display --- api/workspace.go | 1 + commands/workspace/display.go | 3 ++- commands/workspace/workspace.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/workspace.go b/api/workspace.go index b75d96e00..926d3fd08 100644 --- a/api/workspace.go +++ b/api/workspace.go @@ -15,6 +15,7 @@ type Workspace struct { Url string Editor string ActualState string + Devfile string } type WorkspaceCreateInput struct { diff --git a/commands/workspace/display.go b/commands/workspace/display.go index fe5cf45c0..c2b81528b 100644 --- a/commands/workspace/display.go +++ b/commands/workspace/display.go @@ -31,6 +31,7 @@ func DisplayWorkspace(streams *iostreams.IOStreams, workspace *api.Workspace) { fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("Editor"), workspace.Editor) fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("Actual State"), GetStatusWithColor(c, workspace.ActualState)) fmt.Fprintf(streams.StdOut, "%s: %s\n", c.Bold("URL"), workspace.Url) + fmt.Fprintf(streams.StdOut, "%s:\n%s\n", c.Bold("Devfile"), workspace.Devfile) } func GetStatusWithColor(cp *iostreams.ColorPalette, status string) string { @@ -39,7 +40,7 @@ func GetStatusWithColor(cp *iostreams.ColorPalette, status string) string { case "Running": return cp.Green(status) case "Stopped": - return cp.Gray(status) + return cp.Red(status) case "Terminated": return cp.Gray(status) case "Failed": diff --git a/commands/workspace/workspace.go b/commands/workspace/workspace.go index 26d0c0b88..365396495 100644 --- a/commands/workspace/workspace.go +++ b/commands/workspace/workspace.go @@ -15,7 +15,7 @@ func NewCmdWorkspace(f *cmdutils.Factory) *cobra.Command { Example: heredoc.Doc(` glab workspace list --group=gitlab-org glab workspace create --group=gitlab-org --agent=1 -f devfile.yaml --editor=ttyd - glab workspace get --group=gitlab-org --id=1 + glab workspace view--group=gitlab-org 1 `), Annotations: map[string]string{ "help:arguments": heredoc.Doc(` -- GitLab From 96386ee3d647407224780d120834476cacb932f6 Mon Sep 17 00:00:00 2001 From: Shekhar Patnaik Date: Thu, 9 Feb 2023 11:23:45 +0000 Subject: [PATCH 4/4] Added tests and testdata. Fixed return values in display. Refactored factory methods for GraphQLClient to make it easier for tests --- commands/cmdtest/helper.go | 5 + commands/cmdutils/factory.go | 74 +++++++------- commands/workspace/create_test.go | 107 ++++++++++++++++++++ commands/workspace/display.go | 4 +- commands/workspace/list.go | 7 +- commands/workspace/list_test.go | 123 +++++++++++++++++++++++ commands/workspace/testdata/devfile.yaml | 14 +++ commands/workspace/view_test.go | 107 ++++++++++++++++++++ docs/source/workspace/create.md | 41 ++++++++ docs/source/workspace/help.md | 25 +++++ docs/source/workspace/index.md | 41 ++++++++ docs/source/workspace/list.md | 38 +++++++ docs/source/workspace/view.md | 38 +++++++ 13 files changed, 584 insertions(+), 40 deletions(-) create mode 100644 commands/workspace/create_test.go create mode 100644 commands/workspace/list_test.go create mode 100644 commands/workspace/testdata/devfile.yaml create mode 100644 commands/workspace/view_test.go create mode 100644 docs/source/workspace/create.md create mode 100644 docs/source/workspace/help.md create mode 100755 docs/source/workspace/index.md create mode 100644 docs/source/workspace/list.md create mode 100644 docs/source/workspace/view.md diff --git a/commands/cmdtest/helper.go b/commands/cmdtest/helper.go index 05c0082c8..2a566f80a 100644 --- a/commands/cmdtest/helper.go +++ b/commands/cmdtest/helper.go @@ -20,6 +20,7 @@ import ( "gitlab.com/gitlab-org/cli/pkg/iostreams" "github.com/google/shlex" + "github.com/hasura/go-graphql-client" "github.com/otiai10/copy" "github.com/spf13/cobra" "github.com/xanzy/go-gitlab" @@ -164,6 +165,10 @@ func InitFactory(ios *iostreams.IOStreams, rt http.RoundTripper) *cmdutils.Facto BaseRepo: func() (glrepo.Interface, error) { return glrepo.New("OWNER", "REPO"), nil }, + GraphQLClient: func() (*graphql.Client, error) { + client := &http.Client{Transport: rt} + return graphql.NewClient("/api/graphql", client), nil + }, } } diff --git a/commands/cmdutils/factory.go b/commands/cmdutils/factory.go index 476abb233..260797139 100644 --- a/commands/cmdutils/factory.go +++ b/commands/cmdutils/factory.go @@ -22,12 +22,13 @@ var ( ) type Factory struct { - HttpClient func() (*gitlab.Client, error) - BaseRepo func() (glrepo.Interface, error) - Remotes func() (glrepo.Remotes, error) - Config func() (config.Config, error) - Branch func() (string, error) - IO *iostreams.IOStreams + HttpClient func() (*gitlab.Client, error) + BaseRepo func() (glrepo.Interface, error) + Remotes func() (glrepo.Remotes, error) + Config func() (config.Config, error) + Branch func() (string, error) + GraphQLClient func() (*graphql.Client, error) + IO *iostreams.IOStreams } func (f *Factory) RepoOverride(repo string) error { @@ -127,45 +128,44 @@ func NewFactory() *Factory { return currentBranch, nil }, IO: iostreams.Init(), - } -} + GraphQLClient: func() (*graphql.Client, error) { -func initConfig() (config.Config, error) { - if err := config.MigrateOldConfig(); err != nil { - return nil, err - } - return config.Init() -} + cfg, err := configFunc() + if err != nil { + return nil, err + } -func (f *Factory) GraphQLClient() (*graphql.Client, error) { + repo, err := baseRepoFunc() + if err != nil { + // use default hostname if remote resolver fails + repo = glrepo.NewWithHost("", "", glinstance.OverridableDefault()) + } + OverrideAPIProtocol(cfg, repo) - cfg, err := configFunc() - if err != nil { - return nil, err - } + c, err := api.NewClientWithCfg(repo.RepoHost(), cfg, true) + if err != nil { + return nil, err + } - repo, err := baseRepoFunc() - if err != nil { - // use default hostname if remote resolver fails - repo = glrepo.NewWithHost("", "", glinstance.OverridableDefault()) - } - OverrideAPIProtocol(cfg, repo) + client := c.HTTPClient() + client.Transport = addTokenTransport{ + T: client.Transport, + Token: c.Token(), + } + url := glinstance.GraphQLEndpoint(repo.RepoHost(), c.Protocol) - c, err := api.NewClientWithCfg(repo.RepoHost(), cfg, true) - if err != nil { - return nil, err - } + gqlClient := graphql.NewClient(url, c.HTTPClient()) - client := c.HTTPClient() - client.Transport = addTokenTransport{ - T: client.Transport, - Token: c.Token(), + return gqlClient, nil + }, } - url := glinstance.GraphQLEndpoint(repo.RepoHost(), c.Protocol) - - gqlClient := graphql.NewClient(url, c.HTTPClient()) +} - return gqlClient, nil +func initConfig() (config.Config, error) { + if err := config.MigrateOldConfig(); err != nil { + return nil, err + } + return config.Init() } type addTokenTransport struct { diff --git a/commands/workspace/create_test.go b/commands/workspace/create_test.go new file mode 100644 index 000000000..550644236 --- /dev/null +++ b/commands/workspace/create_test.go @@ -0,0 +1,107 @@ +package workspace + +import ( + "net/http" + "testing" + + "github.com/hasura/go-graphql-client" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/cli/pkg/httpmock" +) + +func TestWorkspaceCreate(t *testing.T) { + type httpMock struct { + method string + path string + status int + body string + } + + tests := []struct { + name string + args string + httpMocks []httpMock + + expectedError error + expectedOut string + }{ + { + name: "when valid workspace details are passed in", + args: "-g=MyGroup --editor ttyd --agent 4 --devfile='testdata/devfile.yaml'", + httpMocks: []httpMock{ + { + http.MethodPost, + "/api/graphql", + http.StatusOK, + `{ + "data": { + "workspaceCreate": { + "workspace": { + "id": "123", + "url": null, + "editor": "ttyd", + "devfile": "test", + "actualState": "CreationRequested" + } + } + } + }`, + }, + }, + expectedError: nil, + expectedOut: "Workspace: 123\nEditor: ttyd\nActual State: CreationRequested\nURL: \nDevfile:\ntest\n", + }, + { + name: "when workspace creation fails", + args: "-g=MyGroup --editor ttyd --agent 4 --devfile='testdata/devfile.yaml'", + httpMocks: []httpMock{ + { + http.MethodPost, + "/api/graphql", + http.StatusOK, + `{ + "errors": [{ + "message": "Error Message 1", + "raisedAt": "Test" + }] + }`, + }, + }, + expectedError: graphql.Errors(graphql.Errors{ + graphql.Error{ + Message: "Error Message 1", + Extensions: map[string]interface{}(nil), + Locations: []struct { + Line int "json:\"line\"" + Column int "json:\"column\"" + }(nil), + }, + }), + expectedOut: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fakeHTTP := &httpmock.Mocker{ + MatchURL: httpmock.PathAndQuerystring, + } + defer fakeHTTP.Verify(t) + + for _, mock := range tc.httpMocks { + fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body)) + } + + output, err := runCommand(NewCmdCreate, fakeHTTP, false, tc.args) + if tc.expectedError != nil { + require.Equal(t, tc.expectedError, err, "error expected when running command `workspace list %s`", tc.args) + return + } + + require.Nil(t, err) + + require.Equal(t, tc.expectedOut, output.String()) + require.Empty(t, output.Stderr()) + }) + } +} diff --git a/commands/workspace/display.go b/commands/workspace/display.go index c2b81528b..8a5021155 100644 --- a/commands/workspace/display.go +++ b/commands/workspace/display.go @@ -8,7 +8,7 @@ import ( "gitlab.com/gitlab-org/cli/pkg/tableprinter" ) -func DisplayList(streams *iostreams.IOStreams, workspaces []api.Workspace) string { +func DisplayList(streams *iostreams.IOStreams, workspaces []api.Workspace) { c := streams.Color() table := tableprinter.NewTablePrinter() table.SetIsTTY(streams.IsOutputTTY()) @@ -21,7 +21,7 @@ func DisplayList(streams *iostreams.IOStreams, workspaces []api.Workspace) strin table.EndRow() } - return table.Render() + fmt.Fprintf(streams.StdOut, "%s\n", table.Render()) } func DisplayWorkspace(streams *iostreams.IOStreams, workspace *api.Workspace) { diff --git a/commands/workspace/list.go b/commands/workspace/list.go index 9b8624b1d..ccbdf562b 100644 --- a/commands/workspace/list.go +++ b/commands/workspace/list.go @@ -73,11 +73,16 @@ func listRun(opts *ListOptions) error { } title := utils.NewListTitle("Workspace") + title.EmptyMessage = fmt.Sprintf("No workspaces were found for group %s", opts.Group) title.Page = 1 title.CurrentPageTotal = len(workspaces) title.Total = len(workspaces) - fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), DisplayList(opts.IO, workspaces)) + fmt.Fprintf(opts.IO.StdOut, "%s\n", title.Describe()) + + if len(workspaces) != 0 { + DisplayList(opts.IO, workspaces) + } return nil } \ No newline at end of file diff --git a/commands/workspace/list_test.go b/commands/workspace/list_test.go new file mode 100644 index 000000000..27f8d85a5 --- /dev/null +++ b/commands/workspace/list_test.go @@ -0,0 +1,123 @@ +package workspace + +import ( + "net/http" + "testing" + + "gitlab.com/gitlab-org/cli/commands/cmdtest" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/cli/pkg/httpmock" + "gitlab.com/gitlab-org/cli/test" +) + +func runCommand(cmdCreateFn func(*cmdutils.Factory) *cobra.Command, rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) { + ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "") + + factory := cmdtest.InitFactory(ios, rt) + + _, _ = factory.HttpClient() + + cmd := cmdCreateFn(factory) + + return cmdtest.ExecuteCommand(cmd, cli, stdout, stderr) +} + +func TestWorkspaceList(t *testing.T) { + type httpMock struct { + method string + path string + status int + body string + } + + tests := []struct { + name string + args string + httpMocks []httpMock + + expectedError error + expectedOut string + }{ + { + name: "when no workspaces exist and list is called", + args: "-g=MyGroup", + httpMocks: []httpMock{ + { + http.MethodPost, + "/api/graphql", + http.StatusOK, + `{ + "data": { + "group": { + "workspaces": { + "nodes": [ + ] + } + } + } + }`, + }, + }, + expectedError: nil, + expectedOut: "No workspaces were found for group MyGroup\n", + }, + { + name: "when workspaces exist and list is called", + args: "-g=MyGroup", + httpMocks: []httpMock{ + { + http.MethodPost, + "/api/graphql", + http.StatusOK, + `{ + "data": { + "group": { + "workspaces": { + "nodes": [ + { + "id": "123", + "name": "test", + "editor": "ttyd", + "url": "http://something.remotedev.com", + "actualState": "Running", + "devfile": "test" + } + ] + } + } + } + }`, + }, + }, + expectedError: nil, + expectedOut: "Showing 1 of 1 Workspace on (Page 1)\n\nId\tEditor\tActual State\tURL\n123\tttyd\tRunning\thttp://something.remotedev.com\n\n", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fakeHTTP := &httpmock.Mocker{ + MatchURL: httpmock.PathAndQuerystring, + } + defer fakeHTTP.Verify(t) + + for _, mock := range tc.httpMocks { + fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body)) + } + + output, err := runCommand(NewCmdList, fakeHTTP, false, tc.args) + if tc.expectedError != nil { + require.Equal(t, tc.expectedError, err, "error expected when running command `workspace list %s`", tc.args) + return + } + + require.Nil(t, err, "error running command `workspace list %s`: %v") + + require.Equal(t, tc.expectedOut, output.String()) + require.Empty(t, output.Stderr()) + }) + } +} diff --git a/commands/workspace/testdata/devfile.yaml b/commands/workspace/testdata/devfile.yaml new file mode 100644 index 000000000..9a4ba855a --- /dev/null +++ b/commands/workspace/testdata/devfile.yaml @@ -0,0 +1,14 @@ +attributes: + controller.devfile.io/storage-type: ephemeral +projects: + - name: microservices-demo-frontend + git: + remotes: + origin: "https://github.com/l0rd/microservices-demo-frontend" +components: + - name: tooling-container + attributes: + controller.devfile.io/merge-contribution: true + container: + image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo + diff --git a/commands/workspace/view_test.go b/commands/workspace/view_test.go new file mode 100644 index 000000000..a24f721fc --- /dev/null +++ b/commands/workspace/view_test.go @@ -0,0 +1,107 @@ +package workspace + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/pkg/httpmock" +) + +func TestWorkspaceView(t *testing.T) { + type httpMock struct { + method string + path string + status int + body string + } + + tests := []struct { + name string + args string + httpMocks []httpMock + + expectedError error + expectedOut string + }{ + { + name: "when workspace passed in does not exist", + args: "-g=MyGroup 1", + httpMocks: []httpMock{ + { + http.MethodPost, + "/api/graphql", + http.StatusOK, + `{ + "data": { + "group": { + "workspaces": { + "nodes": [ + ] + } + } + } + }`, + }, + }, + expectedError: api.ErrWorkspaceNotFound, + expectedOut: "", + }, + { + name: "when the workspace passed in does exist", + args: "-g=MyGroup 1", + httpMocks: []httpMock{ + { + http.MethodPost, + "/api/graphql", + http.StatusOK, + `{ + "data": { + "group": { + "workspaces": { + "nodes": [ + { + "id": "123", + "name": "test", + "editor": "ttyd", + "url": "http://something.remotedev.com", + "actualState": "Running", + "devfile": "test" + } + ] + } + } + } + }`, + }, + }, + expectedError: nil, + expectedOut: "Workspace: 123\nEditor: ttyd\nActual State: Running\nURL: http://something.remotedev.com\nDevfile:\ntest\n", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fakeHTTP := &httpmock.Mocker{ + MatchURL: httpmock.PathAndQuerystring, + } + defer fakeHTTP.Verify(t) + + for _, mock := range tc.httpMocks { + fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body)) + } + + output, err := runCommand(NewCmdView, fakeHTTP, false, tc.args) + if tc.expectedError != nil { + require.Equal(t, tc.expectedError, err, "error expected when running command `workspace list %s`", tc.args) + return + } + + require.Nil(t, err) + + require.Equal(t, tc.expectedOut, output.String()) + require.Empty(t, output.Stderr()) + }) + } +} diff --git a/docs/source/workspace/create.md b/docs/source/workspace/create.md new file mode 100644 index 000000000..e65086ab9 --- /dev/null +++ b/docs/source/workspace/create.md @@ -0,0 +1,41 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab workspace create` + +Create a workspace + +```plaintext +glab workspace create [flags] +``` + +## Examples + +```plaintext +glab workspace create --group="gitlab-org" --editor=ttyd -f devfile.yaml + +``` + +## Options + +```plaintext + -a, --agent string The Id of the agent to use for provisioning + -f, --devfile string The path of the devfile + -e, --editor string The editor to be injected + -g, --group string Select a group/subgroup. This option is ignored if a repo argument is set. +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` diff --git a/docs/source/workspace/help.md b/docs/source/workspace/help.md new file mode 100644 index 000000000..741dc9baf --- /dev/null +++ b/docs/source/workspace/help.md @@ -0,0 +1,25 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab workspace help` + +Help about any command + +```plaintext +glab workspace help [command] [flags] +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` diff --git a/docs/source/workspace/index.md b/docs/source/workspace/index.md new file mode 100755 index 000000000..752c88df4 --- /dev/null +++ b/docs/source/workspace/index.md @@ -0,0 +1,41 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab workspace` + +Create, view and manage workspaces + +## Examples + +```plaintext +glab workspace list --group=gitlab-org +glab workspace create --group=gitlab-org --agent=1 -f devfile.yaml --editor=ttyd +glab workspace view--group=gitlab-org 1 + +``` + +## Options + +```plaintext + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command +``` + +## Subcommands + +- [create](create.md) +- [list](list.md) +- [view](view.md) diff --git a/docs/source/workspace/list.md b/docs/source/workspace/list.md new file mode 100644 index 000000000..2dde1e060 --- /dev/null +++ b/docs/source/workspace/list.md @@ -0,0 +1,38 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab workspace list` + +List workspaces + +```plaintext +glab workspace list [flags] +``` + +## Examples + +```plaintext +glab workspace list --group-id="gitlab-org" + +``` + +## Options + +```plaintext + -g, --group string Select a group/subgroup. This option is ignored if a repo argument is set. +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` diff --git a/docs/source/workspace/view.md b/docs/source/workspace/view.md new file mode 100644 index 000000000..6bbcce7fb --- /dev/null +++ b/docs/source/workspace/view.md @@ -0,0 +1,38 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab workspace view` + +View details for a workspace + +```plaintext +glab workspace view [flags] +``` + +## Examples + +```plaintext +glab workspace view --group-id="gitlab-org" 1 + +``` + +## Options + +```plaintext + -g, --group string Select a group/subgroup. This option is ignored if a repo argument is set. +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` -- GitLab