diff --git a/api/workspace.go b/api/workspace.go new file mode 100644 index 0000000000000000000000000000000000000000..926d3fd08e7ca05b6430d9e591b43f3b960690fd --- /dev/null +++ b/api/workspace.go @@ -0,0 +1,93 @@ +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 + Url string + Editor string + ActualState string + Devfile 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 { + 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 +} + +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/cmdtest/helper.go b/commands/cmdtest/helper.go index 05c0082c878be059fda1cb09afa7d0b78971961a..2a566f80a1496d9033cd0abe77e35b559fba8af5 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 490975aaa11b6f2ad69aadac17fe0aa397004d88..260797139f871d3fc6446e8ead3f1b5a15a6118c 100644 --- a/commands/cmdutils/factory.go +++ b/commands/cmdutils/factory.go @@ -3,8 +3,10 @@ package cmdutils import ( "fmt" + "net/http" "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" @@ -20,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 { @@ -125,6 +128,36 @@ func NewFactory() *Factory { return currentBranch, nil }, IO: iostreams.Init(), + GraphQLClient: func() (*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 + } + + client := c.HTTPClient() + client.Transport = addTokenTransport{ + T: client.Transport, + Token: c.Token(), + } + url := glinstance.GraphQLEndpoint(repo.RepoHost(), c.Protocol) + + gqlClient := graphql.NewClient(url, c.HTTPClient()) + + return gqlClient, nil + }, } } @@ -134,3 +167,13 @@ func initConfig() (config.Config, error) { } return config.Init() } + +type addTokenTransport struct { + T http.RoundTripper + Token string +} + +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/root.go b/commands/root.go index 51a4f616404de8bf95f509c2e59b4669c9db9d3e..24a3318c63f05c66c37a9609ec86370098559313 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/create.go b/commands/workspace/create.go new file mode 100644 index 0000000000000000000000000000000000000000..7098e3d31f7f4033a36967974c5cf83842c7006b --- /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/create_test.go b/commands/workspace/create_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5506442367290fece78742bced3e92414e7ae58d --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..8a502115582d7f1a51ec4944d714c4f35fe8851e --- /dev/null +++ b/commands/workspace/display.go @@ -0,0 +1,51 @@ +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) { + 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() + } + + fmt.Fprintf(streams.StdOut, "%s\n", 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) + fmt.Fprintf(streams.StdOut, "%s:\n%s\n", c.Bold("Devfile"), workspace.Devfile) +} + +func GetStatusWithColor(cp *iostreams.ColorPalette, status string) string { + + switch status { + case "Running": + return cp.Green(status) + case "Stopped": + return cp.Red(status) + case "Terminated": + return cp.Gray(status) + case "Failed": + return cp.Red(status) + } + + return status +} diff --git a/commands/workspace/list.go b/commands/workspace/list.go new file mode 100644 index 0000000000000000000000000000000000000000..ccbdf562b4560281380656ee192d3f0d586141dd --- /dev/null +++ b/commands/workspace/list.go @@ -0,0 +1,88 @@ +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" + "gitlab.com/gitlab-org/cli/pkg/utils" + + "github.com/spf13/cobra" +) + +type ListOptions struct { + Group string + + IO *iostreams.IOStreams + 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 + 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.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", 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 0000000000000000000000000000000000000000..27f8d85a589061ef03ac1db2ecb5d200e3869ac4 --- /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 0000000000000000000000000000000000000000..9a4ba855a1c87407a0ebc081c616490506b7c2aa --- /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.go b/commands/workspace/view.go new file mode 100644 index 0000000000000000000000000000000000000000..a718a4cb58751542d88e2e0e5bae4633a4cc125f --- /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/view_test.go b/commands/workspace/view_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a24f721fc0dd8513080270f559740078fb079cc7 --- /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/commands/workspace/workspace.go b/commands/workspace/workspace.go new file mode 100644 index 0000000000000000000000000000000000000000..3653964950420cf7a65f1e6f086b83e94944bb58 --- /dev/null +++ b/commands/workspace/workspace.go @@ -0,0 +1,33 @@ +package workspace + +import ( + "github.com/MakeNowJust/heredoc" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + "github.com/spf13/cobra" +) + +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 view--group=gitlab-org 1 + `), + Annotations: map[string]string{ + "help:arguments": heredoc.Doc(` + `), + }, + } + + cmdutils.EnableRepoOverride(workspaceCmd, f) + + workspaceCmd.AddCommand(NewCmdList(f)) + workspaceCmd.AddCommand(NewCmdCreate(f)) + workspaceCmd.AddCommand(NewCmdView(f)) + + return workspaceCmd +} diff --git a/docs/source/workspace/create.md b/docs/source/workspace/create.md new file mode 100644 index 0000000000000000000000000000000000000000..e65086ab96e1c7f025f04b74973fac17b67926ef --- /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 0000000000000000000000000000000000000000..741dc9baf7cd7969dfd66ca8dbf2ba5e8327c2c3 --- /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 0000000000000000000000000000000000000000..752c88df4352a53b97ec6d1b37f35b627dfede3f --- /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 0000000000000000000000000000000000000000..2dde1e0605fe205301d62f2a6ef321be81ae25ec --- /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 0000000000000000000000000000000000000000..6bbcce7fb6e0d09c6beac1ea6a99f1009127bd13 --- /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 +``` diff --git a/go.mod b/go.mod index d7961c871a71f3edbf87be47fe407178dde34a12..8f58f9cfb5c763ef7d05890980dbe26cebfb32da 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 d61778d49deb40cbc51247df4ceedaa639f25546..886e7757ea61eb67cea6d54a7f9b99a19ae5bce8 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=