diff --git a/commands/google_cloud/google_cloud.go b/commands/google_cloud/google_cloud.go new file mode 100644 index 00000000..c99be45e --- /dev/null +++ b/commands/google_cloud/google_cloud.go @@ -0,0 +1,19 @@ +package google_cloud + +import ( + "github.com/spf13/cobra" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + googleCloudCreateWLIFCmd "gitlab.com/gitlab-org/cli/commands/google_cloud/wlif" +) + +func NewCmdGoogleCloud(f *cmdutils.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "google-cloud [flags]", + Short: "EXPERIMENTAL: Manage Google Cloud integration of a GitLab project", + } + + cmd.AddCommand(googleCloudCreateWLIFCmd.NewCmdCreateWLIF(f)) + + return cmd +} diff --git a/commands/google_cloud/wlif/wlif_create.go b/commands/google_cloud/wlif/wlif_create.go new file mode 100644 index 00000000..3c6a1f04 --- /dev/null +++ b/commands/google_cloud/wlif/wlif_create.go @@ -0,0 +1,84 @@ +package wlif + +import ( + "bytes" + "fmt" + "net/http" + "net/url" + "os/exec" + + "github.com/MakeNowJust/heredoc" + "github.com/spf13/cobra" + "gitlab.com/gitlab-org/cli/commands/cmdutils" +) + +const ( + flagGoogleCloudProjectID = "google-cloud-project-id" +) + +type WLIFCreateOptions struct { + GoogleCloudProjectID string `url:"google_cloud_project_id,omitempty"` + GoogleCloudWorkloadIdentityPoolID string `url:"google_cloud_workload_identity_pool_id,omitempty"` + GoogleCloudWorkloadIdentityPoolDisplayName string `url:"google_cloud_workload_identity_pool_display_name,omitempty"` + GoogleCloudWorkloadIdentityPoolProviderID string `url:"google_cloud_workload_identity_pool_provider_id,omitempty"` + GoogleCloudWorkloadIdentityPoolProviderDisplayName string `url:"google_cloud_workload_identity_pool_provider_display_name,omitempty"` +} + +func NewCmdCreateWLIF(f *cmdutils.Factory) *cobra.Command { + opts := &WLIFCreateOptions{} + + cmd := &cobra.Command{ + Use: "create-wlif [project] [flags]", + Short: "EXPERIMENTAL: Create a new Workload Identity Federation on Google Cloud", + Example: heredoc.Doc(` + $ glab google-cloud create-wlif project-id \ + --google-cloud-project-id=google-cloud-project-id \ + --google-cloud-workload-identity-pool-id=pool-id \ + --google-cloud-workload-identity-pool-display-name=pool-display-name \ + --google-cloud-workload-identity-pool-provider-id=provider-id \ + --google-cloud-workload-identity-pool-provider-display-name=provider-display-name + `), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := f.HttpClient() + if err != nil { + return err + } + + projectID := args[0] + u := fmt.Sprintf("projects/%s/scripts/google_cloud/create_wlif", url.PathEscape(projectID)) + request, err := client.NewRequest(http.MethodGet, u, opts, nil) + if err != nil { + return err + } + + var buf bytes.Buffer + res, err := client.Do(request, &buf) + if err != nil { + return err + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", res.StatusCode) + } + + script := exec.Command("bash", "-c", buf.String()) + output, err := script.CombinedOutput() + fmt.Println(string(output)) + if err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().StringVar(&opts.GoogleCloudProjectID, flagGoogleCloudProjectID, "", "Google Cloud Project ID for the Workload Identity Federation") + cmd.Flags().StringVar(&opts.GoogleCloudWorkloadIdentityPoolID, "google-cloud-workload-identity-pool-id", "", "ID of the Google Cloud Workload Identity Pool to be created") + cmd.Flags().StringVar(&opts.GoogleCloudWorkloadIdentityPoolDisplayName, "google-cloud-workload-identity-pool-display-name", "", "display name of the Google Cloud Workload Identity Pool to be created") + cmd.Flags().StringVar(&opts.GoogleCloudWorkloadIdentityPoolProviderID, "google-cloud-workload-identity-pool-provider-id", "", "ID of the Google Cloud Workload Identity Pool Provider to be created") + cmd.Flags().StringVar(&opts.GoogleCloudWorkloadIdentityPoolProviderDisplayName, "google-cloud-workload-identity-pool-provider-display-name", "", "display name of the Google Cloud Workload Identity Pool Provider to be created") + cobra.CheckErr(cmd.MarkFlagRequired(flagGoogleCloudProjectID)) + + return cmd +} diff --git a/commands/google_cloud/wlif/wlif_create_test.go b/commands/google_cloud/wlif/wlif_create_test.go new file mode 100644 index 00000000..ec2cbe1e --- /dev/null +++ b/commands/google_cloud/wlif/wlif_create_test.go @@ -0,0 +1,62 @@ +package wlif + +import ( + "bytes" + "io" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/gitlab-org/cli/commands/cmdtest" + "gitlab.com/gitlab-org/cli/pkg/httpmock" + "gitlab.com/gitlab-org/cli/test" +) + +func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) { + ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "") + + factory := cmdtest.InitFactory(ios, rt) + + _, _ = factory.HttpClient() + + cmd := NewCmdCreateWLIF(factory) + + return cmdtest.ExecuteCommand(cmd, cli, stdout, stderr) +} + +func TestNewCmdCreateWLIF(t *testing.T) { + old := os.Stdout // keep backup of the real stdout + r, w, _ := os.Pipe() + os.Stdout = w + + fakeHTTP := &httpmock.Mocker{ + MatchURL: httpmock.PathAndQuerystring, + } + defer fakeHTTP.Verify(t) + + fakeHTTP.RegisterResponder(http.MethodGet, "/api/v4/projects/OWNER/REPO/scripts/google_cloud/create_wlif?google_cloud_project_id=my_gcp_project", + httpmock.NewStringResponse(http.StatusOK, `#!/bin/bash +echo "success from script"`)) + + cli := `"OWNER/REPO" --google-cloud-project-id "my_gcp_project"` + _, err := runCommand(fakeHTTP, false, cli) + if err != nil { + return + } + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + _, _ = io.Copy(&buf, r) + outC <- buf.String() + }() + + // back to normal state + w.Close() + os.Stdout = old // restoring the real stdout + out := <-outC + + assert.Contains(t, out, "success from script") +} diff --git a/commands/root.go b/commands/root.go index 69b6ace5..e45cc5d3 100644 --- a/commands/root.go +++ b/commands/root.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/cli/commands/cmdutils" completionCmd "gitlab.com/gitlab-org/cli/commands/completion" configCmd "gitlab.com/gitlab-org/cli/commands/config" + googleCloudCmd "gitlab.com/gitlab-org/cli/commands/google_cloud" "gitlab.com/gitlab-org/cli/commands/help" incidentCmd "gitlab.com/gitlab-org/cli/commands/incident" issueCmd "gitlab.com/gitlab-org/cli/commands/issue" @@ -112,6 +113,7 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command { rootCmd.AddCommand(changelogCmd.NewCmdChangelog(f)) rootCmd.AddCommand(clusterCmd.NewCmdCluster(f)) + rootCmd.AddCommand(googleCloudCmd.NewCmdGoogleCloud(f)) rootCmd.AddCommand(issueCmd.NewCmdIssue(f)) rootCmd.AddCommand(incidentCmd.NewCmdIncident(f)) rootCmd.AddCommand(labelCmd.NewCmdLabel(f)) diff --git a/docs/source/google-cloud/create-wlif.md b/docs/source/google-cloud/create-wlif.md new file mode 100644 index 00000000..8de63f97 --- /dev/null +++ b/docs/source/google-cloud/create-wlif.md @@ -0,0 +1,46 @@ +--- +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 google-cloud create-wlif` + +EXPERIMENTAL: Create a new Workload Identity Federation on Google Cloud + +```plaintext +glab google-cloud create-wlif [project] [flags] +``` + +## Examples + +```plaintext +$ glab google-cloud create-wlif project-id \ + --google-cloud-project-id=google-cloud-project-id \ + --google-cloud-workload-identity-pool-id=pool-id \ + --google-cloud-workload-identity-pool-display-name=pool-display-name \ + --google-cloud-workload-identity-pool-provider-id=provider-id \ + --google-cloud-workload-identity-pool-provider-display-name=provider-display-name + +``` + +## Options + +```plaintext + --google-cloud-project-id string Google Cloud Project ID for the Workload Identity Federation + --google-cloud-workload-identity-pool-display-name string display name of the Google Cloud Workload Identity Pool to be created + --google-cloud-workload-identity-pool-id string ID of the Google Cloud Workload Identity Pool to be created + --google-cloud-workload-identity-pool-provider-display-name string display name of the Google Cloud Workload Identity Pool Provider to be created + --google-cloud-workload-identity-pool-provider-id string ID of the Google Cloud Workload Identity Pool Provider to be created +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command +``` diff --git a/docs/source/google-cloud/help.md b/docs/source/google-cloud/help.md new file mode 100644 index 00000000..1eae9a3e --- /dev/null +++ b/docs/source/google-cloud/help.md @@ -0,0 +1,24 @@ +--- +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 google-cloud help` + +Help about any command + +```plaintext +glab google-cloud help [command] [flags] +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command +``` diff --git a/docs/source/google-cloud/index.md b/docs/source/google-cloud/index.md new file mode 100644 index 00000000..9443d942 --- /dev/null +++ b/docs/source/google-cloud/index.md @@ -0,0 +1,24 @@ +--- +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 google-cloud` + +EXPERIMENTAL: Manage Google Cloud integration of a GitLab project + +## Options inherited from parent commands + +```plaintext + --help Show help for command +``` + +## Subcommands + +- [`create-wlif`](create-wlif.md)