From 9b6b45b65e3fba52019f20d110d3610d62c0e3a9 Mon Sep 17 00:00:00 2001 From: Jakob Gray <20209054+JakobGray@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:41:57 -0400 Subject: [PATCH 1/5] Implement gcp wif-config update command --- cmd/ocm/gcp/gcp-client-shim.go | 4 +- cmd/ocm/gcp/update-wif-config.go | 90 +++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/cmd/ocm/gcp/gcp-client-shim.go b/cmd/ocm/gcp/gcp-client-shim.go index fc5998be..51e444fe 100644 --- a/cmd/ocm/gcp/gcp-client-shim.go +++ b/cmd/ocm/gcp/gcp-client-shim.go @@ -87,7 +87,7 @@ func (c *shim) CreateWorkloadIdentityPool( return errors.Wrapf(err, "failed to check if there is existing workload identity pool %s", poolId) } } else { - log.Printf("Workload identity pool %s already exists", poolId) + log.Printf("Workload identity pool %s exists", poolId) } return nil @@ -137,7 +137,7 @@ func (c *shim) CreateWorkloadIdentityProvider( providerId, poolId) } } else { - return errors.Errorf("workload identity provider %s already exists in pool %s", providerId, poolId) + log.Printf("Workload identity provider %s exists", providerId) } return nil diff --git a/cmd/ocm/gcp/update-wif-config.go b/cmd/ocm/gcp/update-wif-config.go index 31273157..e3374d12 100644 --- a/cmd/ocm/gcp/update-wif-config.go +++ b/cmd/ocm/gcp/update-wif-config.go @@ -1,39 +1,105 @@ package gcp import ( + "context" + "fmt" + "log" + + "github.com/openshift-online/ocm-cli/pkg/gcp" + "github.com/openshift-online/ocm-cli/pkg/ocm" + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + "github.com/pkg/errors" "github.com/spf13/cobra" ) var UpdateWifConfigOpts struct { - wifId string - templateId string } // NewUpdateWorkloadIdentityConfiguration provides the "gcp update wif-config" subcommand func NewUpdateWorkloadIdentityConfiguration() *cobra.Command { updateWifConfigCmd := &cobra.Command{ - Use: "wif-config", + Use: "wif-config [ID|Name]", Short: "Update wif-config.", RunE: updateWorkloadIdentityConfigurationCmd, PreRunE: validationForUpdateWorkloadIdentityConfigurationCmd, } - updateWifConfigCmd.PersistentFlags().StringVar(&UpdateWifConfigOpts.wifId, "wif-id", "", - "Workload Identity Federation ID") - updateWifConfigCmd.MarkPersistentFlagRequired("wif-id") - updateWifConfigCmd.PersistentFlags().StringVar(&UpdateWifConfigOpts.templateId, "template-id", "", - "Template ID") - updateWifConfigCmd.MarkPersistentFlagRequired("template-id") - return updateWifConfigCmd } func validationForUpdateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - // No validation needed + if len(argv) != 1 { + return fmt.Errorf("Expected exactly one command line parameters containing the id of the WIF config") + } return nil } func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - // No implementation yet + ctx := context.Background() + log := log.Default() + id := argv[0] + + // Create the client for the OCM API: + connection, err := ocm.NewConnection().Build() + if err != nil { + return errors.Wrapf(err, "Failed to create OCM connection") + } + + // Verify the WIF configuration exists + wifconfig, err := findWifConfig(connection.ClustersMgmt().V1(), id) + if err != nil { + return errors.Wrapf(err, "failed to get wif-config") + } + + gcpClient, err := gcp.NewGcpClient(ctx) + if err != nil { + return errors.Wrapf(err, "failed to initiate GCP client") + } + + // Re-apply WIF resources + gcpClientWifConfigShim := NewGcpClientWifConfigShim(GcpClientWifConfigShimSpec{ + GcpClient: gcpClient, + WifConfig: wifconfig, + }) + + if err := gcpClientWifConfigShim.GrantSupportAccess(ctx, log); err != nil { + return fmt.Errorf("Failed to grant support access to project: %s", err) + } + + if err := gcpClientWifConfigShim.CreateWorkloadIdentityPool(ctx, log); err != nil { + return fmt.Errorf("Failed to update workload identity pool: %s", err) + } + + if err = gcpClientWifConfigShim.CreateWorkloadIdentityProvider(ctx, log); err != nil { + return fmt.Errorf("Failed to update workload identity provider: %s", err) + } + + if err = gcpClientWifConfigShim.CreateServiceAccounts(ctx, log); err != nil { + return fmt.Errorf("Failed to update IAM service accounts: %s", err) + } + return nil } + +// findWifConfig finds the WIF configuration by ID or name +func findWifConfig(client *cmv1.Client, key string) (*cmv1.WifConfig, error) { + collection := client.GCP().WifConfigs() + page := 1 + size := 1 + query := fmt.Sprintf( + "id = '%s' or display_name = '%s'", + key, key, + ) + + response, err := collection.List().Search(query).Page(page).Size(size).Send() + if err != nil { + return nil, err + } + if response.Total() == 0 { + return nil, fmt.Errorf("WIF configuration with identifier or name '%s' not found", key) + } + if response.Total() > 1 { + return nil, fmt.Errorf("there are %d WIF configurations found with identifier or name '%s'", response.Total(), key) + } + return response.Items().Slice()[0], nil +} From 267e45d81db4119c846b6651d556ebfac68485dc Mon Sep 17 00:00:00 2001 From: Jakob Gray <20209054+JakobGray@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:49:40 -0400 Subject: [PATCH 2/5] Accept name or id in wif commands --- cmd/ocm/gcp/delete-wif-config.go | 23 ++++++++--------------- cmd/ocm/gcp/describe-wif-config.go | 16 ++++++---------- cmd/ocm/gcp/gcp-client-shim.go | 9 +++++++++ cmd/ocm/gcp/generate-wif-script.go | 27 ++++++++++----------------- cmd/ocm/gcp/update-wif-config.go | 11 ++++++----- 5 files changed, 39 insertions(+), 47 deletions(-) diff --git a/cmd/ocm/gcp/delete-wif-config.go b/cmd/ocm/gcp/delete-wif-config.go index 576c3f14..51c044b7 100644 --- a/cmd/ocm/gcp/delete-wif-config.go +++ b/cmd/ocm/gcp/delete-wif-config.go @@ -27,7 +27,7 @@ var ( // NewDeleteWorkloadIdentityConfiguration provides the "gcp delete wif-config" subcommand func NewDeleteWorkloadIdentityConfiguration() *cobra.Command { deleteWifConfigCmd := &cobra.Command{ - Use: "wif-config [ID]", + Use: "wif-config [ID|Name]", Short: "Delete workload identity configuration", RunE: deleteWorkloadIdentityConfigurationCmd, PreRunE: validationForDeleteWorkloadIdentityConfigurationCmd, @@ -42,22 +42,15 @@ func NewDeleteWorkloadIdentityConfiguration() *cobra.Command { } func validationForDeleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - if len(argv) != 1 { - return fmt.Errorf( - "expected exactly one command line parameters containing the id " + - "of the WIF config", - ) + if err := wifKeyArgCheck(argv); err != nil { + return err } return nil } func deleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() - - wifConfigId := argv[0] - if wifConfigId == "" { - return fmt.Errorf("WIF config ID is required") - } + key := argv[0] // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() @@ -66,11 +59,11 @@ func deleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) e } defer connection.Close() - response, err := connection.ClustersMgmt().V1().GCP().WifConfigs().WifConfig(wifConfigId).Get().Send() + // Verify the WIF configuration exists + wifConfig, err := findWifConfig(connection.ClustersMgmt().V1(), key) if err != nil { return errors.Wrapf(err, "failed to get wif-config") } - wifConfig := response.Body() if DeleteWifConfigOpts.DryRun { log.Printf("Writing script files to %s", DeleteWifConfigOpts.TargetDir) @@ -96,11 +89,11 @@ func deleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) e } _, err = connection.ClustersMgmt().V1().GCP().WifConfigs(). - WifConfig(wifConfigId). + WifConfig(wifConfig.ID()). Delete(). Send() if err != nil { - return errors.Wrapf(err, "failed to delete wif config %q", wifConfigId) + return errors.Wrapf(err, "failed to delete wif config %q", wifConfig.ID()) } return nil } diff --git a/cmd/ocm/gcp/describe-wif-config.go b/cmd/ocm/gcp/describe-wif-config.go index bb5cd326..c1df0bd1 100644 --- a/cmd/ocm/gcp/describe-wif-config.go +++ b/cmd/ocm/gcp/describe-wif-config.go @@ -6,7 +6,6 @@ import ( "text/tabwriter" "github.com/openshift-online/ocm-cli/pkg/ocm" - "github.com/openshift-online/ocm-cli/pkg/urls" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -14,7 +13,7 @@ import ( // NewDescribeWorkloadIdentityConfiguration provides the "gcp describe wif-config" subcommand func NewDescribeWorkloadIdentityConfiguration() *cobra.Command { describeWorkloadIdentityPoolCmd := &cobra.Command{ - Use: "wif-config [ID]", + Use: "wif-config [ID|Name]", Short: "Show details of a wif-config.", RunE: describeWorkloadIdentityConfigurationCmd, PreRunE: validationForDescribeWorkloadIdentityConfigurationCmd, @@ -24,10 +23,7 @@ func NewDescribeWorkloadIdentityConfiguration() *cobra.Command { } func describeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - id, err := urls.Expand(argv) - if err != nil { - return errors.Wrapf(err, "could not create URI") - } + key := argv[0] // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() @@ -36,11 +32,11 @@ func describeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) } defer connection.Close() - response, err := connection.ClustersMgmt().V1().GCP().WifConfigs().WifConfig(id).Get().Send() + // Verify the WIF configuration exists + wifConfig, err := findWifConfig(connection.ClustersMgmt().V1(), key) if err != nil { return errors.Wrapf(err, "failed to get wif-config") } - wifConfig := response.Body() // Print output w := tabwriter.NewWriter(os.Stdout, 8, 0, 2, ' ', 0) @@ -54,8 +50,8 @@ func describeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) } func validationForDescribeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - if len(argv) != 1 { - return fmt.Errorf("Expected exactly one command line parameters containing the id of the WIF config") + if err := wifKeyArgCheck(argv); err != nil { + return err } return nil } diff --git a/cmd/ocm/gcp/gcp-client-shim.go b/cmd/ocm/gcp/gcp-client-shim.go index 51e444fe..9c820ef9 100644 --- a/cmd/ocm/gcp/gcp-client-shim.go +++ b/cmd/ocm/gcp/gcp-client-shim.go @@ -545,3 +545,12 @@ func (c *shim) createMemberRoleBindingForProject( Role: roleName, }) } + +// Checks for WIF config name or id in input +func wifKeyArgCheck(args []string) error { + if len(args) != 1 || args[0] == "" { + return fmt.Errorf("expected exactly one command line parameters containing the name " + + "or ID of the WIF config") + } + return nil +} diff --git a/cmd/ocm/gcp/generate-wif-script.go b/cmd/ocm/gcp/generate-wif-script.go index 5a6ca45e..1711c252 100644 --- a/cmd/ocm/gcp/generate-wif-script.go +++ b/cmd/ocm/gcp/generate-wif-script.go @@ -2,7 +2,6 @@ package gcp import ( "context" - "fmt" "log" "github.com/openshift-online/ocm-cli/pkg/gcp" @@ -20,7 +19,7 @@ var ( func NewGenerateCommand() *cobra.Command { generateScriptCmd := &cobra.Command{ - Use: "generate [wif-config ID]", + Use: "generate [wif-config ID|Name]", Short: "Generate script based on a wif-config", Args: cobra.ExactArgs(1), RunE: generateCreateScriptCmd, @@ -34,39 +33,33 @@ func NewGenerateCommand() *cobra.Command { } func validationForGenerateCreateScriptCmd(cmd *cobra.Command, argv []string) error { - if len(argv) != 1 { - return fmt.Errorf( - "Expected exactly one command line parameters containing the id " + - "of the WIF config.", - ) + if err := wifKeyArgCheck(argv); err != nil { + return err } return nil } func generateCreateScriptCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() + key := argv[0] - gcpClient, err := gcp.NewGcpClient(ctx) - if err != nil { - errors.Wrapf(err, "failed to initiate GCP client") - } - + // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() if err != nil { return errors.Wrapf(err, "Failed to create OCM connection") } defer connection.Close() - wifConfigId := argv[0] - if wifConfigId == "" { - return fmt.Errorf("WIF config ID is required") + gcpClient, err := gcp.NewGcpClient(ctx) + if err != nil { + errors.Wrapf(err, "failed to initiate GCP client") } - response, err := connection.ClustersMgmt().V1().GCP().WifConfigs().WifConfig(wifConfigId).Get().Send() + // Verify the WIF configuration exists + wifConfig, err := findWifConfig(connection.ClustersMgmt().V1(), key) if err != nil { return errors.Wrapf(err, "failed to get wif-config") } - wifConfig := response.Body() projectNum, err := gcpClient.ProjectNumberFromId(ctx, wifConfig.Gcp().ProjectId()) if err != nil { diff --git a/cmd/ocm/gcp/update-wif-config.go b/cmd/ocm/gcp/update-wif-config.go index e3374d12..59cacadc 100644 --- a/cmd/ocm/gcp/update-wif-config.go +++ b/cmd/ocm/gcp/update-wif-config.go @@ -28,8 +28,8 @@ func NewUpdateWorkloadIdentityConfiguration() *cobra.Command { } func validationForUpdateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - if len(argv) != 1 { - return fmt.Errorf("Expected exactly one command line parameters containing the id of the WIF config") + if err := wifKeyArgCheck(argv); err != nil { + return err } return nil } @@ -37,16 +37,17 @@ func validationForUpdateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, arg func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() log := log.Default() - id := argv[0] + key := argv[0] // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() if err != nil { return errors.Wrapf(err, "Failed to create OCM connection") } + defer connection.Close() // Verify the WIF configuration exists - wifconfig, err := findWifConfig(connection.ClustersMgmt().V1(), id) + wifConfig, err := findWifConfig(connection.ClustersMgmt().V1(), key) if err != nil { return errors.Wrapf(err, "failed to get wif-config") } @@ -59,7 +60,7 @@ func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) e // Re-apply WIF resources gcpClientWifConfigShim := NewGcpClientWifConfigShim(GcpClientWifConfigShimSpec{ GcpClient: gcpClient, - WifConfig: wifconfig, + WifConfig: wifConfig, }) if err := gcpClientWifConfigShim.GrantSupportAccess(ctx, log); err != nil { From 4455310c55fe30142cb2a3b6809166f32bb778c9 Mon Sep 17 00:00:00 2001 From: Jakob Gray <20209054+JakobGray@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:28:26 -0400 Subject: [PATCH 3/5] Filter available versions when creating WIF cluster --- cmd/ocm/create/cluster/cmd.go | 24 ++++++++++++++++++++---- cmd/ocm/list/version/cmd.go | 2 +- pkg/cluster/versions.go | 9 ++++++++- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/cmd/ocm/create/cluster/cmd.go b/cmd/ocm/create/cluster/cmd.go index a424ebdd..965b791a 100644 --- a/cmd/ocm/create/cluster/cmd.go +++ b/cmd/ocm/create/cluster/cmd.go @@ -477,16 +477,21 @@ func GetDefaultClusterFlavors(connection *sdk.Connection, flavour string) (dMach } func getVersionOptions(connection *sdk.Connection) ([]arguments.Option, error) { - options, _, err := getVersionOptionsWithDefault(connection, "", "") + options, _, err := getVersionOptionsWithDefault(connection, "", "", "") return options, err } -func getVersionOptionsWithDefault(connection *sdk.Connection, channelGroup string, gcpMarketplaceEnabled string) ( +func getVersionOptionsWithDefault( + connection *sdk.Connection, + channelGroup string, + gcpMarketplaceEnabled string, + additionalFilters string, +) ( options []arguments.Option, defaultVersion string, err error, ) { // Check and set the cluster version versionList, defaultVersion, err := c.GetEnabledVersions( - connection.ClustersMgmt().V1(), channelGroup, gcpMarketplaceEnabled) + connection.ClustersMgmt().V1(), channelGroup, gcpMarketplaceEnabled, additionalFilters) if err != nil { return } @@ -674,8 +679,9 @@ func preRun(cmd *cobra.Command, argv []string) error { if isGcpMarketplace { gcpMarketplaceEnabled = strconv.FormatBool(isGcpMarketplace) } + additionalFilters := getVersionFilters() versions, defaultVersion, err := getVersionOptionsWithDefault(connection, args.channelGroup, - gcpMarketplaceEnabled) + gcpMarketplaceEnabled, additionalFilters) if err != nil { return err } @@ -1573,3 +1579,13 @@ func promptAutoscaling(fs *pflag.FlagSet) error { } return nil } + +// getVersionFilters returns a version filter based on the current args +func getVersionFilters() string { + filter := "" + // WIF filter + if args.gcpAuthentication.Type == c.AuthenticationWif { + filter = fmt.Sprintf("%s AND wif_enabled = 'true'", filter) + } + return filter +} diff --git a/cmd/ocm/list/version/cmd.go b/cmd/ocm/list/version/cmd.go index 0fc67062..ab0b74df 100644 --- a/cmd/ocm/list/version/cmd.go +++ b/cmd/ocm/list/version/cmd.go @@ -74,7 +74,7 @@ func run(cmd *cobra.Command, argv []string) error { defer connection.Close() client := connection.ClustersMgmt().V1() - versions, defaultVersion, err := cluster.GetEnabledVersions(client, args.channelGroup, args.marketplaceGcp) + versions, defaultVersion, err := cluster.GetEnabledVersions(client, args.channelGroup, args.marketplaceGcp, "") if err != nil { return fmt.Errorf("Can't retrieve versions: %v", err) } diff --git a/pkg/cluster/versions.go b/pkg/cluster/versions.go index fd292662..1f67554a 100644 --- a/pkg/cluster/versions.go +++ b/pkg/cluster/versions.go @@ -42,7 +42,11 @@ func EnsureOpenshiftVPrefix(v string) string { // GetEnabledVersions returns the versions with enabled=true, and the one that has default=true. // The returned strings are the IDs without "openshift-v" prefix (e.g. "4.6.0-rc.4-candidate") // sorted in approximate SemVer order (handling of text parts is somewhat arbitrary). -func GetEnabledVersions(client *cmv1.Client, channelGroup string, gcpMarketplaceEnabled string) ( +func GetEnabledVersions(client *cmv1.Client, + channelGroup string, + gcpMarketplaceEnabled string, + additionalFilters string, +) ( versions []string, defaultVersion string, err error) { collection := client.Versions() page := 1 @@ -54,6 +58,9 @@ func GetEnabledVersions(client *cmv1.Client, channelGroup string, gcpMarketplace if channelGroup != "" { filter = fmt.Sprintf("%s AND channel_group = '%s'", filter, channelGroup) } + if additionalFilters != "" { + filter = fmt.Sprintf("%s %s", filter, additionalFilters) + } for { response, err := collection.List(). Search(filter). From a077ebb0fc11cf7d1071691728a33d378a84720a Mon Sep 17 00:00:00 2001 From: Jakob Gray <20209054+JakobGray@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:05:02 -0400 Subject: [PATCH 4/5] Create gcp wif helpers file and remove unnecessary validation functions --- cmd/ocm/gcp/delete-wif-config.go | 8 ++--- cmd/ocm/gcp/describe-wif-config.go | 19 +++++------- cmd/ocm/gcp/gcp-client-shim.go | 9 ------ cmd/ocm/gcp/generate-wif-script.go | 21 +++++-------- cmd/ocm/gcp/helpers.go | 47 ++++++++++++++++++++++++++++++ cmd/ocm/gcp/list-wif-config.go | 6 ---- cmd/ocm/gcp/update-wif-config.go | 43 +++++---------------------- 7 files changed, 73 insertions(+), 80 deletions(-) create mode 100644 cmd/ocm/gcp/helpers.go diff --git a/cmd/ocm/gcp/delete-wif-config.go b/cmd/ocm/gcp/delete-wif-config.go index 51c044b7..d2f3f854 100644 --- a/cmd/ocm/gcp/delete-wif-config.go +++ b/cmd/ocm/gcp/delete-wif-config.go @@ -42,15 +42,15 @@ func NewDeleteWorkloadIdentityConfiguration() *cobra.Command { } func validationForDeleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - if err := wifKeyArgCheck(argv); err != nil { - return err - } return nil } func deleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() - key := argv[0] + key, err := wifKeyFromArgs(argv) + if err != nil { + return err + } // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() diff --git a/cmd/ocm/gcp/describe-wif-config.go b/cmd/ocm/gcp/describe-wif-config.go index c1df0bd1..26a7a9db 100644 --- a/cmd/ocm/gcp/describe-wif-config.go +++ b/cmd/ocm/gcp/describe-wif-config.go @@ -13,17 +13,19 @@ import ( // NewDescribeWorkloadIdentityConfiguration provides the "gcp describe wif-config" subcommand func NewDescribeWorkloadIdentityConfiguration() *cobra.Command { describeWorkloadIdentityPoolCmd := &cobra.Command{ - Use: "wif-config [ID|Name]", - Short: "Show details of a wif-config.", - RunE: describeWorkloadIdentityConfigurationCmd, - PreRunE: validationForDescribeWorkloadIdentityConfigurationCmd, + Use: "wif-config [ID|Name]", + Short: "Show details of a wif-config.", + RunE: describeWorkloadIdentityConfigurationCmd, } return describeWorkloadIdentityPoolCmd } func describeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - key := argv[0] + key, err := wifKeyFromArgs(argv) + if err != nil { + return err + } // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() @@ -48,10 +50,3 @@ func describeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) return w.Flush() } - -func validationForDescribeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - if err := wifKeyArgCheck(argv); err != nil { - return err - } - return nil -} diff --git a/cmd/ocm/gcp/gcp-client-shim.go b/cmd/ocm/gcp/gcp-client-shim.go index 9c820ef9..51e444fe 100644 --- a/cmd/ocm/gcp/gcp-client-shim.go +++ b/cmd/ocm/gcp/gcp-client-shim.go @@ -545,12 +545,3 @@ func (c *shim) createMemberRoleBindingForProject( Role: roleName, }) } - -// Checks for WIF config name or id in input -func wifKeyArgCheck(args []string) error { - if len(args) != 1 || args[0] == "" { - return fmt.Errorf("expected exactly one command line parameters containing the name " + - "or ID of the WIF config") - } - return nil -} diff --git a/cmd/ocm/gcp/generate-wif-script.go b/cmd/ocm/gcp/generate-wif-script.go index 1711c252..f6d1ce48 100644 --- a/cmd/ocm/gcp/generate-wif-script.go +++ b/cmd/ocm/gcp/generate-wif-script.go @@ -19,11 +19,10 @@ var ( func NewGenerateCommand() *cobra.Command { generateScriptCmd := &cobra.Command{ - Use: "generate [wif-config ID|Name]", - Short: "Generate script based on a wif-config", - Args: cobra.ExactArgs(1), - RunE: generateCreateScriptCmd, - PreRunE: validationForGenerateCreateScriptCmd, + Use: "generate [wif-config ID|Name]", + Short: "Generate script based on a wif-config", + Args: cobra.ExactArgs(1), + RunE: generateCreateScriptCmd, } generateScriptCmd.PersistentFlags().StringVar(&GenerateScriptOpts.TargetDir, "output-dir", "", @@ -32,16 +31,12 @@ func NewGenerateCommand() *cobra.Command { return generateScriptCmd } -func validationForGenerateCreateScriptCmd(cmd *cobra.Command, argv []string) error { - if err := wifKeyArgCheck(argv); err != nil { - return err - } - return nil -} - func generateCreateScriptCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() - key := argv[0] + key, err := wifKeyFromArgs(argv) + if err != nil { + return err + } // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() diff --git a/cmd/ocm/gcp/helpers.go b/cmd/ocm/gcp/helpers.go new file mode 100644 index 00000000..19ca216e --- /dev/null +++ b/cmd/ocm/gcp/helpers.go @@ -0,0 +1,47 @@ +package gcp + +import ( + "fmt" + + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" +) + +// Checks for WIF config name or id in input +func wifKeyArgCheck(args []string) error { + if len(args) != 1 || args[0] == "" { + return fmt.Errorf("expected exactly one command line parameters containing the name " + + "or ID of the WIF config") + } + return nil +} + +// Extracts WIF config name or id from input +func wifKeyFromArgs(args []string) (string, error) { + if err := wifKeyArgCheck(args); err != nil { + return "", err + } + return args[0], nil +} + +// findWifConfig finds the WIF configuration by ID or name +func findWifConfig(client *cmv1.Client, key string) (*cmv1.WifConfig, error) { + collection := client.GCP().WifConfigs() + page := 1 + size := 1 + query := fmt.Sprintf( + "id = '%s' or display_name = '%s'", + key, key, + ) + + response, err := collection.List().Search(query).Page(page).Size(size).Send() + if err != nil { + return nil, err + } + if response.Total() == 0 { + return nil, fmt.Errorf("WIF configuration with identifier or name '%s' not found", key) + } + if response.Total() > 1 { + return nil, fmt.Errorf("there are %d WIF configurations found with identifier or name '%s'", response.Total(), key) + } + return response.Items().Slice()[0], nil +} diff --git a/cmd/ocm/gcp/list-wif-config.go b/cmd/ocm/gcp/list-wif-config.go index 98f88363..025380fa 100644 --- a/cmd/ocm/gcp/list-wif-config.go +++ b/cmd/ocm/gcp/list-wif-config.go @@ -24,7 +24,6 @@ func NewListWorkloadIdentityConfiguration() *cobra.Command { Aliases: []string{"wif-configs"}, Short: "List wif-configs.", RunE: listWorkloadIdentityConfigurationCmd, - PreRunE: validationForListWorkloadIdentityConfigurationCmd, } fs := listWorkloadIdentityPoolCmd.Flags() @@ -44,11 +43,6 @@ func NewListWorkloadIdentityConfiguration() *cobra.Command { return listWorkloadIdentityPoolCmd } -func validationForListWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - // No validation needed - return nil -} - func listWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { // Create a context: ctx := context.Background() diff --git a/cmd/ocm/gcp/update-wif-config.go b/cmd/ocm/gcp/update-wif-config.go index 59cacadc..236484db 100644 --- a/cmd/ocm/gcp/update-wif-config.go +++ b/cmd/ocm/gcp/update-wif-config.go @@ -7,7 +7,6 @@ import ( "github.com/openshift-online/ocm-cli/pkg/gcp" "github.com/openshift-online/ocm-cli/pkg/ocm" - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -18,26 +17,21 @@ var UpdateWifConfigOpts struct { // NewUpdateWorkloadIdentityConfiguration provides the "gcp update wif-config" subcommand func NewUpdateWorkloadIdentityConfiguration() *cobra.Command { updateWifConfigCmd := &cobra.Command{ - Use: "wif-config [ID|Name]", - Short: "Update wif-config.", - RunE: updateWorkloadIdentityConfigurationCmd, - PreRunE: validationForUpdateWorkloadIdentityConfigurationCmd, + Use: "wif-config [ID|Name]", + Short: "Update wif-config.", + RunE: updateWorkloadIdentityConfigurationCmd, } return updateWifConfigCmd } -func validationForUpdateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { - if err := wifKeyArgCheck(argv); err != nil { - return err - } - return nil -} - func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() log := log.Default() - key := argv[0] + key, err := wifKeyFromArgs(argv) + if err != nil { + return err + } // Create the client for the OCM API: connection, err := ocm.NewConnection().Build() @@ -81,26 +75,3 @@ func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) e return nil } - -// findWifConfig finds the WIF configuration by ID or name -func findWifConfig(client *cmv1.Client, key string) (*cmv1.WifConfig, error) { - collection := client.GCP().WifConfigs() - page := 1 - size := 1 - query := fmt.Sprintf( - "id = '%s' or display_name = '%s'", - key, key, - ) - - response, err := collection.List().Search(query).Page(page).Size(size).Send() - if err != nil { - return nil, err - } - if response.Total() == 0 { - return nil, fmt.Errorf("WIF configuration with identifier or name '%s' not found", key) - } - if response.Total() > 1 { - return nil, fmt.Errorf("there are %d WIF configurations found with identifier or name '%s'", response.Total(), key) - } - return response.Items().Slice()[0], nil -} From b8ef9ba4ec1ae83884093cb8af6dc40cffea5566 Mon Sep 17 00:00:00 2001 From: Jakob Gray <20209054+JakobGray@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:18:57 -0400 Subject: [PATCH 5/5] Update WIF scripting logic --- cmd/ocm/gcp/create-wif-config.go | 26 +----- cmd/ocm/gcp/delete-wif-config.go | 5 ++ cmd/ocm/gcp/gcp-client-shim.go | 32 ++++---- cmd/ocm/gcp/gcp.go | 1 - cmd/ocm/gcp/generate-wif-script.go | 72 ----------------- cmd/ocm/gcp/helpers.go | 30 +++++++ cmd/ocm/gcp/scripting.go | 126 ++++++++++++++++++++++++++--- cmd/ocm/gcp/update-wif-config.go | 43 ++++++++-- 8 files changed, 209 insertions(+), 126 deletions(-) delete mode 100644 cmd/ocm/gcp/generate-wif-script.go diff --git a/cmd/ocm/gcp/create-wif-config.go b/cmd/ocm/gcp/create-wif-config.go index 2f59858c..67cb7808 100644 --- a/cmd/ocm/gcp/create-wif-config.go +++ b/cmd/ocm/gcp/create-wif-config.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "log" - "os" - "path/filepath" "strconv" "github.com/openshift-online/ocm-cli/pkg/gcp" @@ -65,26 +63,10 @@ func validationForCreateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, arg return fmt.Errorf("Project is required") } - if CreateWifConfigOpts.TargetDir == "" { - pwd, err := os.Getwd() - if err != nil { - return errors.Wrapf(err, "failed to get current directory") - } - - CreateWifConfigOpts.TargetDir = pwd - } - - fPath, err := filepath.Abs(CreateWifConfigOpts.TargetDir) + var err error + CreateWifConfigOpts.TargetDir, err = getPathFromFlag(CreateWifConfigOpts.TargetDir) if err != nil { - return errors.Wrapf(err, "failed to resolve full path") - } - - sResult, err := os.Stat(fPath) - if os.IsNotExist(err) { - return fmt.Errorf("directory %s does not exist", fPath) - } - if !sResult.IsDir() { - return fmt.Errorf("file %s exists and is not a directory", fPath) + return err } return nil } @@ -116,7 +98,7 @@ func createWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) e if err != nil { return errors.Wrapf(err, "failed to get project number from id") } - err = createScript(CreateWifConfigOpts.TargetDir, wifConfig, projectNum) + err = createCreateScript(CreateWifConfigOpts.TargetDir, wifConfig, projectNum) if err != nil { return errors.Wrapf(err, "Failed to create script files") } diff --git a/cmd/ocm/gcp/delete-wif-config.go b/cmd/ocm/gcp/delete-wif-config.go index d2f3f854..29beb83c 100644 --- a/cmd/ocm/gcp/delete-wif-config.go +++ b/cmd/ocm/gcp/delete-wif-config.go @@ -42,6 +42,11 @@ func NewDeleteWorkloadIdentityConfiguration() *cobra.Command { } func validationForDeleteWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { + var err error + DeleteWifConfigOpts.TargetDir, err = getPathFromFlag(DeleteWifConfigOpts.TargetDir) + if err != nil { + return err + } return nil } diff --git a/cmd/ocm/gcp/gcp-client-shim.go b/cmd/ocm/gcp/gcp-client-shim.go index 51e444fe..2a2fc86e 100644 --- a/cmd/ocm/gcp/gcp-client-shim.go +++ b/cmd/ocm/gcp/gcp-client-shim.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "sort" "strings" "time" @@ -86,8 +87,6 @@ func (c *shim) CreateWorkloadIdentityPool( } else { return errors.Wrapf(err, "failed to check if there is existing workload identity pool %s", poolId) } - } else { - log.Printf("Workload identity pool %s exists", poolId) } return nil @@ -136,8 +135,6 @@ func (c *shim) CreateWorkloadIdentityProvider( return errors.Wrapf(err, "failed to check if there is existing workload identity provider %s in pool %s", providerId, poolId) } - } else { - log.Printf("Workload identity provider %s exists", providerId) } return nil @@ -175,7 +172,6 @@ func (c *shim) GrantSupportAccess( if err := c.bindRolesToGroup(ctx, support.Principal(), support.Roles()); err != nil { return err } - log.Printf("support access granted to %s", support.Principal()) return nil } @@ -255,9 +251,11 @@ func (c *shim) createOrUpdateRoles( log.Printf("Role %q undeleted", roleID) } - // Update role if permissions have changed - if c.roleRequiresUpdate(permissions, existingRole.IncludedPermissions) { - existingRole.IncludedPermissions = permissions + if addedPermissions, needsUpdate := c.missingPermissions(permissions, existingRole.IncludedPermissions); needsUpdate { + // Add missing permissions + existingRole.IncludedPermissions = append(existingRole.IncludedPermissions, addedPermissions...) + sort.Strings(existingRole.IncludedPermissions) + _, err := c.updateRole(ctx, existingRole, c.fmtRoleResourceId(role)) if err != nil { return errors.Wrap(err, fmt.Sprintf("Failed to update %s", roleID)) @@ -268,23 +266,27 @@ func (c *shim) createOrUpdateRoles( return nil } -func (c *shim) roleRequiresUpdate( +// missingPermissions returns true if there are new permissions that are not in the existing permissions +// and returns the list of missing permissions +func (c *shim) missingPermissions( newPermissions []string, existingPermissions []string, -) bool { +) ([]string, bool) { + missing := []string{} permissionMap := map[string]bool{} for _, permission := range existingPermissions { permissionMap[permission] = true } - if len(permissionMap) != len(newPermissions) { - return true - } for _, permission := range newPermissions { if !permissionMap[permission] { - return true + missing = append(missing, permission) } } - return false + if len(missing) > 0 { + return missing, true + } else { + return missing, false + } } func (c *shim) bindRolesToServiceAccount( diff --git a/cmd/ocm/gcp/gcp.go b/cmd/ocm/gcp/gcp.go index 9cbbc68b..7486dc6e 100644 --- a/cmd/ocm/gcp/gcp.go +++ b/cmd/ocm/gcp/gcp.go @@ -30,7 +30,6 @@ func NewGcpCmd() *cobra.Command { gcpCmd.AddCommand(NewGetCmd()) gcpCmd.AddCommand(NewListCmd()) gcpCmd.AddCommand(NewDescribeCmd()) - gcpCmd.AddCommand(NewGenerateCommand()) return gcpCmd } diff --git a/cmd/ocm/gcp/generate-wif-script.go b/cmd/ocm/gcp/generate-wif-script.go deleted file mode 100644 index f6d1ce48..00000000 --- a/cmd/ocm/gcp/generate-wif-script.go +++ /dev/null @@ -1,72 +0,0 @@ -package gcp - -import ( - "context" - "log" - - "github.com/openshift-online/ocm-cli/pkg/gcp" - "github.com/openshift-online/ocm-cli/pkg/ocm" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // CreateWorkloadIdentityPoolOpts captures the options that affect creation of the workload identity pool - GenerateScriptOpts = options{ - TargetDir: "", - } -) - -func NewGenerateCommand() *cobra.Command { - generateScriptCmd := &cobra.Command{ - Use: "generate [wif-config ID|Name]", - Short: "Generate script based on a wif-config", - Args: cobra.ExactArgs(1), - RunE: generateCreateScriptCmd, - } - - generateScriptCmd.PersistentFlags().StringVar(&GenerateScriptOpts.TargetDir, "output-dir", "", - "Directory to place generated files (defaults to current directory)") - - return generateScriptCmd -} - -func generateCreateScriptCmd(cmd *cobra.Command, argv []string) error { - ctx := context.Background() - key, err := wifKeyFromArgs(argv) - if err != nil { - return err - } - - // Create the client for the OCM API: - connection, err := ocm.NewConnection().Build() - if err != nil { - return errors.Wrapf(err, "Failed to create OCM connection") - } - defer connection.Close() - - gcpClient, err := gcp.NewGcpClient(ctx) - if err != nil { - errors.Wrapf(err, "failed to initiate GCP client") - } - - // Verify the WIF configuration exists - wifConfig, err := findWifConfig(connection.ClustersMgmt().V1(), key) - if err != nil { - return errors.Wrapf(err, "failed to get wif-config") - } - - projectNum, err := gcpClient.ProjectNumberFromId(ctx, wifConfig.Gcp().ProjectId()) - if err != nil { - return errors.Wrapf(err, "failed to get project number from id") - } - - log.Printf("Writing script files to %s", GenerateScriptOpts.TargetDir) - if err := createScript(GenerateScriptOpts.TargetDir, wifConfig, projectNum); err != nil { - return errors.Wrapf(err, "failed to generate create script") - } - if err := createDeleteScript(GenerateScriptOpts.TargetDir, wifConfig); err != nil { - return errors.Wrapf(err, "failed to generate delete script") - } - return nil -} diff --git a/cmd/ocm/gcp/helpers.go b/cmd/ocm/gcp/helpers.go index 19ca216e..6298d28c 100644 --- a/cmd/ocm/gcp/helpers.go +++ b/cmd/ocm/gcp/helpers.go @@ -2,8 +2,11 @@ package gcp import ( "fmt" + "os" + "path/filepath" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + "github.com/pkg/errors" ) // Checks for WIF config name or id in input @@ -45,3 +48,30 @@ func findWifConfig(client *cmv1.Client, key string) (*cmv1.WifConfig, error) { } return response.Items().Slice()[0], nil } + +// getPathFromFlag validates the filepath +func getPathFromFlag(targetDir string) (string, error) { + if targetDir == "" { + pwd, err := os.Getwd() + if err != nil { + return "", errors.Wrapf(err, "failed to get current directory") + } + + return pwd, nil + } + + fPath, err := filepath.Abs(targetDir) + if err != nil { + return "", errors.Wrapf(err, "failed to resolve full path") + } + + sResult, err := os.Stat(fPath) + if os.IsNotExist(err) { + return "", fmt.Errorf("directory %s does not exist", fPath) + } + if !sResult.IsDir() { + return "", fmt.Errorf("file %s exists and is not a directory", fPath) + } + + return targetDir, nil +} diff --git a/cmd/ocm/gcp/scripting.go b/cmd/ocm/gcp/scripting.go index 8f54f111..c528d38f 100644 --- a/cmd/ocm/gcp/scripting.go +++ b/cmd/ocm/gcp/scripting.go @@ -9,10 +9,28 @@ import ( cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) -func createScript(targetDir string, wifConfig *cmv1.WifConfig, projectNum int64) error { +const bashShebang = "#!/bin/bash\n" + +func createCreateScript(targetDir string, wifConfig *cmv1.WifConfig, projectNum int64) error { // Write the script content to the path - scriptContent := generateScriptContent(wifConfig, projectNum) - err := os.WriteFile(filepath.Join(targetDir, "script.sh"), []byte(scriptContent), 0600) + scriptContent := generateCreateScriptContent(wifConfig, projectNum) + err := os.WriteFile(filepath.Join(targetDir, "create.sh"), []byte(scriptContent), 0600) + if err != nil { + return err + } + // Write jwk json file to the path + jwkPath := filepath.Join(targetDir, "jwk.json") + err = os.WriteFile(jwkPath, []byte(wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().Jwks()), 0600) + if err != nil { + return err + } + return nil +} + +func createUpdateScript(targetDir string, wifConfig *cmv1.WifConfig, projectNum int64) error { + // Write the script content to the path + scriptContent := generateUpdateScriptContent(wifConfig, projectNum) + err := os.WriteFile(filepath.Join(targetDir, "apply.sh"), []byte(scriptContent), 0600) if err != nil { return err } @@ -36,7 +54,7 @@ func createDeleteScript(targetDir string, wifConfig *cmv1.WifConfig) error { } func generateDeleteScriptContent(wifConfig *cmv1.WifConfig) string { - scriptContent := "#!/bin/bash\n" + scriptContent := bashShebang // Append the script to delete the service accounts scriptContent += deleteServiceAccountScriptContent(wifConfig) @@ -67,8 +85,8 @@ gcloud iam workload-identity-pools delete %s --project=%s --location=global `, pool.PoolId(), wifConfig.Gcp().ProjectId()) } -func generateScriptContent(wifConfig *cmv1.WifConfig, projectNum int64) string { - scriptContent := "#!/bin/bash\n" +func generateCreateScriptContent(wifConfig *cmv1.WifConfig, projectNum int64) string { + scriptContent := bashShebang // Create a script to create the workload identity pool scriptContent += createIdentityPoolScriptContent(wifConfig) @@ -84,6 +102,23 @@ func generateScriptContent(wifConfig *cmv1.WifConfig, projectNum int64) string { return scriptContent } +func generateUpdateScriptContent(wifConfig *cmv1.WifConfig, projectNum int64) string { + scriptContent := bashShebang + + // Create a script to create the workload identity pool + scriptContent += createIdentityPoolScriptContent(wifConfig) + + // Append the script to create the identity provider + scriptContent += createIdentityProviderScriptContent(wifConfig) + + // Append the script to create/update the service accounts + scriptContent += updateServiceAccountScriptContent(wifConfig, projectNum) + + scriptContent += grantSupportAccessScriptContent(wifConfig) + + return scriptContent +} + func createIdentityPoolScriptContent(wifConfig *cmv1.WifConfig) string { name := wifConfig.Gcp().WorkloadIdentityPool().PoolId() project := wifConfig.Gcp().ProjectId() @@ -125,6 +160,44 @@ func createServiceAccountScriptContent(wifConfig *cmv1.WifConfig, projectNum int var sb strings.Builder sb.WriteString("\n# Create service accounts:\n") + sb.WriteString(createServiceAccountScript(wifConfig)) + + sb.WriteString("\n# Create custom roles for service accounts:\n") + sb.WriteString(createCustomRoleScript(wifConfig)) + + sb.WriteString("\n# Bind roles to service accounts:\n") + sb.WriteString(addRoleBindingsScript(wifConfig)) + + sb.WriteString("\n# Grant access to service accounts:\n") + sb.WriteString(grantServiceAccountAccessScript(wifConfig, projectNum)) + + return sb.String() +} + +func updateServiceAccountScriptContent(wifConfig *cmv1.WifConfig, projectNum int64) string { + // For each service account, create a service account and bind it to the workload identity pool + var sb strings.Builder + + sb.WriteString("\n# Create service accounts:\n") + sb.WriteString(createServiceAccountScript(wifConfig)) + + sb.WriteString("\n# Create custom roles for service accounts:\n") + sb.WriteString(createCustomRoleScript(wifConfig)) + + sb.WriteString("\n# Update custom roles for service accounts:\n") + sb.WriteString(updateCustomRolesScript(wifConfig)) + + sb.WriteString("\n# Bind roles to service accounts:\n") + sb.WriteString(addRoleBindingsScript(wifConfig)) + + sb.WriteString("\n# Grant access to service accounts:\n") + sb.WriteString(grantServiceAccountAccessScript(wifConfig, projectNum)) + + return sb.String() +} + +func createServiceAccountScript(wifConfig *cmv1.WifConfig) string { + var sb strings.Builder for _, sa := range wifConfig.Gcp().ServiceAccounts() { project := wifConfig.Gcp().ProjectId() serviceAccountID := sa.ServiceAccountId() @@ -134,11 +207,15 @@ func createServiceAccountScriptContent(wifConfig *cmv1.WifConfig, projectNum int sb.WriteString(fmt.Sprintf("gcloud iam service-accounts create %s --display-name=%s --description=\"%s\" --project=%s\n", serviceAccountID, serviceAccountName, serviceAccountDesc, project)) } - sb.WriteString("\n# Create custom roles for service accounts:\n") + return sb.String() +} + +func createCustomRoleScript(wifConfig *cmv1.WifConfig) string { + var sb strings.Builder for _, sa := range wifConfig.Gcp().ServiceAccounts() { for _, role := range sa.Roles() { if !role.Predefined() { - roleId := strings.ReplaceAll(role.RoleId(), "-", "_") + roleId := role.RoleId() project := wifConfig.Gcp().ProjectId() permissions := strings.Join(role.Permissions(), ",") roleName := roleId @@ -149,7 +226,27 @@ func createServiceAccountScriptContent(wifConfig *cmv1.WifConfig, projectNum int } } } - sb.WriteString("\n# Bind roles to service accounts:\n") + return sb.String() +} + +func updateCustomRolesScript(wifConfig *cmv1.WifConfig) string { + var sb strings.Builder + for _, sa := range wifConfig.Gcp().ServiceAccounts() { + for _, role := range sa.Roles() { + if !role.Predefined() { + project := wifConfig.Gcp().ProjectId() + permissions := strings.Join(role.Permissions(), ",") + //nolint:lll + sb.WriteString(fmt.Sprintf("gcloud iam roles update %s --project=%s --permissions=%s\n", + role.RoleId(), project, permissions)) + } + } + } + return sb.String() +} + +func addRoleBindingsScript(wifConfig *cmv1.WifConfig) string { + var sb strings.Builder for _, sa := range wifConfig.Gcp().ServiceAccounts() { for _, role := range sa.Roles() { project := wifConfig.Gcp().ProjectId() @@ -164,7 +261,11 @@ func createServiceAccountScriptContent(wifConfig *cmv1.WifConfig, projectNum int project, member, roleResource)) } } - sb.WriteString("\n# Grant access to service accounts:\n") + return sb.String() +} + +func grantServiceAccountAccessScript(wifConfig *cmv1.WifConfig, projectNum int64) string { + var sb strings.Builder for _, sa := range wifConfig.Gcp().ServiceAccounts() { if sa.AccessMethod() == "wif" { project := wifConfig.Gcp().ProjectId() @@ -198,13 +299,16 @@ func grantSupportAccessScriptContent(wifConfig *cmv1.WifConfig) string { sb.WriteString("\n# Create custom roles for support:\n") for _, role := range roles { if !role.Predefined() { - roleId := strings.ReplaceAll(role.RoleId(), "-", "_") + roleId := role.RoleId() permissions := strings.Join(role.Permissions(), ",") roleName := roleId roleDesc := roleDescription + " for WIF config " + wifConfig.DisplayName() //nolint:lll sb.WriteString(fmt.Sprintf("gcloud iam roles create %s --project=%s --title=%s --description=\"%s\" --stage=GA --permissions=%s\n", roleId, project, roleName, roleDesc, permissions)) + //nolint:lll + sb.WriteString(fmt.Sprintf("gcloud iam roles update %s --project=%s --permissions=%s\n", + roleId, project, permissions)) } } diff --git a/cmd/ocm/gcp/update-wif-config.go b/cmd/ocm/gcp/update-wif-config.go index 236484db..8e3945f2 100644 --- a/cmd/ocm/gcp/update-wif-config.go +++ b/cmd/ocm/gcp/update-wif-config.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "strconv" "github.com/openshift-online/ocm-cli/pkg/gcp" "github.com/openshift-online/ocm-cli/pkg/ocm" @@ -11,20 +12,39 @@ import ( "github.com/spf13/cobra" ) -var UpdateWifConfigOpts struct { -} +var ( + UpdateWifConfigOpts = options{ + DryRun: false, + TargetDir: "", + } +) // NewUpdateWorkloadIdentityConfiguration provides the "gcp update wif-config" subcommand func NewUpdateWorkloadIdentityConfiguration() *cobra.Command { updateWifConfigCmd := &cobra.Command{ - Use: "wif-config [ID|Name]", - Short: "Update wif-config.", - RunE: updateWorkloadIdentityConfigurationCmd, + Use: "wif-config [ID|Name]", + Short: "Update wif-config.", + RunE: updateWorkloadIdentityConfigurationCmd, + PreRunE: validationForUpdateWorkloadIdentityConfigurationCmd, } + updateWifConfigCmd.PersistentFlags().BoolVar(&UpdateWifConfigOpts.DryRun, "dry-run", false, + "Skip creating objects, and just save what would have been created into files") + updateWifConfigCmd.PersistentFlags().StringVar(&UpdateWifConfigOpts.TargetDir, "output-dir", "", + "Directory to place generated files (defaults to current directory)") + return updateWifConfigCmd } +func validationForUpdateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { + var err error + UpdateWifConfigOpts.TargetDir, err = getPathFromFlag(UpdateWifConfigOpts.TargetDir) + if err != nil { + return err + } + return nil +} + func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { ctx := context.Background() log := log.Default() @@ -51,6 +71,19 @@ func updateWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) e return errors.Wrapf(err, "failed to initiate GCP client") } + if UpdateWifConfigOpts.DryRun { + log.Printf("Writing script files to %s", UpdateWifConfigOpts.TargetDir) + projectNumInt64, err := strconv.ParseInt(wifConfig.Gcp().ProjectNumber(), 10, 64) + if err != nil { + return errors.Wrapf(err, "failed to parse project number from WifConfig") + } + + if err := createUpdateScript(UpdateWifConfigOpts.TargetDir, wifConfig, projectNumInt64); err != nil { + return errors.Wrapf(err, "failed to generate script files") + } + return nil + } + // Re-apply WIF resources gcpClientWifConfigShim := NewGcpClientWifConfigShim(GcpClientWifConfigShimSpec{ GcpClient: gcpClient,