Skip to content

Commit

Permalink
Merge pull request #644 from JakobGray/OCM-8008
Browse files Browse the repository at this point in the history
Add wif config cluster create option
  • Loading branch information
ckandag authored Aug 5, 2024
2 parents 75ac687 + 78317e9 commit 57ce1d5
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 43 deletions.
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
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
56 changes: 56 additions & 0 deletions pkg/provider/wif_configs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package provider

import (
"fmt"

"github.com/openshift-online/ocm-cli/pkg/arguments"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
)

func getWifConfigs(client *cmv1.Client) (wifConfigs []*cmv1.WifConfig, err error) {
collection := client.GCP().WifConfigs()
page := 1
size := 100
for {
var response *cmv1.WifConfigsListResponse
response, err = collection.List().
Page(page).
Size(size).
Send()
if err != nil {
return
}
wifConfigs = append(wifConfigs, response.Items().Slice()...)
if response.Size() < size {
break
}
page++
}

if len(wifConfigs) == 0 {
return nil, fmt.Errorf("no WIF configurations available")
}
return
}

func GetWifConfigs(client *cmv1.Client) (wifConfigs []*cmv1.WifConfig, err error) {
return getWifConfigs(client)
}

// GetWifConfigNameOptions returns the wif config options for the cluster
// with display name as the value and id as the description
func GetWifConfigNameOptions(client *cmv1.Client) (options []arguments.Option, err error) {
wifConfigs, err := getWifConfigs(client)
if err != nil {
err = fmt.Errorf("failed to retrieve WIF configurations: %s", err)
return
}

for _, wc := range wifConfigs {
options = append(options, arguments.Option{
Value: wc.DisplayName(),
Description: wc.ID(),
})
}
return
}

0 comments on commit 57ce1d5

Please sign in to comment.