diff --git a/cmd/mapt/cmd/aws/services/openshift-snc.go b/cmd/mapt/cmd/aws/services/openshift-snc.go index ed0c73ff2..0cec707e7 100644 --- a/cmd/mapt/cmd/aws/services/openshift-snc.go +++ b/cmd/mapt/cmd/aws/services/openshift-snc.go @@ -3,6 +3,7 @@ package services import ( params "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" maptContext "github.com/redhat-developer/mapt/pkg/manager/context" + sncAPI "github.com/redhat-developer/mapt/pkg/provider/api/openshift-snc" openshiftsnc "github.com/redhat-developer/mapt/pkg/provider/aws/action/openshift-snc" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -55,9 +56,8 @@ func createSNC() *cobra.Command { DebugLevel: viper.GetUint(params.DebugLevel), Tags: viper.GetStringMapString(params.Tags), }, - &openshiftsnc.OpenshiftSNCArgs{ + &sncAPI.OpenshiftSNCArgs{ ComputeRequest: params.ComputeRequestArgs(), - Spot: params.SpotArgs(), Version: viper.GetString(ocpVersion), Arch: viper.GetString(params.LinuxArch), PullSecretFile: viper.GetString(pullSecretFile), diff --git a/cmd/mapt/cmd/azure/azure.go b/cmd/mapt/cmd/azure/azure.go index 17e67dee7..ea8e6d64f 100644 --- a/cmd/mapt/cmd/azure/azure.go +++ b/cmd/mapt/cmd/azure/azure.go @@ -35,6 +35,7 @@ func GetCmd() *cobra.Command { hosts.GetUbuntuCmd(), hosts.GetRHELCmd(), hosts.GetFedoraCmd(), - services.GetAKSCmd()) + services.GetAKSCmd(), + services.GetOpenshiftSNCCmd()) return c } diff --git a/cmd/mapt/cmd/azure/services/openshift-snc.go b/cmd/mapt/cmd/azure/services/openshift-snc.go new file mode 100644 index 000000000..f8255b62b --- /dev/null +++ b/cmd/mapt/cmd/azure/services/openshift-snc.go @@ -0,0 +1,117 @@ +package services + +import ( + "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" + maptContext "github.com/redhat-developer/mapt/pkg/manager/context" + sncAPI "github.com/redhat-developer/mapt/pkg/provider/api/openshift-snc" + openshiftsnc "github.com/redhat-developer/mapt/pkg/provider/azure/action/openshift-snc" + "github.com/redhat-developer/mapt/pkg/util/logging" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + cmdOpenshiftSNC = "openshift-snc" + cmdOpenshiftSNCDesc = "Manage an OpenShift Single Node Cluster based on OpenShift Local. This is not intended for production use" + + ocpVersion = "version" + ocpVersionDesc = "version for Openshift. If not set it will pick latest available version" + pullSecretFile = "pull-secret-file" + pullSecretFileDesc = "file path of image pull secret (download from https://console.redhat.com/openshift/create/local)" + + location = "location" + locationDesc = "If spot is passed location will be calculated based on spot results. Otherwise localtion will be used to create resources." + defaultLocation = "centralindia" +) + +func GetOpenshiftSNCCmd() *cobra.Command { + c := &cobra.Command{ + Use: cmdOpenshiftSNC, + Short: cmdOpenshiftSNCDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.Flags()); err != nil { + return err + } + return nil + }, + } + flagSet := pflag.NewFlagSet(cmdOpenshiftSNC, pflag.ExitOnError) + params.AddCommonFlags(flagSet) + c.PersistentFlags().AddFlagSet(flagSet) + c.AddCommand(createSNC(), destroySNC()) + return c + +} + +func createSNC() *cobra.Command { + c := &cobra.Command{ + Use: params.CreateCmdName, + Short: params.CreateCmdName, + RunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.Flags()); err != nil { + return err + } + + if err := openshiftsnc.Create( + &maptContext.ContextArgs{ + ProjectName: viper.GetString(params.ProjectName), + BackedURL: viper.GetString(params.BackedURL), + ResultsOutput: viper.GetString(params.ConnectionDetailsOutput), + Debug: viper.IsSet(params.Debug), + DebugLevel: viper.GetUint(params.DebugLevel), + Tags: viper.GetStringMapString(params.Tags), + }, + &sncAPI.OpenshiftSNCArgs{ + ComputeRequest: params.ComputeRequestArgs(), + Version: viper.GetString(ocpVersion), + Arch: viper.GetString(params.LinuxArch), + PullSecretFile: viper.GetString(pullSecretFile), + Spot: params.SpotArgs(), + Location: viper.GetString(location), + Timeout: viper.GetString(params.Timeout)}); err != nil { + logging.Error(err) + } + return nil + }, + } + flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError) + flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc) + flagSet.StringP(ocpVersion, "", "", ocpVersionDesc) + flagSet.StringP(params.LinuxArch, "", params.LinuxArchDefault, params.LinuxArchDesc) + flagSet.StringP(pullSecretFile, "", "", pullSecretFileDesc) + flagSet.StringP(params.Timeout, "", "", params.TimeoutDesc) + flagSet.StringP(location, "", defaultLocation, locationDesc) + flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc) + params.AddComputeRequestFlags(flagSet) + params.AddSpotFlags(flagSet) + c.PersistentFlags().AddFlagSet(flagSet) + return c +} + +func destroySNC() *cobra.Command { + c := &cobra.Command{ + Use: params.DestroyCmdName, + Short: params.DestroyCmdName, + RunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.Flags()); err != nil { + return err + } + + if err := openshiftsnc.Destroy(&maptContext.ContextArgs{ + ProjectName: viper.GetString(params.ProjectName), + BackedURL: viper.GetString(params.BackedURL), + Debug: viper.IsSet(params.Debug), + DebugLevel: viper.GetUint(params.DebugLevel), + Serverless: viper.IsSet(params.Serverless), + }); err != nil { + logging.Error(err) + } + return nil + }, + } + flagSet := pflag.NewFlagSet(params.DestroyCmdName, pflag.ExitOnError) + flagSet.Bool(params.Serverless, false, params.ServerlessDesc) + c.PersistentFlags().AddFlagSet(flagSet) + return c +} diff --git a/pkg/provider/aws/action/openshift-snc/cloudconfig.go b/pkg/provider/api/openshift-snc/cloudconfig.go similarity index 58% rename from pkg/provider/aws/action/openshift-snc/cloudconfig.go rename to pkg/provider/api/openshift-snc/cloudconfig.go index ed6f5f42f..088221a96 100644 --- a/pkg/provider/aws/action/openshift-snc/cloudconfig.go +++ b/pkg/provider/api/openshift-snc/cloudconfig.go @@ -7,7 +7,7 @@ import ( "github.com/redhat-developer/mapt/pkg/util/file" ) -type dataValues struct { +type CloudConfigDataValues struct { // user auth information Username string PubKey string @@ -17,15 +17,16 @@ type dataValues struct { SSMPullSecretName string SSMKubeAdminPasswordName string SSMDeveloperPasswordName string + // Unprotected, used for azure + PullSecret string + PassDeveloper string + PassKubeadmin string } -//go:embed cloud-config -var CloudConfig []byte +var AWSCloudConfigRequiredProfiles = []string{"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"} -var cloudConfigRequiredProfiles = []string{"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"} - -func cloudConfig(data dataValues) (*string, error) { - templateConfig := string(CloudConfig[:]) +func GenCloudConfig(data CloudConfigDataValues, cloudConfig []byte) (*string, error) { + templateConfig := string(cloudConfig[:]) cc, err := file.Template(data, templateConfig) if err != nil { return nil, err diff --git a/pkg/provider/api/openshift-snc/snc.go b/pkg/provider/api/openshift-snc/snc.go new file mode 100644 index 000000000..24b58d74f --- /dev/null +++ b/pkg/provider/api/openshift-snc/snc.go @@ -0,0 +1,45 @@ +package openshiftsnc + +import ( + cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request" + "github.com/redhat-developer/mapt/pkg/provider/api/spot" +) + +var ( + ConsoleURLRegex = "https://console-openshift-console.apps.%s.nip.io" + + OutputHost = "aosHost" + OutputUsername = "aosUsername" + OutputUserPrivateKey = "aosPrivatekey" + OutputKubeconfig = "aosKubeconfig" + OutputKubeAdminPass = "aosKubeAdminPasss" + OutputDeveloperPass = "aosDeveloperPass" + + CommandReadiness = "while [ ! -f /tmp/.crc-cluster-ready ]; do sleep 5; done" + CommandCaServiceRan = "sudo bash -c 'until oc get node --kubeconfig /opt/kubeconfig --context system:admin || oc get node --kubeconfig /opt/crc/kubeconfig --context system:admin; do sleep 5; done'" + + // portHTTP = 80 + PortHTTPS = 443 + PortAPI = 6443 +) + +type OpenshiftSNCArgs struct { + Location string + Prefix string + ComputeRequest *cr.ComputeRequestArgs + Version string + Arch string + PullSecretFile string + Spot *spot.SpotArgs + Timeout string +} + +type OpenshiftSncResultsMetadata struct { + Username string `json:"username"` + PrivateKey string `json:"private_key"` + Host string `json:"host"` + Kubeconfig string `json:"kubeconfig"` + KubeadminPass string `json:"kubeadmin_pass"` + SpotPrice *float64 `json:"spot_price,omitempty"` + ConsoleUrl string `json:"console_url,omitempty"` +} diff --git a/pkg/provider/aws/action/openshift-snc/constants.go b/pkg/provider/aws/action/openshift-snc/constants.go index 762a837df..65a547120 100644 --- a/pkg/provider/aws/action/openshift-snc/constants.go +++ b/pkg/provider/aws/action/openshift-snc/constants.go @@ -16,22 +16,6 @@ var ( amiOwner = "391597328979" // amiOriginRegion = "us-east-1" - consoleURLRegex = "https://console-openshift-console.apps.%s.nip.io" - - outputHost = "aosHost" - outputUsername = "aosUsername" - outputUserPrivateKey = "aosPrivatekey" - outputKubeconfig = "aosKubeconfig" - outputKubeAdminPass = "aosKubeAdminPasss" - outputDeveloperPass = "aosDeveloperPass" - - commandReadiness = "while [ ! -f /tmp/.crc-cluster-ready ]; do sleep 5; done" - commandCaServiceRan = "sudo bash -c 'until oc get node --kubeconfig /opt/kubeconfig --context system:admin || oc get node --kubeconfig /opt/crc/kubeconfig --context system:admin; do sleep 5; done'" - - // portHTTP = 80 - portHTTPS = 443 - portAPI = 6443 - // SSM ocpPullSecretID = "ocppullsecretid" kapass = "kapass" diff --git a/pkg/provider/aws/action/openshift-snc/openshift-snc.go b/pkg/provider/aws/action/openshift-snc/openshift-snc.go index 91cf99619..0b8e38d22 100644 --- a/pkg/provider/aws/action/openshift-snc/openshift-snc.go +++ b/pkg/provider/aws/action/openshift-snc/openshift-snc.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "strings" + _ "embed" "github.com/go-playground/validator/v10" "github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2" @@ -12,8 +13,7 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "github.com/redhat-developer/mapt/pkg/manager" mc "github.com/redhat-developer/mapt/pkg/manager/context" - cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request" - spotTypes "github.com/redhat-developer/mapt/pkg/provider/api/spot" + sncAPI "github.com/redhat-developer/mapt/pkg/provider/api/openshift-snc" "github.com/redhat-developer/mapt/pkg/provider/aws" awsConstants "github.com/redhat-developer/mapt/pkg/provider/aws/constants" "github.com/redhat-developer/mapt/pkg/provider/aws/data" @@ -35,16 +35,6 @@ import ( resourcesUtil "github.com/redhat-developer/mapt/pkg/util/resources" ) -type OpenshiftSNCArgs struct { - Prefix string - ComputeRequest *cr.ComputeRequestArgs - Version string - Arch string - PullSecretFile string - Spot *spotTypes.SpotArgs - Timeout string -} - type openshiftSNCRequest struct { mCtx *mc.Context prefix *string @@ -56,6 +46,9 @@ type openshiftSNCRequest struct { allocationData *allocation.AllocationResult } +//go:embed cloud-config +var CloudConfig []byte + func (r *openshiftSNCRequest) validate() error { v := validator.New(validator.WithRequiredStructEnabled()) err := v.Var(r.mCtx, "required") @@ -65,20 +58,10 @@ func (r *openshiftSNCRequest) validate() error { return v.Struct(r) } -type OpenshiftSncResultsMetadata struct { - Username string `json:"username"` - PrivateKey string `json:"private_key"` - Host string `json:"host"` - Kubeconfig string `json:"kubeconfig"` - KubeadminPass string `json:"kubeadmin_pass"` - SpotPrice *float64 `json:"spot_price,omitempty"` - ConsoleUrl string `json:"console_url,omitempty"` -} - // Create orchestrate 3 stacks: // If spot is enable it will run best spot option to get the best option to spin the machine // Then it will run the stack for windows dedicated host -func Create(mCtxArgs *mc.ContextArgs, args *OpenshiftSNCArgs) (_ *OpenshiftSncResultsMetadata, err error) { +func Create(mCtxArgs *mc.ContextArgs, args *sncAPI.OpenshiftSNCArgs) (_ *sncAPI.OpenshiftSncResultsMetadata, err error) { // Create mapt Context mCtx, err := mc.Init(mCtxArgs, aws.Provider()) if err != nil { @@ -146,7 +129,7 @@ func Destroy(mCtxArgs *mc.ContextArgs) (err error) { return nil } -func (r *openshiftSNCRequest) createCluster() (*OpenshiftSncResultsMetadata, error) { +func (r *openshiftSNCRequest) createCluster() (*sncAPI.OpenshiftSncResultsMetadata, error) { cs := manager.Stack{ StackName: r.mCtx.StackNameByProject(stackName), ProjectName: r.mCtx.ProjectName(), @@ -198,7 +181,7 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error { if err != nil { return err } - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUserPrivateKey), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, sncAPI.OutputUserPrivateKey), keyResources.PrivateKey.PrivateKeyPem) if r.mCtx.Debug() { keyResources.PrivateKey.PrivateKeyPem.ApplyT( @@ -213,7 +196,7 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error { return err } // Instance profile required by logic within userdata - iProfile, err := iam.InstanceProfile(ctx, r.prefix, &awsOCPSNCID, cloudConfigRequiredProfiles) + iProfile, err := iam.InstanceProfile(ctx, r.prefix, &awsOCPSNCID, sncAPI.AWSCloudConfigRequiredProfiles) if err != nil { return err } @@ -222,9 +205,9 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error { if err != nil { return err } - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputKubeAdminPass), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, sncAPI.OutputKubeAdminPass), kaPass) - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputDeveloperPass), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, sncAPI.OutputDeveloperPass), devPass) // Create instance cr := compute.ComputeRequest{ @@ -240,7 +223,7 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error { DiskSize: &diskSize, LB: nw.LoadBalancer, Eip: nw.Eip, - LBTargetGroups: []int{securityGroup.SSH_PORT, portHTTPS, portAPI}, + LBTargetGroups: []int{securityGroup.SSH_PORT, sncAPI.PortHTTPS, sncAPI.PortAPI}, SpotPrice: *r.allocationData.SpotPrice, Spot: true, InstanceProfile: iProfile, @@ -251,9 +234,9 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error { if err != nil { return err } - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUsername), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, sncAPI.OutputUsername), pulumi.String(amiUserDefault)) - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputHost), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, sncAPI.OutputHost), c.GetHostIP(true)) if len(*r.timeout) > 0 { if err = serverless.OneTimeDelayedTask(ctx, r.mCtx, @@ -272,42 +255,42 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error { if err != nil { return err } - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputKubeconfig), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, sncAPI.OutputKubeconfig), pulumi.ToSecret(kubeconfig)) return nil } // Write exported values in context to files o a selected target folder -func (r *openshiftSNCRequest) manageResults(stackResult auto.UpResult, prefix *string) (*OpenshiftSncResultsMetadata, error) { - username, err := getResultOutput(outputUsername, stackResult, prefix) +func (r *openshiftSNCRequest) manageResults(stackResult auto.UpResult, prefix *string) (*sncAPI.OpenshiftSncResultsMetadata, error) { + username, err := getResultOutput(sncAPI.OutputUsername, stackResult, prefix) if err != nil { return nil, err } - privateKey, err := getResultOutput(outputUserPrivateKey, stackResult, prefix) + privateKey, err := getResultOutput(sncAPI.OutputUserPrivateKey, stackResult, prefix) if err != nil { return nil, err } - host, err := getResultOutput(outputHost, stackResult, prefix) + host, err := getResultOutput(sncAPI.OutputHost, stackResult, prefix) if err != nil { return nil, err } - kubeconfig, err := getResultOutput(outputKubeconfig, stackResult, prefix) + kubeconfig, err := getResultOutput(sncAPI.OutputKubeconfig, stackResult, prefix) if err != nil { return nil, err } - kubeAdminPass, err := getResultOutput(outputKubeAdminPass, stackResult, prefix) + kubeAdminPass, err := getResultOutput(sncAPI.OutputKubeAdminPass, stackResult, prefix) if err != nil { return nil, err } - hostIPKey := fmt.Sprintf("%s-%s", *prefix, outputHost) + hostIPKey := fmt.Sprintf("%s-%s", *prefix, sncAPI.OutputHost) results := map[string]string{ - fmt.Sprintf("%s-%s", *prefix, outputUsername): "username", - fmt.Sprintf("%s-%s", *prefix, outputUserPrivateKey): "id_rsa", + fmt.Sprintf("%s-%s", *prefix, sncAPI.OutputUsername): "username", + fmt.Sprintf("%s-%s", *prefix, sncAPI.OutputUserPrivateKey): "id_rsa", hostIPKey: "host", - fmt.Sprintf("%s-%s", *prefix, outputKubeconfig): "kubeconfig", - fmt.Sprintf("%s-%s", *prefix, outputKubeAdminPass): "kubeadmin_pass", - fmt.Sprintf("%s-%s", *prefix, outputDeveloperPass): "developer_pass", + fmt.Sprintf("%s-%s", *prefix, sncAPI.OutputKubeconfig): "kubeconfig", + fmt.Sprintf("%s-%s", *prefix, sncAPI.OutputKubeAdminPass): "kubeadmin_pass", + fmt.Sprintf("%s-%s", *prefix, sncAPI.OutputDeveloperPass): "developer_pass", } outputPath := r.mCtx.GetResultsOutputPath() @@ -319,12 +302,12 @@ func (r *openshiftSNCRequest) manageResults(stackResult auto.UpResult, prefix *s } } - consoleURL := fmt.Sprintf(consoleURLRegex, host) + consoleURL := fmt.Sprintf(sncAPI.ConsoleURLRegex, host) if eip, ok := stackResult.Outputs[hostIPKey].Value.(string); ok { - fmt.Printf("Cluster has been started you can access console at: %s.\n", fmt.Sprintf(consoleURLRegex, eip)) + fmt.Printf("Cluster has been started you can access console at: %s.\n", fmt.Sprintf(sncAPI.ConsoleURLRegex, eip)) } - return &OpenshiftSncResultsMetadata{ + return &sncAPI.OpenshiftSncResultsMetadata{ Username: username, PrivateKey: privateKey, Host: host, @@ -344,8 +327,8 @@ func securityGroups(ctx *pulumi.Context, mCtx *mc.Context, prefix *string, VPC: vpc, Description: fmt.Sprintf("sg for %s", awsOCPSNCID), IngressRules: []securityGroup.IngressRules{securityGroup.SSH_TCP, - {Description: "Console", FromPort: portHTTPS, ToPort: portHTTPS, Protocol: "tcp"}, - {Description: "API", FromPort: portAPI, ToPort: portAPI, Protocol: "tcp"}}, + {Description: "Console", FromPort: sncAPI.PortHTTPS, ToPort: sncAPI.PortHTTPS, Protocol: "tcp"}, + {Description: "API", FromPort: sncAPI.PortAPI, ToPort: sncAPI.PortAPI, Protocol: "tcp"}}, }.Create(ctx, mCtx) if err != nil { return nil, err @@ -427,13 +410,13 @@ func (r *openshiftSNCRequest) userData(ctx *pulumi.Context, dependecies = append(dependecies, devPassParam) ccB64 := pulumi.All(newPublicKey, lbEIP).ApplyT( func(args []interface{}) (string, error) { - ccB64, err := cloudConfig(dataValues{ + ccB64, err := sncAPI.GenCloudConfig(sncAPI.CloudConfigDataValues{ Username: amiUserDefault, PubKey: args[0].(string), PublicIP: args[1].(string), SSMPullSecretName: *psName, SSMKubeAdminPasswordName: *kaPassName, - SSMDeveloperPasswordName: *devPassName}) + SSMDeveloperPasswordName: *devPassName}, CloudConfig) return *ccB64, err }).(pulumi.StringOutput) @@ -451,7 +434,7 @@ func kubeconfig(ctx *pulumi.Context, // Check cluster is ready ocpReadyCmd, err := c.RunCommand(ctx, - commandReadiness, + sncAPI.CommandReadiness, compute.LoggingCmdStd, fmt.Sprintf("%s-ocp-readiness", *prefix), awsOCPSNCID, mk, amiUserDefault, nil, c.Dependencies) @@ -460,7 +443,7 @@ func kubeconfig(ctx *pulumi.Context, } // Check ocp-cluster-ca.service succeeds ocpCaRotatedCmd, err := c.RunCommand(ctx, - commandCaServiceRan, + sncAPI.CommandCaServiceRan, compute.LoggingCmdStd, fmt.Sprintf("%s-ocp-ca-rotated", *prefix), awsOCPSNCID, mk, amiUserDefault, nil, []pulumi.Resource{ocpReadyCmd}) diff --git a/pkg/provider/azure/action/linux/linux.go b/pkg/provider/azure/action/linux/linux.go index c6bd824d9..654050a3b 100644 --- a/pkg/provider/azure/action/linux/linux.go +++ b/pkg/provider/azure/action/linux/linux.go @@ -178,7 +178,6 @@ func (r *linuxRequest) deployer(ctx *pulumi.Context) error { return fmt.Errorf("error creating RHEL Server on Azure: %v", err) } } - vm, err := virtualmachine.Create(ctx, r.mCtx, &virtualmachine.VirtualMachineArgs{ Prefix: *r.prefix, @@ -194,7 +193,7 @@ func (r *linuxRequest) deployer(ctx *pulumi.Context) error { AdminUsername: *r.username, PrivateKey: privateKey, SpotPrice: r.allocationData.Price, - Userdata: userDataB64, + Userdata: pulumi.String(userDataB64), Location: *r.allocationData.Location, }) if err != nil { diff --git a/pkg/provider/azure/action/openshift-snc/cloud-config b/pkg/provider/azure/action/openshift-snc/cloud-config new file mode 100644 index 000000000..6ad50b731 --- /dev/null +++ b/pkg/provider/azure/action/openshift-snc/cloud-config @@ -0,0 +1,35 @@ +#cloud-config +runcmd: + - systemctl enable --now kubelet +write_files: +- path: /home/core/.ssh/authorized_keys + content: {{ .PubKey }} + owner: {{ .Username }} + permissions: '0600' +- path: /opt/crc/id_rsa.pub + content: {{ .PubKey }} + owner: root:root + permissions: '0644' +- content: | + CRC_CLOUD=1 + CRC_NETWORK_MODE_USER=0 + owner: root:root + path: /etc/sysconfig/crc-env + permissions: '0644' +- path: /opt/crc/pull-secret + content: | + {{ .PullSecret }} + owner: root:root + permissions: '0644' +- path: /opt/crc/pass_developer + content: '{{ .PassDeveloper }}' + owner: root:root + permissions: '0644' +- path: /opt/crc/pass_kubeadmin + content: '{{ .PassKubeadmin }}' + owner: root:root + permissions: '0644' +- path: /opt/crc/eip + content: '{{ .PublicIP }}' + owner: root:root + permissions: '0644' diff --git a/pkg/provider/azure/action/openshift-snc/openshift-snc.go b/pkg/provider/azure/action/openshift-snc/openshift-snc.go new file mode 100644 index 000000000..f92b2c707 --- /dev/null +++ b/pkg/provider/azure/action/openshift-snc/openshift-snc.go @@ -0,0 +1,321 @@ +package openshiftsnc + +import ( + _ "embed" + "fmt" + "os" + + "github.com/go-playground/validator/v10" + "github.com/pulumi/pulumi-azure-native-sdk/resources/v3" + "github.com/pulumi/pulumi-command/sdk/go/command/remote" + "github.com/pulumi/pulumi-tls/sdk/v5/go/tls" + "github.com/pulumi/pulumi/sdk/v3/go/auto" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/redhat-developer/mapt/pkg/manager" + mc "github.com/redhat-developer/mapt/pkg/manager/context" + infra "github.com/redhat-developer/mapt/pkg/provider" + sncAPI "github.com/redhat-developer/mapt/pkg/provider/api/openshift-snc" + "github.com/redhat-developer/mapt/pkg/provider/azure" + "github.com/redhat-developer/mapt/pkg/provider/azure/data" + "github.com/redhat-developer/mapt/pkg/provider/azure/modules/allocation" + "github.com/redhat-developer/mapt/pkg/provider/azure/modules/network" + virtualmachine "github.com/redhat-developer/mapt/pkg/provider/azure/modules/virtual-machine" + securityGroup "github.com/redhat-developer/mapt/pkg/provider/azure/services/network/security-group" + "github.com/redhat-developer/mapt/pkg/provider/util/command" + "github.com/redhat-developer/mapt/pkg/provider/util/output" + "github.com/redhat-developer/mapt/pkg/provider/util/security" + "github.com/redhat-developer/mapt/pkg/util" + "github.com/redhat-developer/mapt/pkg/util/logging" + resourcesUtil "github.com/redhat-developer/mapt/pkg/util/resources" +) + +// move the cloud init template code to common openshift-snc api package +// parameterized it to modify the cloud-config based on the provider to +// SSM for pull secret in AWS and Key Vault in case of Azure + +const ( + stackAzureSNC = "stackAzureOpenshiftSNC" + + azureSNCId = "azsnc" + + outputHost = "asncHost" + outputUsername = "asncUsername" + outputUserPrivateKey = "asncUserPrivatekey" + outputKubeadminPass = "asncKubeadminPass" + outputDeveloperPass = "asncDeveloperPass" + defaultVMSize = "Standard_D8as_v5" +) + +var ( + // snc + defaultUsername = "core" +) + +type openshiftSNCRequest struct { + mCtx *mc.Context `validate:"required"` + prefix *string + arch *string + osType *data.OSType + version *string + username *string + pullSecretFile string + allocationData *allocation.AllocationResult +} + +//go:embed cloud-config +var CloudConfig []byte + +func (r *openshiftSNCRequest) validate() error { + v := validator.New(validator.WithRequiredStructEnabled()) + err := v.Var(r.mCtx, "required") + if err != nil { + return err + } + return v.Struct(r) +} + +func Create(mCtxArgs *mc.ContextArgs, args *sncAPI.OpenshiftSNCArgs) (err error) { + // Create mapt Context + mCtx, err := mc.Init(mCtxArgs, azure.Provider()) + if err != nil { + return err + } + + osType := data.OpenShiftSNC + prefix := util.If(len(args.Prefix) > 0, args.Prefix, "main") + r := &openshiftSNCRequest{ + mCtx: mCtx, + prefix: &prefix, + arch: &args.Arch, + osType: &osType, + version: &args.Version, + username: &defaultUsername, + pullSecretFile: args.PullSecretFile, + } + + ir, err := data.GetImageRef(*r.osType, *r.arch, *r.version) + if err != nil { + return err + } + + r.allocationData, err = allocation.Allocation(mCtx, + &allocation.AllocationArgs{ + ComputeRequest: args.ComputeRequest, + OSType: "linux", + ImageRef: ir, + Location: &args.Location, + Spot: args.Spot}) + if err != nil { + return err + } + + logging.Debug("Creating Linux Server") + cs := manager.Stack{ + StackName: mCtx.StackNameByProject(stackAzureSNC), + ProjectName: mCtx.ProjectName(), + BackedURL: mCtx.BackedURL(), + ProviderCredentials: azure.DefaultCredentials, + DeployFunc: r.deployer, + } + sr, err := manager.UpStack(mCtx, cs) + if err != nil { + logging.Debugf("Error during upstack: %v", err) + } + return r.manageResults(sr) +} + +func Destroy(mCtxArgs *mc.ContextArgs) error { + // Create mapt Context + mCtx, err := mc.Init(mCtxArgs, azure.Provider()) + if err != nil { + return err + } + // destroy + return azure.Destroy(mCtx, stackAzureSNC) +} + +// Main function to deploy all requried resources to azure +func (r *openshiftSNCRequest) deployer(ctx *pulumi.Context) error { + if err := r.validate(); err != nil { + return err + } + + // get suitable location for the resource group + rgLocation := azure.GetSuitableLocationForResourceGroup(*r.allocationData.Location) + rg, err := resources.NewResourceGroup(ctx, + resourcesUtil.GetResourceName(*r.prefix, azureSNCId, "rg"), + &resources.ResourceGroupArgs{ + Location: pulumi.String(rgLocation), + ResourceGroupName: pulumi.String(r.mCtx.RunID()), + Tags: r.mCtx.ResourceTags(), + }) + if err != nil { + return err + } + // Networking + sg, err := securityGroups(ctx, r.mCtx, r.prefix, r.allocationData.Location, rg) + if err != nil { + return err + } + n, err := network.Create(ctx, r.mCtx, &network.NetworkArgs{ + Prefix: *r.prefix, + ComponentID: azureSNCId, + ResourceGroup: rg, + Location: r.allocationData.Location, + SecurityGroup: sg, + }) + if err != nil { + return err + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputHost), n.PublicIP.IpAddress) + // Virutal machine + privateKey, err := tls.NewPrivateKey( + ctx, + resourcesUtil.GetResourceName(*r.prefix, azureSNCId, "privatekey-user"), + &tls.PrivateKeyArgs{ + Algorithm: pulumi.String("RSA"), + RsaBits: pulumi.Int(4096), + }) + if err != nil { + return err + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUserPrivateKey), privateKey.PrivateKeyPem) + + // generate the snc cloud-config userdata + userDataB64, kaPass, devPass, err := r.getUserData(ctx, n.PublicIP.IpAddress, privateKey.PublicKeyOpenssh) + if err != nil { + return fmt.Errorf("error creating RHEL Server on Azure: %v", err) + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputKubeadminPass), kaPass) + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputDeveloperPass), devPass) + + vmArgs := &virtualmachine.VirtualMachineArgs{ + Prefix: *r.prefix, + ComponentID: azureSNCId, + ResourceGroup: rg, + NetworkInteface: n.NetworkInterface, + VMSize: util.If(len(r.allocationData.ComputeSizes) > 0, r.allocationData.ComputeSizes[0], string(defaultVMSize)), + ImageID: r.allocationData.ImageRef.ID, + AdminUsername: *r.username, + PrivateKey: privateKey, + SpotPrice: r.allocationData.Price, + Userdata: userDataB64, + DiskSizeGB: 256, + Location: *r.allocationData.Location, + } + vm, err := virtualmachine.Create(ctx, r.mCtx, vmArgs) + if err != nil { + return err + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUsername), pulumi.String(*r.username)) + _, err = remote.NewCommand(ctx, + resourcesUtil.GetResourceName(*r.prefix, azureSNCId, "cmd"), + &remote.CommandArgs{ + Connection: remote.ConnectionArgs{ + Host: n.PublicIP.IpAddress.Elem(), + PrivateKey: privateKey.PrivateKeyOpenssh, + User: pulumi.String(*r.username), + DialErrorLimit: pulumi.Int(-1), + }, + Create: pulumi.String(command.CommandPing), + Update: pulumi.String(command.CommandPing), + }, + pulumi.Timeouts( + &pulumi.CustomTimeouts{ + Create: "10m", + Update: "10m"}), + pulumi.DependsOn([]pulumi.Resource{vm})) + return err +} + +func Kubeconfig() { + +} + +// Write exported values in context to files o a selected target folder +func (r *openshiftSNCRequest) manageResults(stackResult auto.UpResult) error { + return output.Write(stackResult, r.mCtx.GetResultsOutputPath(), map[string]string{ + fmt.Sprintf("%s-%s", *r.prefix, outputUsername): "username", + fmt.Sprintf("%s-%s", *r.prefix, outputUserPrivateKey): "id_rsa", + fmt.Sprintf("%s-%s", *r.prefix, outputHost): "host", + fmt.Sprintf("%s-%s", *r.prefix, outputKubeadminPass): "kubeadmin_pass", + fmt.Sprintf("%s-%s", *r.prefix, outputDeveloperPass): "developer_pass", + }) +} + +func (r *openshiftSNCRequest) getUserData(ctx *pulumi.Context, publicIP pulumi.StringPtrOutput, pubKey pulumi.StringOutput) (pulumi.StringOutput, pulumi.StringOutput, pulumi.StringOutput, error) { + // KubeAdmin pass + kaPassword, err := security.CreatePassword(ctx, + resourcesUtil.GetResourceName( + *r.prefix, azureSNCId, "kubeadminpassword")) + if err != nil { + return pulumi.StringOutput{}, pulumi.StringOutput{}, pulumi.StringOutput{}, err + } + // Developer pass + devPassword, err := security.CreatePassword(ctx, + resourcesUtil.GetResourceName( + *r.prefix, azureSNCId, "devpassword")) + if err != nil { + return pulumi.StringOutput{}, pulumi.StringOutput{}, pulumi.StringOutput{}, err + } + // Manage pull secret + ps, err := os.ReadFile(r.pullSecretFile) + if err != nil { + return pulumi.StringOutput{}, pulumi.StringOutput{}, pulumi.StringOutput{}, err + } + + ccB64 := pulumi.All(pubKey, publicIP, kaPassword.Result, devPassword.Result).ApplyT( + func(args []interface{}) (string, error) { + var eip string + ip, ok := args[1].(*string) + if ok && ip != nil { + eip = *ip + } + ccB64, err := sncAPI.GenCloudConfig(sncAPI.CloudConfigDataValues{ + Username: defaultUsername, + PubKey: args[0].(string), + PublicIP: eip, + PullSecret: string(ps), + PassKubeadmin: args[2].(string), + PassDeveloper: args[3].(string), + }, CloudConfig) + return *ccB64, err + }).(pulumi.StringOutput) + + return ccB64, kaPassword.Result, devPassword.Result, err +} + +func securityGroups(ctx *pulumi.Context, mCtx *mc.Context, prefix, location *string, rg *resources.ResourceGroup) (securityGroup.SecurityGroup, error) { + sshIngressRule := securityGroup.SSH_TCP + sshIngressRule.CidrBlocks = infra.NETWORKING_CIDR_ANY_IPV4 + + consoleIngressRule := securityGroup.IngressRules{ + Description: "Console", + FromPort: sncAPI.PortHTTPS, + ToPort: sncAPI.PortHTTPS, + Protocol: "tcp", + } + + apiSrvIngressRule := securityGroup.IngressRules{ + Description: "API", + FromPort: sncAPI.PortAPI, + ToPort: sncAPI.PortAPI, + Protocol: "tcp", + } + + // Create SG with ingress rules + return securityGroup.Create( + ctx, + mCtx, + &securityGroup.SecurityGroupArgs{ + Name: resourcesUtil.GetResourceName(*prefix, azureSNCId, "sg"), + RG: rg, + Location: location, + IngressRules: []securityGroup.IngressRules{ + sshIngressRule, + consoleIngressRule, + apiSrvIngressRule, + }, + }, + ) +} diff --git a/pkg/provider/azure/data/compute-request.go b/pkg/provider/azure/data/compute-request.go index d9b0fc719..b4161f42b 100644 --- a/pkg/provider/azure/data/compute-request.go +++ b/pkg/provider/azure/data/compute-request.go @@ -116,8 +116,11 @@ type virtualMachine struct { PremiumIO bool AcceleratedNetworkingEnabled bool EncryptionAtHostSupported bool + DiskControllerTypes []string } +const DiskControllerTypeNVMe = "NVMe" + func (vm *virtualMachine) nestedVirtSupported() bool { dSeries := regexp.MustCompile(dSeriesPattern) if dSeries.Match([]byte(vm.Family)) { @@ -144,6 +147,13 @@ func (vm *virtualMachine) baseFeaturesSupported() bool { vm.emptyDiskSupported() && vm.hypervGen2Supported() } +func (vm *virtualMachine) nvmeSupported() bool { + if len(vm.DiskControllerTypes) > 0 { + return slices.Contains(vm.DiskControllerTypes, DiskControllerTypeNVMe) + } + return false +} + func resourceSKUToVirtualMachine(res *armcompute.ResourceSKU) *virtualMachine { if res.ResourceType != nil && *res.ResourceType != "virtualMachines" { return nil @@ -212,6 +222,8 @@ func resourceSKUToVirtualMachine(res *armcompute.ResourceSKU) *virtualMachine { vm.MaxResourceVolumeMB = int32(disk) case "VMDeploymentTypes": vm.VMDeploymentTypes = strings.Split(*capability.Value, ",") + case "DiskControllerTypes": + vm.DiskControllerTypes = strings.Split(*capability.Value, ",") default: continue } @@ -235,7 +247,8 @@ func filterCPUsAndMemory(args *cr.ComputeRequestArgs) filterFunc { if vm.VCPUs >= args.CPUs && vm.Memory >= args.MemoryGib && vm.Arch == args.Arch.String() && - vm.baseFeaturesSupported() { + vm.baseFeaturesSupported() && + !vm.nvmeSupported() { dSeries := regexp.MustCompile(lowerCpuPattern) if !dSeries.Match([]byte(vm.Name)) { vmCh <- vm.Name diff --git a/pkg/provider/azure/data/imageref.go b/pkg/provider/azure/data/imageref.go index f32c7076a..3077a37cc 100644 --- a/pkg/provider/azure/data/imageref.go +++ b/pkg/provider/azure/data/imageref.go @@ -11,6 +11,7 @@ const ( Ubuntu OSType = iota + 1 RHEL Fedora + OpenShiftSNC ) const fedoraImageGalleryBase = "/CommunityGalleries/Fedora-5e266ba4-2250-406d-adad-5d73860d958f/Images/" @@ -19,7 +20,7 @@ type ImageReference struct { Publisher string Offer string Sku string - // community gallery image ID + // community gallery image or custom image ID ID string } @@ -52,6 +53,14 @@ var ( ID: fedoraImageGalleryBase + "Fedora-Cloud-%s-Arm64/Versions/latest", }, }, + OpenShiftSNC: { + "x86_64": { + ID: "/subscriptions/b0ad4737-8299-4c0a-9dd5-959cbcf8d81c/resourceGroups/cloud-importer-resourceGroup-a558d7c1/providers/Microsoft.Compute/images/openshift-local-%s-%s", + }, + "arm64": { + ID: "", + }, + }, } ) @@ -76,6 +85,10 @@ func GetImageRef(osTarget OSType, arch string, version string) (*ImageReference, return &ImageReference{ ID: fmt.Sprintf(ir.ID, versions[0]), }, nil + case OpenShiftSNC: + return &ImageReference{ + ID: fmt.Sprintf(ir.ID, version, arch), + }, nil } return nil, fmt.Errorf("os type not supported") } diff --git a/pkg/provider/azure/data/images.go b/pkg/provider/azure/data/images.go index dff829b15..8134e1795 100644 --- a/pkg/provider/azure/data/images.go +++ b/pkg/provider/azure/data/images.go @@ -8,7 +8,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" - mc "github.com/redhat-developer/mapt/pkg/manager/context" "github.com/redhat-developer/mapt/pkg/util/logging" ) @@ -17,7 +16,7 @@ type ImageRequest struct { ImageReference } -func GetImage(req ImageRequest) (*armcompute.CommunityGalleryImagesClientGetResponse, error) { +func GetCommunityGalleryImage(req ImageRequest) (*armcompute.CommunityGalleryImagesClientGetResponse, error) { cred, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { return nil, err @@ -48,11 +47,46 @@ func GetImage(req ImageRequest) (*armcompute.CommunityGalleryImagesClientGetResp return nil, nil } -func IsImageOffered(mCtx *mc.Context, req ImageRequest) bool { - if _, err := GetImage(req); err != nil { - if mCtx.Debug() { +func GetCustomImage(req ImageRequest) (*armcompute.ImagesClientGetResponse, error) { + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, err + } + ctx := context.Background() + subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID") + + clientFactory, err := armcompute.NewClientFactory(subscriptionId, cred, nil) + if err != nil { + return nil, err + } + + if len(req.ID) > 0 { + // extract resource group and image name from ID url which looks like: + // /subscriptions/b0ad4737-8299-4c0a-9dd5-959cbcf8d81c/resourceGroups/cloud-importer-resourceGroup-a558d7c1/providers/Microsoft.Compute/images/openshift-local-%s-%s + parts := strings.Split(req.ID, "/") + if len(parts) != 9 { + return nil, fmt.Errorf("invalid custom image ID: %s", req.ID) + } + + res, err := clientFactory.NewImagesClient().Get(ctx, parts[4], parts[8], nil) + if err != nil { + return nil, err + } + return &res, nil + } + return nil, nil +} + +func IsImageOffered(req ImageRequest) bool { + if strings.Contains(req.ID, "CommunityGalleries") { + if _, err := GetCommunityGalleryImage(req); err != nil { logging.Debugf("error while checking if image available at location: %v", err) + return false } + return true + } + if _, err := GetCustomImage(req); err != nil { + logging.Debugf("error while checking if image available at location: %v", err) return false } return true diff --git a/pkg/provider/azure/data/spot.go b/pkg/provider/azure/data/spot.go index 5c82bfe1a..36d9c0708 100644 --- a/pkg/provider/azure/data/spot.go +++ b/pkg/provider/azure/data/spot.go @@ -183,7 +183,7 @@ func filterLocations(mCtx *mc.Context, args *SpotInfoArgs) ([]string, error) { if args.ImageRef != nil { locations = util.ArrayFilter(locations, func(location string) bool { - return IsImageOffered(mCtx, + return IsImageOffered( ImageRequest{ Region: location, ImageReference: *args.ImageRef, diff --git a/pkg/provider/azure/modules/virtual-machine/virtual-machine.go b/pkg/provider/azure/modules/virtual-machine/virtual-machine.go index dc46a39af..b19b3d16a 100644 --- a/pkg/provider/azure/modules/virtual-machine/virtual-machine.go +++ b/pkg/provider/azure/modules/virtual-machine/virtual-machine.go @@ -2,6 +2,7 @@ package virtualmachine import ( "fmt" + "strings" "github.com/pulumi/pulumi-azure-native-sdk/compute/v3" "github.com/pulumi/pulumi-azure-native-sdk/network/v3" @@ -11,6 +12,7 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" mc "github.com/redhat-developer/mapt/pkg/manager/context" "github.com/redhat-developer/mapt/pkg/provider/azure/data" + "github.com/redhat-developer/mapt/pkg/util" "github.com/redhat-developer/mapt/pkg/util/logging" resourcesUtil "github.com/redhat-developer/mapt/pkg/util/resources" ) @@ -38,8 +40,9 @@ type VirtualMachineArgs struct { PrivateKey *tls.PrivateKey AdminPasswd *random.RandomPassword // Linux optional - Userdata string - Location string + Userdata pulumi.StringInput + Location string + DiskSizeGB int } type VirtualMachine = *compute.VirtualMachine @@ -49,8 +52,7 @@ type VirtualMachine = *compute.VirtualMachine func Create(ctx *pulumi.Context, mCtx *mc.Context, args *VirtualMachineArgs) (VirtualMachine, error) { var imageReferenceArgs compute.ImageReferenceArgs if len(args.ImageID) > 0 { - imageReferenceArgs = compute.ImageReferenceArgs{ - CommunityGalleryImageId: pulumi.String(args.ImageID)} + imageReferenceArgs = getImageRefArgs(args.ImageID) } else { finalSku, err := data.SkuG2Support(args.Location, args.Publisher, args.Offer, args.Sku) if err != nil { @@ -81,7 +83,7 @@ func Create(ctx *pulumi.Context, mCtx *mc.Context, args *VirtualMachineArgs) (Vi ImageReference: imageReferenceArgs, OsDisk: compute.OSDiskArgs{ Name: pulumi.String(mCtx.RunID()), - DiskSizeGB: pulumi.Int(diskSize), + DiskSizeGB: util.If(args.DiskSizeGB > 0, pulumi.Int(args.DiskSizeGB), pulumi.Int(diskSize)), CreateOption: pulumi.String("FromImage"), Caching: compute.CachingTypesReadWrite, ManagedDisk: compute.ManagedDiskParametersArgs{ @@ -98,6 +100,7 @@ func Create(ctx *pulumi.Context, mCtx *mc.Context, args *VirtualMachineArgs) (Vi OsProfile: osProfile(mCtx.RunID(), args), Tags: mCtx.ResourceTags(), + UserData: args.Userdata, } if args.SpotPrice != nil { vmArgs.Priority = pulumi.String(prioritySpot) @@ -105,9 +108,6 @@ func Create(ctx *pulumi.Context, mCtx *mc.Context, args *VirtualMachineArgs) (Vi MaxPrice: pulumi.Float64(*args.SpotPrice), } } - if len(args.Userdata) > 0 { - vmArgs.UserData = pulumi.String(args.Userdata) - } logging.Debug("About to create the VM with compute.NewVirtualMachine") return compute.NewVirtualMachine(ctx, resourcesUtil.GetResourceName(args.Prefix, args.ComponentID, "vm"), @@ -138,3 +138,14 @@ func osProfile(computerName string, args *VirtualMachineArgs) compute.OSProfileA } return osProfile } + +func getImageRefArgs(imageID string) compute.ImageReferenceArgs { + if strings.Contains(imageID, "Community") { + return compute.ImageReferenceArgs{ + CommunityGalleryImageId: pulumi.String(imageID), + } + } + return compute.ImageReferenceArgs{ + Id: pulumi.String(imageID), + } +} diff --git a/pkg/provider/azure/services/network/security-group/security-group.go b/pkg/provider/azure/services/network/security-group/security-group.go index 99c4f5a66..818667178 100644 --- a/pkg/provider/azure/services/network/security-group/security-group.go +++ b/pkg/provider/azure/services/network/security-group/security-group.go @@ -40,9 +40,6 @@ func Create(ctx *pulumi.Context, mCtx *mc.Context, args *SecurityGroupArgs) (Sec if err != nil { return nil, err } - if err != nil { - return nil, err - } return nsg, nil }