Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wif config cluster create option #644

Merged
merged 1 commit into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 162 additions & 29 deletions cmd/ocm/create/cluster/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ var args struct {
clusterWideProxy c.ClusterWideProxy
gcpServiceAccountFile arguments.FilePath
gcpSecureBoot c.GcpSecurity
gcpAuthentication c.GcpAuthentication
gcpWifConfig string
etcdEncryption bool
subscriptionType string
marketplaceGcpTerms bool
Expand Down Expand Up @@ -125,6 +127,14 @@ func parseSubscriptionType(subscriptionTypeOption string) string {
return strings.Split(subscriptionTypeOption, " ")[0]
}

func setWifConfigOption(id, name string) string {
return fmt.Sprintf("%s (%s)", name, id)
}

func parseWifConfigOption(wifConfigOption string) string {
return strings.Split(wifConfigOption, " ")[0]
}

// Cmd Constant:
var Cmd = &cobra.Command{
Use: "cluster [flags] NAME",
Expand Down Expand Up @@ -364,6 +374,24 @@ func init() {
"Secure Boot enables the use of Shielded VMs in the Google Cloud Platform.",
)
arguments.SetQuestion(fs, "secure-boot-for-shielded-vms", "Secure boot support for Shielded VMs:")

fs.StringVar(
&args.gcpAuthentication.Type,
"gcp-auth-type",
c.AuthenticationWif,
"Method of authenticating GCP cluster",
)
arguments.SetQuestion(fs, "gcp-auth-type", "Authentication method:")
fs.MarkHidden("gcp-auth-type")

fs.StringVar(
&args.gcpWifConfig,
"wif-config",
"",
"Specifies the GCP Workload Identity Federation config used to authenticate.",
)
arguments.SetQuestion(fs, "wif-config", "WIF Configuration:")
Cmd.RegisterFlagCompletionFunc("wif-config", arguments.MakeCompleteFunc(getWifConfigNameOptions))
}

func osdProviderOptions(_ *sdk.Connection) ([]arguments.Option, error) {
Expand All @@ -373,6 +401,13 @@ func osdProviderOptions(_ *sdk.Connection) ([]arguments.Option, error) {
}, nil
}

func gcpAuthenticationOptions(_ *sdk.Connection) ([]arguments.Option, error) {
return []arguments.Option{
{Value: c.AuthenticationWif, Description: ""},
{Value: c.AuthenticationKey, Description: ""},
}, nil
}

func getRegionOptions(connection *sdk.Connection) ([]arguments.Option, error) {
regions, err := provider.GetRegions(connection.ClustersMgmt().V1(), args.provider, args.ccs)
if err != nil {
Expand Down Expand Up @@ -499,6 +534,27 @@ func getMachineTypeOptions(connection *sdk.Connection) ([]arguments.Option, erro
args.provider, args.ccs.Enabled)
}

func getWifConfigOptions(wifConfigs []*cmv1.WifConfig) ([]arguments.Option, error) {
options := []arguments.Option{}
for _, wc := range wifConfigs {
option := wifConfigOption(wc.ID(), wc.DisplayName())
options = append(options, option)
}
return options, nil
}

func wifConfigOption(id string, name string) arguments.Option {
return arguments.Option{
Value: setWifConfigOption(id, name),
}
}

// getWifConfigNameOptions returns the wif config options for the cluster
// with display name as the value
func getWifConfigNameOptions(connection *sdk.Connection) ([]arguments.Option, error) {
return provider.GetWifConfigNameOptions(connection.ClustersMgmt().V1())
}

func networkTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{c.NetworkTypeSDN, c.NetworkTypeOVN}, cobra.ShellCompDirectiveDefault
}
Expand Down Expand Up @@ -589,6 +645,11 @@ func preRun(cmd *cobra.Command, argv []string) error {
return err
}

err = promptAuthentication(fs, connection)
if err != nil {
return err
}

err = arguments.PromptBool(fs, "multi-az")
if err != nil {
return err
Expand Down Expand Up @@ -754,6 +815,7 @@ func run(cmd *cobra.Command, argv []string) error {
DefaultIngress: defaultIngress,
SubscriptionType: args.subscriptionType,
GcpSecurity: args.gcpSecureBoot,
GcpAuthentication: args.gcpAuthentication,
}

cluster, err := c.CreateCluster(connection.ClustersMgmt().V1(), clusterConfig, args.dryRun)
Expand Down Expand Up @@ -1258,43 +1320,114 @@ func promptCCS(fs *pflag.FlagSet, presetCCS bool) error {
if err != nil {
return err
}
if args.ccs.Enabled {
switch args.provider {
case c.ProviderAWS:
err = arguments.PromptString(fs, "aws-account-id")
if err != nil {
return err
}

err = arguments.PromptString(fs, "aws-access-key-id")
if err != nil {
return err
}
err = arguments.CheckIgnoredCCSFlags(args.ccs)
if err != nil {
return err
}
return nil
}

err = arguments.PromptPassword(fs, "aws-secret-access-key")
if err != nil {
return err
}
case c.ProviderGCP:
// TODO: re-prompt when selected file is not readable / invalid JSON
err = arguments.PromptFilePath(fs, "service-account-file", true)
if err != nil {
return err
}
func promptAuthentication(fs *pflag.FlagSet, connection *sdk.Connection) error {
var err error
if !args.ccs.Enabled {
return nil
}
switch args.provider {
case c.ProviderAWS:
err = arguments.PromptString(fs, "aws-account-id")
if err != nil {
return err
}

if args.gcpServiceAccountFile == "" {
return fmt.Errorf("A valid GCP service account file must be specified for CCS clusters")
}
err = constructGCPCredentials(args.gcpServiceAccountFile, &args.ccs)
if err != nil {
return err
}
err = arguments.PromptString(fs, "aws-access-key-id")
if err != nil {
return err
}

err = arguments.PromptPassword(fs, "aws-secret-access-key")
if err != nil {
return err
}
case c.ProviderGCP:
err = promptGcpAuth(fs, connection)
if err != nil {
return err
}
}
err = arguments.CheckIgnoredCCSFlags(args.ccs)
return nil
}

func promptGcpAuth(fs *pflag.FlagSet, connection *sdk.Connection) error {
var err error

isWif := fs.Changed("wif-config")
isNonWif := fs.Changed("service-account-file")
if isWif && isNonWif {
return fmt.Errorf("can't use both wif-config and GCP service account file at the same time")
}

if !isWif && !isNonWif {
options, _ := gcpAuthenticationOptions(connection)
err = arguments.PromptOneOf(fs, "gcp-auth-type", options)
if err != nil {
return err
}
}
if isWif {
args.gcpAuthentication.Type = c.AuthenticationWif
} else if isNonWif {
args.gcpAuthentication.Type = c.AuthenticationKey
}

switch args.gcpAuthentication.Type {
case c.AuthenticationWif:
err = promptWifConfig(fs, connection)
if err != nil {
return err
}
case c.AuthenticationKey:
// TODO: re-prompt when selected file is not readable / invalid JSON
err = arguments.PromptFilePath(fs, "service-account-file", true)
if err != nil {
return err
}

if args.gcpServiceAccountFile == "" {
return fmt.Errorf("a valid GCP service account file must be specified for CCS clusters")
}
err = constructGCPCredentials(args.gcpServiceAccountFile, &args.ccs)
if err != nil {
return err
}
}
return nil
}

func promptWifConfig(fs *pflag.FlagSet, connection *sdk.Connection) error {
wifConfigs, err := provider.GetWifConfigs(connection.ClustersMgmt().V1())
if err != nil {
return err
}
options, err := getWifConfigOptions(wifConfigs)
if err != nil {
return err
}
err = arguments.PromptOneOf(fs, "wif-config", options)
if err != nil {
return err
}
if args.interactive {
args.gcpWifConfig = parseWifConfigOption(args.gcpWifConfig)
}

// map wif name to wif id
wifMapping := map[string]string{}
for _, wc := range wifConfigs {
wifMapping[wc.DisplayName()] = wc.ID()
}

args.gcpAuthentication.Id = wifMapping[args.gcpWifConfig]
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/nwidger/jsoncolor v0.3.2
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.8
github.com/openshift-online/ocm-sdk-go v0.1.422
github.com/openshift-online/ocm-sdk-go v0.1.433
github.com/openshift/rosa v1.2.24
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,8 @@ github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
github.com/openshift-online/ocm-sdk-go v0.1.422 h1:NWXLNTg7sLgUJRM3tyuk/QuVbUCRuMH+aLlbCKNzXWc=
github.com/openshift-online/ocm-sdk-go v0.1.422/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/openshift-online/ocm-sdk-go v0.1.433 h1:8i0Si9KrFkMprMBGR1a4ppmgVezMBjjXXkvhv28OVUk=
github.com/openshift-online/ocm-sdk-go v0.1.433/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/openshift/rosa v1.2.24 h1:vv0yYnWHx6CCPEAau/0rS54P2ksaf+uWXb1TQPWxiYE=
github.com/openshift/rosa v1.2.24/go.mod h1:MVXB27O3PF8WoOic23I03mmq6/9kVxpFx6FKyLMCyrQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
Expand Down
52 changes: 38 additions & 14 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const (

NetworkTypeSDN = "OpenShiftSDN"
NetworkTypeOVN = "OVNKubernetes"

AuthenticationWif = "Workload Identity Federation (WIF)"
AuthenticationKey = "Service account"
)

type DefaultIngressSpec struct {
Expand Down Expand Up @@ -92,6 +95,9 @@ type Spec struct {

// Gcp-specific settings
GcpSecurity GcpSecurity

// GCP Authentication settings
GcpAuthentication GcpAuthentication
}

type Autoscaling struct {
Expand Down Expand Up @@ -151,6 +157,11 @@ type GcpSecurity struct {
SecureBoot bool `json:"secure_boot,omitempty"`
}

type GcpAuthentication struct {
Type string
Id string
}

type AddOnItem struct {
ID string
Name string
Expand Down Expand Up @@ -404,21 +415,34 @@ func CreateCluster(cmv1Client *cmv1.Client, config Spec, dryRun bool) (*cmv1.Clu
}
clusterBuilder = clusterBuilder.AWS(awsBuilder)
case ProviderGCP:
if config.CCS.GCP.Type == "" || config.CCS.GCP.ClientEmail == "" ||
config.CCS.GCP.ProjectID == "" {
return nil, fmt.Errorf("Missing credentials for GCP CCS cluster")
switch config.GcpAuthentication.Type {
case AuthenticationWif:
if config.GcpAuthentication.Id == "" {
return nil, fmt.Errorf("missing WIF config ID")
}
gcpAuth := cmv1.NewGcpAuthentication().
Kind(cmv1.WifConfigKind).
Id(config.GcpAuthentication.Id)
gcpBuilder.Authentication(gcpAuth)
case AuthenticationKey:
if config.CCS.GCP.Type == "" || config.CCS.GCP.ClientEmail == "" ||
config.CCS.GCP.ProjectID == "" {
return nil, fmt.Errorf("missing credentials for GCP CCS cluster")
}
gcpBuilder.
Type(config.CCS.GCP.Type).
ProjectID(config.CCS.GCP.ProjectID).
PrivateKeyID(config.CCS.GCP.PrivateKeyID).
PrivateKey(config.CCS.GCP.PrivateKey).
ClientEmail(config.CCS.GCP.ClientEmail).
ClientID(config.CCS.GCP.ClientID).
AuthURI(config.CCS.GCP.AuthURI).
TokenURI(config.CCS.GCP.TokenURI).
AuthProviderX509CertURL(config.CCS.GCP.AuthProviderX509CertURL).
ClientX509CertURL(config.CCS.GCP.ClientX509CertURL)
default:
return nil, fmt.Errorf("unexpected GCP authentication method %q", config.GcpAuthentication.Type)
}
gcpBuilder.
Type(config.CCS.GCP.Type).
ProjectID(config.CCS.GCP.ProjectID).
PrivateKeyID(config.CCS.GCP.PrivateKeyID).
PrivateKey(config.CCS.GCP.PrivateKey).
ClientEmail(config.CCS.GCP.ClientEmail).
ClientID(config.CCS.GCP.ClientID).
AuthURI(config.CCS.GCP.AuthURI).
TokenURI(config.CCS.GCP.TokenURI).
AuthProviderX509CertURL(config.CCS.GCP.AuthProviderX509CertURL).
ClientX509CertURL(config.CCS.GCP.ClientX509CertURL)

if isGCPNetworkExists(config.ExistingVPC) {
gcpNetwork := cmv1.NewGCPNetwork().VPCName(config.ExistingVPC.VPCName).
Expand Down
Loading
Loading