diff --git a/cmd/mapt/cmd/aws/services/kind.go b/cmd/mapt/cmd/aws/services/kind.go index 69b5554b9..a5e2c2ace 100644 --- a/cmd/mapt/cmd/aws/services/kind.go +++ b/cmd/mapt/cmd/aws/services/kind.go @@ -6,7 +6,7 @@ import ( "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" maptContext "github.com/redhat-developer/mapt/pkg/manager/context" "github.com/redhat-developer/mapt/pkg/provider/aws/action/kind" - kindCloudConfig "github.com/redhat-developer/mapt/pkg/provider/util/cloud-config/kind" + kindApi "github.com/redhat-developer/mapt/pkg/targets/service/kind" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -41,11 +41,11 @@ func createKind() *cobra.Command { } // Parse extra port mappings from JSON string to PortMapping struct - var extraPortMappings []kindCloudConfig.PortMapping + var extraPortMappings []kindApi.PortMapping extraPortMappingsStr := viper.GetString(params.KindExtraPortMappings) if extraPortMappingsStr != "" { var err error - extraPortMappings, err = kindCloudConfig.ParseExtraPortMappings(extraPortMappingsStr) + extraPortMappings, err = kindApi.ParseExtraPortMappings(extraPortMappingsStr) if err != nil { return fmt.Errorf("failed to parse 'extra-port-mappings' flag: %w", err) } @@ -60,7 +60,7 @@ func createKind() *cobra.Command { DebugLevel: viper.GetUint(params.DebugLevel), Tags: viper.GetStringMapString(params.Tags), }, - &kind.KindArgs{ + &kindApi.KindArgs{ ComputeRequest: params.ComputeRequestArgs(), Spot: params.SpotArgs(), Version: viper.GetString(params.KindK8SVersion), @@ -74,7 +74,7 @@ func createKind() *cobra.Command { } flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError) flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc) - flagSet.StringP(params.KindK8SVersion, "", "", params.KindK8SVersionDesc) + flagSet.StringP(params.KindK8SVersion, "", params.KindK8SVersionDefault, params.KindK8SVersionDesc) flagSet.StringP(params.LinuxArch, "", params.LinuxArchDefault, params.LinuxArchDesc) flagSet.StringP(params.KindExtraPortMappings, "", "", params.KindExtraPortMappingsDesc) flagSet.StringP(params.Timeout, "", "", params.TimeoutDesc) diff --git a/cmd/mapt/cmd/azure/azure.go b/cmd/mapt/cmd/azure/azure.go index 17e67dee7..2ba8e1cc5 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.GetKindCmd()) return c } diff --git a/cmd/mapt/cmd/azure/hosts/constants.go b/cmd/mapt/cmd/azure/hosts/constants.go index ff83f54b4..30575e654 100644 --- a/cmd/mapt/cmd/azure/hosts/constants.go +++ b/cmd/mapt/cmd/azure/hosts/constants.go @@ -1,9 +1,6 @@ package hosts const ( - paramLocation = "location" - paramLocationDesc = "If spot is passed location will be calculated based on spot results. Otherwise localtion will be used to create resources." - defaultLocation = "westeurope" paramVMSize = "vmsize" paramVMSizeDesc = "set specific size for the VM and ignore any CPUs, Memory and Arch parameters set. Type requires to allow nested virtualization" paramUsername = "username" diff --git a/cmd/mapt/cmd/azure/hosts/linux.go b/cmd/mapt/cmd/azure/hosts/linux.go index 4e40a5d4f..15fe757ae 100644 --- a/cmd/mapt/cmd/azure/hosts/linux.go +++ b/cmd/mapt/cmd/azure/hosts/linux.go @@ -1,6 +1,7 @@ package hosts import ( + azureParams "github.com/redhat-developer/mapt/cmd/mapt/cmd/azure/params" "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" maptContext "github.com/redhat-developer/mapt/pkg/manager/context" azureLinux "github.com/redhat-developer/mapt/pkg/provider/azure/action/linux" @@ -61,7 +62,7 @@ func getCreateLinux(ostype data.OSType, defaultOSVersion string) *cobra.Command &azureLinux.LinuxArgs{ ComputeRequest: params.ComputeRequestArgs(), Spot: params.SpotArgs(), - Location: viper.GetString(paramLocation), + Location: viper.GetString(azureParams.Location), Version: viper.GetString(paramLinuxVersion), Arch: viper.GetString(params.LinuxArch), OSType: ostype, @@ -71,7 +72,7 @@ func getCreateLinux(ostype data.OSType, defaultOSVersion string) *cobra.Command flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError) flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc) flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc) - flagSet.StringP(paramLocation, "", defaultLocation, paramLocationDesc) + flagSet.StringP(azureParams.Location, "", azureParams.LocationDefault, azureParams.LocationDesc) flagSet.StringP(params.LinuxArch, "", params.LinuxArchDefault, params.LinuxArchDesc) flagSet.StringP(paramLinuxVersion, "", defaultOSVersion, paramLinuxVersionDesc) flagSet.StringP(paramUsername, "", defaultUsername, paramUsernameDesc) diff --git a/cmd/mapt/cmd/azure/hosts/rhel.go b/cmd/mapt/cmd/azure/hosts/rhel.go index 3705a42b5..d70ef2fb2 100644 --- a/cmd/mapt/cmd/azure/hosts/rhel.go +++ b/cmd/mapt/cmd/azure/hosts/rhel.go @@ -1,6 +1,7 @@ package hosts import ( + azureParams "github.com/redhat-developer/mapt/cmd/mapt/cmd/azure/params" "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" maptContext "github.com/redhat-developer/mapt/pkg/manager/context" azureRHEL "github.com/redhat-developer/mapt/pkg/provider/azure/action/rhel" @@ -52,7 +53,7 @@ func getCreateRHEL() *cobra.Command { &azureRHEL.RhelArgs{ ComputeRequest: params.ComputeRequestArgs(), Spot: params.SpotArgs(), - Location: viper.GetString(paramLocation), + Location: viper.GetString(azureParams.Location), Version: viper.GetString(paramLinuxVersion), Arch: viper.GetString(params.LinuxArch), SubsUsername: viper.GetString(params.SubsUsername), @@ -64,7 +65,7 @@ func getCreateRHEL() *cobra.Command { flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError) flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc) flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc) - flagSet.StringP(paramLocation, "", defaultLocation, paramLocationDesc) + flagSet.StringP(azureParams.Location, "", azureParams.LocationDefault, azureParams.LocationDesc) flagSet.StringP(params.LinuxArch, "", params.LinuxArchDefault, params.LinuxArchDesc) flagSet.StringP(paramLinuxVersion, "", defaultRHELVersion, paramLinuxVersionDesc) flagSet.StringP(paramUsername, "", defaultUsername, paramUsernameDesc) diff --git a/cmd/mapt/cmd/azure/hosts/windows.go b/cmd/mapt/cmd/azure/hosts/windows.go index 858daa9d5..1423f967f 100644 --- a/cmd/mapt/cmd/azure/hosts/windows.go +++ b/cmd/mapt/cmd/azure/hosts/windows.go @@ -1,6 +1,7 @@ package hosts import ( + azureParams "github.com/redhat-developer/mapt/cmd/mapt/cmd/azure/params" "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" maptContext "github.com/redhat-developer/mapt/pkg/manager/context" azureWindows "github.com/redhat-developer/mapt/pkg/provider/azure/action/windows" @@ -66,7 +67,7 @@ func getCreateWindowsDesktop() *cobra.Command { ComputeRequest: params.ComputeRequestArgs(), Spot: params.SpotArgs(), Prefix: viper.GetString(params.ProjectName), - Location: viper.GetString(paramLocation), + Location: viper.GetString(azureParams.Location), Version: viper.GetString(paramWindowsVersion), Feature: viper.GetString(paramFeature), Username: viper.GetString(paramUsername), @@ -77,7 +78,7 @@ func getCreateWindowsDesktop() *cobra.Command { flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError) flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc) flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc) - flagSet.StringP(paramLocation, "", defaultLocation, paramLocationDesc) + flagSet.StringP(azureParams.Location, "", azureParams.LocationDefault, azureParams.LocationDesc) flagSet.StringP(paramWindowsVersion, "", defaultWindowsVersion, paramWindowsVersionDesc) flagSet.StringP(paramFeature, "", defaultFeature, paramFeatureDesc) flagSet.StringP(paramUsername, "", defaultUsername, paramUsernameDesc) diff --git a/cmd/mapt/cmd/azure/params/params.go b/cmd/mapt/cmd/azure/params/params.go new file mode 100644 index 000000000..a62919c98 --- /dev/null +++ b/cmd/mapt/cmd/azure/params/params.go @@ -0,0 +1,7 @@ +package params + +const ( + Location = "location" + LocationDesc = "If spot is passed location will be calculated based on spot results. Otherwise localtion will be used to create resources." + LocationDefault = "westeurope" +) diff --git a/cmd/mapt/cmd/azure/services/kind.go b/cmd/mapt/cmd/azure/services/kind.go new file mode 100644 index 000000000..fd31dfeb8 --- /dev/null +++ b/cmd/mapt/cmd/azure/services/kind.go @@ -0,0 +1,112 @@ +package services + +import ( + "fmt" + + azureParams "github.com/redhat-developer/mapt/cmd/mapt/cmd/azure/params" + "github.com/redhat-developer/mapt/cmd/mapt/cmd/params" + maptContext "github.com/redhat-developer/mapt/pkg/manager/context" + "github.com/redhat-developer/mapt/pkg/provider/azure/action/kind" + kindApi "github.com/redhat-developer/mapt/pkg/targets/service/kind" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +func GetKindCmd() *cobra.Command { + c := &cobra.Command{ + Use: params.KindCmd, + Short: params.KindCmdDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.Flags()); err != nil { + return err + } + return nil + }, + } + flagSet := pflag.NewFlagSet(params.KindCmd, pflag.ExitOnError) + params.AddCommonFlags(flagSet) + c.PersistentFlags().AddFlagSet(flagSet) + c.AddCommand(createKind(), destroyKind()) + return c + +} + +func createKind() *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 + } + + // Parse extra port mappings from JSON string to PortMapping struct + var extraPortMappings []kindApi.PortMapping + extraPortMappingsStr := viper.GetString(params.KindExtraPortMappings) + if extraPortMappingsStr != "" { + var err error + extraPortMappings, err = kindApi.ParseExtraPortMappings(extraPortMappingsStr) + if err != nil { + return fmt.Errorf("failed to parse 'extra-port-mappings' flag: %w", err) + } + } + + if _, err := kind.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), + }, + &kindApi.KindArgs{ + ComputeRequest: params.ComputeRequestArgs(), + Spot: params.SpotArgs(), + HostingPlace: viper.GetString(azureParams.Location), + Version: viper.GetString(params.KindK8SVersion), + Arch: viper.GetString(params.LinuxArch), + ExtraPortMappings: extraPortMappings}); err != nil { + return err + } + return nil + }, + } + flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError) + flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc) + flagSet.StringP(params.KindK8SVersion, "", params.KindK8SVersionDefault, params.KindK8SVersionDesc) + flagSet.StringP(params.LinuxArch, "", params.LinuxArchDefault, params.LinuxArchDesc) + flagSet.StringP(params.KindExtraPortMappings, "", "", params.KindExtraPortMappingsDesc) + flagSet.StringP(azureParams.Location, "", azureParams.LocationDefault, azureParams.LocationDesc) + flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc) + params.AddComputeRequestFlags(flagSet) + params.AddSpotFlags(flagSet) + c.PersistentFlags().AddFlagSet(flagSet) + return c +} + +func destroyKind() *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 + } + return kind.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), + ForceDestroy: viper.IsSet(params.ForceDestroy), + }) + }, + } + flagSet := pflag.NewFlagSet(params.DestroyCmdName, pflag.ExitOnError) + flagSet.Bool(params.Serverless, false, params.ServerlessDesc) + flagSet.Bool(params.ForceDestroy, false, params.ForceDestroyDesc) + c.PersistentFlags().AddFlagSet(flagSet) + return c +} diff --git a/cmd/mapt/cmd/params/params.go b/cmd/mapt/cmd/params/params.go index e3f864a77..03f257629 100644 --- a/cmd/mapt/cmd/params/params.go +++ b/cmd/mapt/cmd/params/params.go @@ -106,6 +106,7 @@ const ( KindCmdDesc = "Manage a Kind cluster. This is not intended for production use" KindK8SVersion = "version" KindK8SVersionDesc = "version for k8s offered through Kind." + KindK8SVersionDefault = "v1.34" KindExtraPortMappings = "extra-port-mappings" KindExtraPortMappingsDesc = "Additional port mappings for the Kind cluster. Value should be a JSON array of objects with containerPort, hostPort, and protocol properties. Example: '[{\"containerPort\": 8080, \"hostPort\": 8080, \"protocol\": \"TCP\"}]'" diff --git a/docs/azure/kind.md b/docs/azure/kind.md new file mode 100644 index 000000000..b5ab33584 --- /dev/null +++ b/docs/azure/kind.md @@ -0,0 +1,3 @@ +## Overview + +TBC diff --git a/pkg/manager/context/context.go b/pkg/manager/context/context.go index ae9bbf276..9f37d3c95 100644 --- a/pkg/manager/context/context.go +++ b/pkg/manager/context/context.go @@ -89,7 +89,9 @@ func Init(ca *ContextArgs, provider Provider) (*Context, error) { if err != nil { return nil, err } - c.targetHostingPlace = *hp + if hp != nil { + c.targetHostingPlace = *hp + } // Manage if err := provider.Init(ca.BackedURL); err != nil { return nil, err diff --git a/pkg/provider/api/config/userdata/userdata.go b/pkg/provider/api/config/userdata/userdata.go new file mode 100644 index 000000000..f6ba82e5c --- /dev/null +++ b/pkg/provider/api/config/userdata/userdata.go @@ -0,0 +1,5 @@ +package userdata + +type CloudConfig interface { + CloudConfig() (*string, error) +} diff --git a/pkg/provider/aws/action/kind/constants.go b/pkg/provider/aws/action/kind/constants.go index a874423e0..8c54fcd6b 100644 --- a/pkg/provider/aws/action/kind/constants.go +++ b/pkg/provider/aws/action/kind/constants.go @@ -3,9 +3,6 @@ package kind import "fmt" var ( - stackName = "stackKind" - awsKindID = "akd" - diskSize int = 200 // Official AMIs from Fedora use aarch64 format for arm64 @@ -17,31 +14,6 @@ var ( amiOwner = "125523088429" amiUserDefault = "fedora" amiProduct = "Linux/UNIX" - - outputHost = "akdHost" - outputUsername = "akdUsername" - outputUserPrivateKey = "akdPrivatekey" - outputKubeconfig = "akdKubeconfig" -) - -// TODO do some code to get this info from kind source code -type kindK8SImages struct { - kindVersion string - KindImage string -} - -var KindK8sVersions map[string]kindK8SImages = map[string]kindK8SImages{ - "v1.33": {"v0.29.0", "kindest/node:v1.33.1@sha256:050072256b9a903bd914c0b2866828150cb229cea0efe5892e2b644d5dd3b34f"}, - "v1.32": {"v0.29.0", "kindest/node:v1.32.5@sha256:e3b2327e3a5ab8c76f5ece68936e4cafaa82edf58486b769727ab0b3b97a5b0d"}, - "v1.31": {"v0.29.0", "kindest/node:v1.31.9@sha256:b94a3a6c06198d17f59cca8c6f486236fa05e2fb359cbd75dabbfc348a10b211"}, - "v1.30": {"v0.29.0", "kindest/node:v1.30.13@sha256:397209b3d947d154f6641f2d0ce8d473732bd91c87d9575ade99049aa33cd648"}, -} - -// TODO check if allow customize this, specially ingress related ports -var ( - portHTTP = 8888 - portHTTPS = 9443 - portAPI = 6443 ) // TODO check latest stable Fedora version diff --git a/pkg/provider/aws/action/kind/kind.go b/pkg/provider/aws/action/kind/kind.go index dbe7bcde4..9fd4ffc0f 100644 --- a/pkg/provider/aws/action/kind/kind.go +++ b/pkg/provider/aws/action/kind/kind.go @@ -7,12 +7,9 @@ import ( "github.com/go-playground/validator/v10" "github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2" "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" - cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request" - spotTypes "github.com/redhat-developer/mapt/pkg/provider/api/spot" "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/modules/allocation" @@ -23,24 +20,13 @@ import ( amiSVC "github.com/redhat-developer/mapt/pkg/provider/aws/services/ec2/ami" "github.com/redhat-developer/mapt/pkg/provider/aws/services/ec2/keypair" securityGroup "github.com/redhat-developer/mapt/pkg/provider/aws/services/ec2/security-group" - kindCloudConfig "github.com/redhat-developer/mapt/pkg/provider/util/cloud-config/kind" "github.com/redhat-developer/mapt/pkg/provider/util/command" - "github.com/redhat-developer/mapt/pkg/provider/util/output" + utilKind "github.com/redhat-developer/mapt/pkg/targets/service/kind" "github.com/redhat-developer/mapt/pkg/util" "github.com/redhat-developer/mapt/pkg/util/logging" resourcesUtil "github.com/redhat-developer/mapt/pkg/util/resources" ) -type KindArgs struct { - Prefix string - ComputeRequest *cr.ComputeRequestArgs - Version string - Arch string - Spot *spotTypes.SpotArgs - Timeout string - ExtraPortMappings []kindCloudConfig.PortMapping -} - type kindRequest struct { mCtx *mc.Context prefix *string @@ -49,7 +35,7 @@ type kindRequest struct { spot bool timeout *string allocationData *allocation.AllocationResult - extraPortMappings []kindCloudConfig.PortMapping + extraPortMappings []utilKind.PortMapping } func (r *kindRequest) validate() error { @@ -61,18 +47,10 @@ func (r *kindRequest) validate() error { return v.Struct(r) } -type KindResultsMetadata struct { - Username string `json:"username"` - PrivateKey string `json:"private_key"` - Host string `json:"host"` - Kubeconfig string `json:"kubeconfig"` - SpotPrice *float64 `json:"spot_price,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 *KindArgs) (kr *KindResultsMetadata, err error) { +func Create(mCtxArgs *mc.ContextArgs, args *utilKind.KindArgs) (kr *utilKind.KindResults, err error) { mCtx, err := mc.Init(mCtxArgs, aws.Provider()) if err != nil { return nil, err @@ -107,7 +85,7 @@ func Destroy(mCtxArgs *mc.ContextArgs) (err error) { if err != nil { return err } - if err := aws.DestroyStack(mCtx, aws.DestroyStackRequest{Stackname: stackName}); err != nil { + if err := aws.DestroyStack(mCtx, aws.DestroyStackRequest{Stackname: utilKind.StackName}); err != nil { return err } if spot.Exist(mCtx) { @@ -116,9 +94,9 @@ func Destroy(mCtxArgs *mc.ContextArgs) (err error) { return nil } -func (r *kindRequest) createHost() (*KindResultsMetadata, error) { +func (r *kindRequest) createHost() (*utilKind.KindResults, error) { cs := manager.Stack{ - StackName: r.mCtx.StackNameByProject(stackName), + StackName: r.mCtx.StackNameByProject(utilKind.StackName), ProjectName: r.mCtx.ProjectName(), BackedURL: r.mCtx.BackedURL(), ProviderCredentials: aws.GetClouProviderCredentials( @@ -133,7 +111,7 @@ func (r *kindRequest) createHost() (*KindResultsMetadata, error) { return nil, fmt.Errorf("stack creation failed: %w", err) } - metadataResults, err := r.manageResults(sr, r.prefix) + metadataResults, err := utilKind.Results(r.mCtx, sr, r.prefix) if err != nil { return nil, fmt.Errorf("failed to manage results: %w", err) } @@ -145,6 +123,8 @@ func (r *kindRequest) deploy(ctx *pulumi.Context) error { if err := r.validate(); err != nil { return err } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKSpotPrice), + pulumi.Float64(*r.allocationData.SpotPrice)) // Get AMI ami, err := amiSVC.GetAMIByName(ctx, amiName(r.arch), @@ -165,7 +145,7 @@ func (r *kindRequest) deploy(ctx *pulumi.Context) error { nw, err := network.Create(ctx, r.mCtx, &network.NetworkArgs{ Prefix: *r.prefix, - ID: awsKindID, + ID: utilKind.KindID, Region: *r.allocationData.Region, AZ: *r.allocationData.AZ, CreateLoadBalancer: r.allocationData.SpotPrice != nil, @@ -176,12 +156,12 @@ func (r *kindRequest) deploy(ctx *pulumi.Context) error { // Create Keypair kpr := keypair.KeyPairRequest{ - Name: resourcesUtil.GetResourceName(*r.prefix, awsKindID, "pk")} + Name: resourcesUtil.GetResourceName(*r.prefix, utilKind.KindID, "pk")} keyResources, err := kpr.Create(ctx, r.mCtx) if err != nil { return err } - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUserPrivateKey), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKPrivateKey), keyResources.PrivateKey.PrivateKeyPem) // Security groups @@ -196,13 +176,13 @@ func (r *kindRequest) deploy(ctx *pulumi.Context) error { return err } // Build LB target groups including both default and extra ports - lbTargetGroups := []int{22, portAPI, portHTTP, portHTTPS} + lbTargetGroups := []int{22, utilKind.PortAPI, utilKind.PortHTTP, utilKind.PortHTTPS} lbTargetGroups = append(lbTargetGroups, extraHostPorts...) cr := compute.ComputeRequest{ MCtx: r.mCtx, Prefix: *r.prefix, - ID: awsKindID, + ID: utilKind.KindID, VPC: nw.Vpc, Subnet: nw.Subnet, AMI: ami, @@ -224,16 +204,16 @@ func (r *kindRequest) deploy(ctx *pulumi.Context) error { return err } - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUsername), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKUsername), pulumi.String(amiUserDefault)) - ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputHost), + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKHost), c.GetHostIP(true)) if len(*r.timeout) > 0 { err := serverless.OneTimeDelayedTask(ctx, r.mCtx, *r.allocationData.Region, *r.prefix, - awsKindID, + utilKind.KindID, fmt.Sprintf("aws %s destroy --project-name %s --backed-url %s --serverless --force-destroy", "kind", r.mCtx.ProjectName(), r.mCtx.BackedURL()), *r.timeout) @@ -246,78 +226,21 @@ func (r *kindRequest) 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, utilKind.OKKubeconfig), pulumi.ToSecret(kubeconfig)) return nil } -// Write exported values in context to files o a selected target folder -func (r *kindRequest) manageResults(stackResult auto.UpResult, prefix *string) (*KindResultsMetadata, error) { - username, err := getResultOutput(outputUsername, stackResult, prefix) - if err != nil { - return nil, err - } - privateKey, err := getResultOutput(outputUserPrivateKey, stackResult, prefix) - if err != nil { - return nil, err - } - host, err := getResultOutput(outputHost, stackResult, prefix) - if err != nil { - return nil, err - } - kubeconfig, err := getResultOutput(outputKubeconfig, stackResult, prefix) - if err != nil { - return nil, err - } - - metadataResults := &KindResultsMetadata{ - Username: username, - PrivateKey: privateKey, - Host: host, - Kubeconfig: kubeconfig, - SpotPrice: r.allocationData.SpotPrice, - } - - hostIPKey := fmt.Sprintf("%s-%s", *prefix, outputHost) - results := map[string]string{ - fmt.Sprintf("%s-%s", *prefix, outputUsername): "username", - fmt.Sprintf("%s-%s", *prefix, outputUserPrivateKey): "id_rsa", - hostIPKey: "host", - fmt.Sprintf("%s-%s", *prefix, outputKubeconfig): "kubeconfig", - } - - if r.mCtx.GetResultsOutputPath() != "" { - if err := output.Write(stackResult, r.mCtx.GetResultsOutputPath(), results); err != nil { - return nil, fmt.Errorf("failed to write results: %w", err) - } - } - - return metadataResults, nil -} - -func getResultOutput(name string, sr auto.UpResult, prefix *string) (string, error) { - key := fmt.Sprintf("%s-%s", *prefix, name) - output, ok := sr.Outputs[key] - if !ok { - return "", fmt.Errorf("output not found: %s", key) - } - value, ok := output.Value.(string) - if !ok { - return "", fmt.Errorf("output for %s is not a string", key) - } - return value, nil -} - // security group for Openshift func securityGroups(ctx *pulumi.Context, mCtx *mc.Context, prefix *string, vpc *ec2.Vpc, extraHostPorts []int) (pulumi.StringArray, error) { // Build ingress rules including both default and extra ports ingressRules := []securityGroup.IngressRules{ securityGroup.SSH_TCP, - {Description: "HTTPS", FromPort: portHTTPS, ToPort: portHTTPS, Protocol: "tcp"}, - {Description: "HTTP", FromPort: portHTTP, ToPort: portHTTP, Protocol: "tcp"}, - {Description: "API", FromPort: portAPI, ToPort: portAPI, Protocol: "tcp"}, + {Description: "HTTPS", FromPort: utilKind.PortHTTPS, ToPort: utilKind.PortHTTPS, Protocol: "tcp"}, + {Description: "HTTP", FromPort: utilKind.PortHTTP, ToPort: utilKind.PortHTTP, Protocol: "tcp"}, + {Description: "API", FromPort: utilKind.PortAPI, ToPort: utilKind.PortAPI, Protocol: "tcp"}, } // Add extra ports to ingress rules @@ -332,9 +255,9 @@ func securityGroups(ctx *pulumi.Context, mCtx *mc.Context, prefix *string, // Create SG with ingress rules sg, err := securityGroup.SGRequest{ - Name: resourcesUtil.GetResourceName(*prefix, awsKindID, "sg"), + Name: resourcesUtil.GetResourceName(*prefix, utilKind.KindID, "sg"), VPC: vpc, - Description: fmt.Sprintf("sg for %s", awsKindID), + Description: fmt.Sprintf("sg for %s", utilKind.KindID), IngressRules: ingressRules, }.Create(ctx, mCtx) if err != nil { @@ -348,22 +271,21 @@ func securityGroups(ctx *pulumi.Context, mCtx *mc.Context, prefix *string, return pulumi.StringArray(sgs[:]), nil } -func userData(arch, k8sVersion *string, parsedPortMappings []kindCloudConfig.PortMapping, lbEIP *pulumi.StringOutput) (pulumi.StringPtrInput, error) { +func userData(arch, k8sVersion *string, parsedPortMappings []utilKind.PortMapping, lbEIP *pulumi.StringOutput) (pulumi.StringPtrInput, error) { ccB64 := lbEIP.ApplyT( func(publicIP string) (string, error) { - ccB64, err := kindCloudConfig.CloudConfig( - &kindCloudConfig.DataValues{ - Arch: util.If(*arch == "x86_64", - kindCloudConfig.X86_64, - kindCloudConfig.Arm64), - KindVersion: KindK8sVersions[*k8sVersion].kindVersion, - KindImage: KindK8sVersions[*k8sVersion].KindImage, - Username: amiUserDefault, - PublicIP: publicIP, - ExtraPortMappings: parsedPortMappings}) + cc := &utilKind.CloudConfigArgs{ + Arch: util.If(*arch == "x86_64", + utilKind.X86_64, + utilKind.Arm64), + KindVersion: utilKind.KindK8sVersions[*k8sVersion].KindVersion, + KindImage: utilKind.KindK8sVersions[*k8sVersion].KindImage, + Username: amiUserDefault, + PublicIP: publicIP, + ExtraPortMappings: parsedPortMappings} + ccB64, err := cc.CloudConfig() return *ccB64, err }).(pulumi.StringOutput) - return ccB64, nil } @@ -380,7 +302,7 @@ func kubeconfig(ctx *pulumi.Context, kindReadyCmd, err := c.RunCommand(ctx, command.CommandCloudInitWait, compute.LoggingCmdStd, - fmt.Sprintf("%s-kind-readiness", *prefix), awsKindID, + fmt.Sprintf("%s-kind-readiness", *prefix), utilKind.KindID, mk, amiUserDefault, nil, c.Dependencies) if err != nil { return pulumi.StringOutput{}, err @@ -390,7 +312,7 @@ func kubeconfig(ctx *pulumi.Context, getKC, err := c.RunCommand(ctx, getKCCmd, compute.NoLoggingCmdStd, - fmt.Sprintf("%s-kubeconfig", *prefix), awsKindID, mk, amiUserDefault, + fmt.Sprintf("%s-kubeconfig", *prefix), utilKind.KindID, mk, amiUserDefault, nil, []pulumi.Resource{kindReadyCmd}) if err != nil { return pulumi.StringOutput{}, err diff --git a/pkg/provider/aws/action/rhel/rhel.go b/pkg/provider/aws/action/rhel/rhel.go index 90e70d6d8..e28217fd6 100644 --- a/pkg/provider/aws/action/rhel/rhel.go +++ b/pkg/provider/aws/action/rhel/rhel.go @@ -23,9 +23,9 @@ import ( amiSVC "github.com/redhat-developer/mapt/pkg/provider/aws/services/ec2/ami" "github.com/redhat-developer/mapt/pkg/provider/aws/services/ec2/keypair" securityGroup "github.com/redhat-developer/mapt/pkg/provider/aws/services/ec2/security-group" - cloudConfigRHEL "github.com/redhat-developer/mapt/pkg/provider/util/cloud-config/rhel" "github.com/redhat-developer/mapt/pkg/provider/util/command" "github.com/redhat-developer/mapt/pkg/provider/util/output" + rhelApi "github.com/redhat-developer/mapt/pkg/targets/host/rhel" "github.com/redhat-developer/mapt/pkg/util" "github.com/redhat-developer/mapt/pkg/util/logging" resourcesUtil "github.com/redhat-developer/mapt/pkg/util/resources" @@ -217,12 +217,12 @@ func (r *rhelRequest) deploy(ctx *pulumi.Context) error { return err } // Compute - rhelCloudConfig := &cloudConfigRHEL.RequestArgs{ + rhelCloudConfig := &rhelApi.CloudConfigArgs{ SNCProfile: *r.profileSNC, SubsUsername: *r.subsUsername, SubsPassword: *r.subsUserpass, Username: amiUserDefault} - userDataB64, err := rhelCloudConfig.GetAsUserdata() + userDataB64, err := rhelCloudConfig.CloudConfig() if err != nil { return err } @@ -233,7 +233,7 @@ func (r *rhelRequest) deploy(ctx *pulumi.Context) error { VPC: nw.Vpc, Subnet: nw.Subnet, AMI: ami, - UserDataAsBase64: pulumi.String(userDataB64), + UserDataAsBase64: pulumi.String(*userDataB64), KeyResources: keyResources, SecurityGroups: securityGroups, InstaceTypes: r.allocationData.InstanceTypes, diff --git a/pkg/provider/azure/action/kind/constants.go b/pkg/provider/azure/action/kind/constants.go new file mode 100644 index 000000000..81c340482 --- /dev/null +++ b/pkg/provider/azure/action/kind/constants.go @@ -0,0 +1,10 @@ +package kind + +const ( + stackAzureKind = "stackAzureKind" + azureKindID = "akd" +) + +var ( + amiUserDefault = "fedora" +) diff --git a/pkg/provider/azure/action/kind/kind.go b/pkg/provider/azure/action/kind/kind.go new file mode 100644 index 000000000..d4b0e966d --- /dev/null +++ b/pkg/provider/azure/action/kind/kind.go @@ -0,0 +1,316 @@ +package kind + +import ( + "fmt" + "regexp" + + "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/pulumi" + "github.com/redhat-developer/mapt/pkg/manager" + mc "github.com/redhat-developer/mapt/pkg/manager/context" + "github.com/redhat-developer/mapt/pkg/provider/aws/modules/ec2/compute" + "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" + utilKind "github.com/redhat-developer/mapt/pkg/targets/service/kind" + "github.com/redhat-developer/mapt/pkg/util" + resourcesUtil "github.com/redhat-developer/mapt/pkg/util/resources" +) + +type kindRequest struct { + mCtx *mc.Context + prefix *string + version *string + arch *string + spot bool + allocationData *allocation.AllocationResult + extraPortMappings []utilKind.PortMapping +} + +func (r *kindRequest) 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 *utilKind.KindArgs) (*utilKind.KindResults, error) { + // Create mapt Context + mCtx, err := mc.Init(mCtxArgs, azure.Provider()) + if err != nil { + return nil, err + } + prefix := util.If(len(args.Prefix) > 0, args.Prefix, "main") + r := &kindRequest{ + mCtx: mCtx, + prefix: &prefix, + version: &args.Version, + arch: &args.Arch, + extraPortMappings: args.ExtraPortMappings, + } + if args.Spot != nil { + r.spot = args.Spot.Spot + } + ir, err := data.GetImageRef(data.Fedora, *r.arch, data.FedoraDefaultVersion) + if err != nil { + return nil, err + } + r.allocationData, err = allocation.Allocation(mCtx, + &allocation.AllocationArgs{ + ComputeRequest: args.ComputeRequest, + OSType: "linux", + ImageRef: ir, + Location: &args.HostingPlace, + Spot: args.Spot}) + if err != nil { + return nil, err + } + cs := manager.Stack{ + StackName: mCtx.StackNameByProject(stackAzureKind), + ProjectName: mCtx.ProjectName(), + BackedURL: mCtx.BackedURL(), + ProviderCredentials: azure.DefaultCredentials, + DeployFunc: r.deployer, + } + sr, err := manager.UpStack(r.mCtx, cs) + if err != nil { + return nil, fmt.Errorf("stack creation failed: %w", err) + } + + metadataResults, err := utilKind.Results(r.mCtx, sr, r.prefix) + if err != nil { + return nil, fmt.Errorf("failed to manage results: %w", err) + } + + return metadataResults, nil +} + +func Destroy(mCtxArgs *mc.ContextArgs) (err error) { + // Create mapt Context + mCtx, err := mc.Init(mCtxArgs, azure.Provider()) + if err != nil { + return err + } + // destroy + return azure.Destroy(mCtx, stackAzureKind) +} + +// Main function to deploy all requried resources to azure +func (r *kindRequest) deployer(ctx *pulumi.Context) error { + if err := r.validate(); err != nil { + return err + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKSpotPrice), + pulumi.Float64(*r.allocationData.Price)) + // Get location for creating the Resource Group + rgLocation := azure.GetSuitableLocationForResourceGroup(*r.allocationData.Location) + rg, err := resources.NewResourceGroup(ctx, + resourcesUtil.GetResourceName(*r.prefix, azureKindID, "rg"), + &resources.ResourceGroupArgs{ + Location: pulumi.String(rgLocation), + ResourceGroupName: pulumi.String(r.mCtx.RunID()), + Tags: r.mCtx.ResourceTags(), + }) + if err != nil { + return err + } + // Networking + // Extract hostPort values for LB target groups and security group rules + extraHostPorts := make([]int, 0, len(r.extraPortMappings)) + for _, pm := range r.extraPortMappings { + extraHostPorts = append(extraHostPorts, pm.HostPort) + } + sg, err := securityGroups(ctx, r.mCtx, r.prefix, r.allocationData.Location, extraHostPorts, rg) + if err != nil { + return err + } + n, err := network.Create(ctx, r.mCtx, + &network.NetworkArgs{ + Prefix: *r.prefix, + ComponentID: azureKindID, + ResourceGroup: rg, + Location: r.allocationData.Location, + SecurityGroup: sg, + }) + if err != nil { + return err + } + // Userdata + udB64, err := userData(r.arch, r.version, r.extraPortMappings, n.PublicIP.IpAddress.Elem()) + if err != nil { + return err + } + + // Virutal machine + privateKey, err := tls.NewPrivateKey( + ctx, + resourcesUtil.GetResourceName(*r.prefix, azureKindID, "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, utilKind.OKPrivateKey), + privateKey.PrivateKeyPem) + vm, err := virtualmachine.Create(ctx, r.mCtx, + &virtualmachine.VirtualMachineArgs{ + Prefix: *r.prefix, + ComponentID: azureKindID, + ResourceGroup: rg, + NetworkInteface: n.NetworkInterface, + // Check this + VMSize: r.allocationData.ComputeSizes[0], + Publisher: r.allocationData.ImageRef.Publisher, + Offer: r.allocationData.ImageRef.Offer, + Sku: r.allocationData.ImageRef.Sku, + ImageID: r.allocationData.ImageRef.ID, + PrivateKey: privateKey, + SpotPrice: r.allocationData.Price, + UserDataAsBase64: udB64, + Location: *r.allocationData.Location, + AdminUsername: amiUserDefault, + }) + if err != nil { + return err + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKUsername), + pulumi.String(amiUserDefault)) + + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKHost), + n.PublicIP.IpAddress.Elem()) + + kubeconfig, err := kubeconfig(ctx, r.prefix, n.PublicIP.IpAddress.Elem(), privateKey, []pulumi.Resource{vm}) + if err != nil { + return err + } + ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, utilKind.OKKubeconfig), + pulumi.ToSecret(kubeconfig)) + + return nil +} + +// security group for mac machine with ingress rules for ssh and vnc +func securityGroups(ctx *pulumi.Context, mCtx *mc.Context, + prefix, location *string, extraHostPorts []int, + rg *resources.ResourceGroup) (securityGroup.SecurityGroup, error) { + ingressRules := []securityGroup.IngressRules{ + securityGroup.SSH_TCP, + {Description: "HTTPS", FromPort: utilKind.PortHTTPS, ToPort: utilKind.PortHTTPS, Protocol: "tcp"}, + {Description: "HTTP", FromPort: utilKind.PortHTTP, ToPort: utilKind.PortHTTP, Protocol: "tcp"}, + {Description: "API", FromPort: utilKind.PortAPI, ToPort: utilKind.PortAPI, Protocol: "tcp"}, + } + + // Add extra ports to ingress rules + for _, port := range extraHostPorts { + ingressRules = append(ingressRules, securityGroup.IngressRules{ + Description: fmt.Sprintf("Extra Port %d", port), + FromPort: port, + ToPort: port, + Protocol: "tcp", + }) + } + + // Create SG with ingress rules + return securityGroup.Create( + ctx, + mCtx, + &securityGroup.SecurityGroupArgs{ + Name: resourcesUtil.GetResourceName(*prefix, azureKindID, "sg"), + RG: rg, + Location: location, + IngressRules: ingressRules, + }) +} + +func userData(arch, k8sVersion *string, parsedPortMappings []utilKind.PortMapping, ip pulumi.StringOutput) (pulumi.StringPtrInput, error) { + ccB64 := ip.ApplyT( + func(publicIP string) (string, error) { + cc := &utilKind.CloudConfigArgs{ + Arch: util.If(*arch == "x86_64", + utilKind.X86_64, + utilKind.Arm64), + KindVersion: utilKind.KindK8sVersions[*k8sVersion].KindVersion, + KindImage: utilKind.KindK8sVersions[*k8sVersion].KindImage, + Username: amiUserDefault, + PublicIP: publicIP, + ExtraPortMappings: parsedPortMappings} + ccB64, err := cc.CloudConfig() + return *ccB64, err + }).(pulumi.StringOutput) + return ccB64, nil +} + +func kubeconfig(ctx *pulumi.Context, + prefix *string, ip pulumi.StringOutput, mk *tls.PrivateKey, + dependecies []pulumi.Resource, +) (pulumi.StringOutput, error) { + // Once the cluster setup is comleted we + // get the kubeconfig file from the host running the cluster + // then we replace the internal access with the public IP + // the resulting kubeconfig file can be used to access the cluster + + // Check cluster is ready + kindReadyCmd, err := runCommand(ctx, + command.CommandCloudInitWait, + compute.LoggingCmdStd, + fmt.Sprintf("%s-kind-readiness", *prefix), utilKind.KindID, + mk, amiUserDefault, ip, dependecies) + if err != nil { + return pulumi.StringOutput{}, err + } + // Get content for /opt/kubeconfig + getKCCmd := ("cat /home/fedora/kubeconfig") + getKC, err := runCommand(ctx, + getKCCmd, + compute.NoLoggingCmdStd, + fmt.Sprintf("%s-kubeconfig", *prefix), utilKind.KindID, mk, amiUserDefault, + ip, []pulumi.Resource{kindReadyCmd}) + if err != nil { + return pulumi.StringOutput{}, err + } + kubeconfig := pulumi.All(getKC.Stdout, ip).ApplyT( + func(args []interface{}) string { + re := regexp.MustCompile(`https://[^:]+:\d+`) + return re.ReplaceAllString( + args[0].(string), + fmt.Sprintf("https://%s:6443", args[1].(string))) + }).(pulumi.StringOutput) + return kubeconfig, nil +} + +func runCommand(ctx *pulumi.Context, + cmd string, + loggingCmdStd bool, + prefix, id string, + mk *tls.PrivateKey, username string, + ip pulumi.StringOutput, + dependecies []pulumi.Resource) (*remote.Command, error) { + return remote.NewCommand(ctx, + resourcesUtil.GetResourceName(prefix, id, "cmd"), + &remote.CommandArgs{ + Connection: remote.ConnectionArgs{ + Host: ip, + PrivateKey: mk.PrivateKeyOpenssh, + User: pulumi.String(amiUserDefault), + DialErrorLimit: pulumi.Int(-1), + }, + Create: pulumi.String(cmd), + Update: pulumi.String(cmd), + }, + pulumi.Timeouts( + &pulumi.CustomTimeouts{ + Create: "10m", + Update: "10m"}), + pulumi.DependsOn(dependecies)) +} diff --git a/pkg/provider/azure/action/linux/linux.go b/pkg/provider/azure/action/linux/linux.go index c6bd824d9..0590039f6 100644 --- a/pkg/provider/azure/action/linux/linux.go +++ b/pkg/provider/azure/action/linux/linux.go @@ -13,6 +13,7 @@ import ( mc "github.com/redhat-developer/mapt/pkg/manager/context" infra "github.com/redhat-developer/mapt/pkg/provider" cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request" + userDataApi "github.com/redhat-developer/mapt/pkg/provider/api/config/userdata" spotTypes "github.com/redhat-developer/mapt/pkg/provider/api/spot" "github.com/redhat-developer/mapt/pkg/provider/azure" "github.com/redhat-developer/mapt/pkg/provider/azure/data" @@ -38,28 +39,28 @@ const ( ) type LinuxArgs struct { - Prefix string - Location string - Arch string - ComputeRequest *cr.ComputeRequestArgs - OSType data.OSType - Version string - Username string - Spot *spotTypes.SpotArgs - GetUserdata func() (string, error) - ReadinessCommand string + Prefix string + Location string + Arch string + ComputeRequest *cr.ComputeRequestArgs + OSType data.OSType + Version string + Username string + Spot *spotTypes.SpotArgs + CloudConfigAsUserData userDataApi.CloudConfig + ReadinessCommand string } type linuxRequest struct { - mCtx *mc.Context `validate:"required"` - prefix *string - arch *string - osType *data.OSType - version *string - allocationData *allocation.AllocationResult - username *string - getUserdata func() (string, error) - readinessCommand *string + mCtx *mc.Context `validate:"required"` + prefix *string + arch *string + osType *data.OSType + version *string + allocationData *allocation.AllocationResult + username *string + cloudConfigAsUserData userDataApi.CloudConfig + readinessCommand *string } func (r *linuxRequest) validate() error { @@ -79,14 +80,14 @@ func Create(mCtxArgs *mc.ContextArgs, args *LinuxArgs) (err error) { } prefix := util.If(len(args.Prefix) > 0, args.Prefix, "main") r := &linuxRequest{ - mCtx: mCtx, - prefix: &prefix, - arch: &args.Arch, - osType: &args.OSType, - version: &args.Version, - username: &args.Username, - getUserdata: args.GetUserdata, - readinessCommand: &args.ReadinessCommand, + mCtx: mCtx, + prefix: &prefix, + arch: &args.Arch, + osType: &args.OSType, + version: &args.Version, + username: &args.Username, + cloudConfigAsUserData: args.CloudConfigAsUserData, + readinessCommand: &args.ReadinessCommand, } ir, err := data.GetImageRef(*r.osType, *r.arch, *r.version) if err != nil { @@ -109,7 +110,10 @@ func Create(mCtxArgs *mc.ContextArgs, args *LinuxArgs) (err error) { ProviderCredentials: azure.DefaultCredentials, DeployFunc: r.deployer, } - sr, _ := manager.UpStack(mCtx, cs) + sr, err := manager.UpStack(mCtx, cs) + if err != nil { + return err + } return r.manageResults(sr) } @@ -170,10 +174,10 @@ func (r *linuxRequest) deployer(ctx *pulumi.Context) error { } ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputUserPrivateKey), privateKey.PrivateKeyPem) // Image refence info - var userDataB64 string - if r.getUserdata != nil { + var userDataB64 *string + if r.cloudConfigAsUserData != nil { var err error - userDataB64, err = r.getUserdata() + userDataB64, err = r.cloudConfigAsUserData.CloudConfig() if err != nil { return fmt.Errorf("error creating RHEL Server on Azure: %v", err) } @@ -186,16 +190,16 @@ func (r *linuxRequest) deployer(ctx *pulumi.Context) error { ResourceGroup: rg, NetworkInteface: n.NetworkInterface, // Check this - VMSize: r.allocationData.ComputeSizes[0], - Publisher: r.allocationData.ImageRef.Publisher, - Offer: r.allocationData.ImageRef.Offer, - Sku: r.allocationData.ImageRef.Sku, - ImageID: r.allocationData.ImageRef.ID, - AdminUsername: *r.username, - PrivateKey: privateKey, - SpotPrice: r.allocationData.Price, - Userdata: userDataB64, - Location: *r.allocationData.Location, + VMSize: r.allocationData.ComputeSizes[0], + Publisher: r.allocationData.ImageRef.Publisher, + Offer: r.allocationData.ImageRef.Offer, + Sku: r.allocationData.ImageRef.Sku, + ImageID: r.allocationData.ImageRef.ID, + AdminUsername: *r.username, + PrivateKey: privateKey, + SpotPrice: r.allocationData.Price, + UserDataAsBase64: pulumi.String(*userDataB64), + Location: *r.allocationData.Location, }) if err != nil { return err diff --git a/pkg/provider/azure/action/rhel/rhel.go b/pkg/provider/azure/action/rhel/rhel.go index 0efd75cdd..80608d2be 100644 --- a/pkg/provider/azure/action/rhel/rhel.go +++ b/pkg/provider/azure/action/rhel/rhel.go @@ -6,8 +6,8 @@ import ( spotTypes "github.com/redhat-developer/mapt/pkg/provider/api/spot" azureLinux "github.com/redhat-developer/mapt/pkg/provider/azure/action/linux" "github.com/redhat-developer/mapt/pkg/provider/azure/data" - cloudConfigRHEL "github.com/redhat-developer/mapt/pkg/provider/util/cloud-config/rhel" "github.com/redhat-developer/mapt/pkg/provider/util/command" + rhelApi "github.com/redhat-developer/mapt/pkg/targets/host/rhel" "github.com/redhat-developer/mapt/pkg/util/logging" ) @@ -26,7 +26,7 @@ type RhelArgs struct { func Create(ctx *maptContext.ContextArgs, r *RhelArgs) (err error) { logging.Debug("Creating RHEL Server") - rhelCloudConfig := &cloudConfigRHEL.RequestArgs{ + rhelCloudConfig := &rhelApi.CloudConfigArgs{ SNCProfile: r.ProfileSNC, SubsUsername: r.SubsUsername, SubsPassword: r.SubsUserpass, @@ -41,9 +41,9 @@ func Create(ctx *maptContext.ContextArgs, r *RhelArgs) (err error) { Arch: r.Arch, OSType: data.RHEL, Username: r.Username, - GetUserdata: rhelCloudConfig.GetAsUserdata, // As RHEL now is set with cloud init this is the ReadinessCommand to check - ReadinessCommand: command.CommandCloudInitWait} + CloudConfigAsUserData: rhelCloudConfig, + ReadinessCommand: command.CommandCloudInitWait} return azureLinux.Create(ctx, azureLinuxRequest) } diff --git a/pkg/provider/azure/data/imageref.go b/pkg/provider/azure/data/imageref.go index f32c7076a..90516cdd4 100644 --- a/pkg/provider/azure/data/imageref.go +++ b/pkg/provider/azure/data/imageref.go @@ -13,6 +13,10 @@ const ( Fedora ) +var ( + FedoraDefaultVersion string = "42" +) + const fedoraImageGalleryBase = "/CommunityGalleries/Fedora-5e266ba4-2250-406d-adad-5d73860d958f/Images/" type ImageReference struct { diff --git a/pkg/provider/azure/modules/virtual-machine/virtual-machine.go b/pkg/provider/azure/modules/virtual-machine/virtual-machine.go index dc46a39af..cd1124f0b 100644 --- a/pkg/provider/azure/modules/virtual-machine/virtual-machine.go +++ b/pkg/provider/azure/modules/virtual-machine/virtual-machine.go @@ -37,9 +37,9 @@ type VirtualMachineArgs struct { // Linux required PrivateKey *tls.PrivateKey AdminPasswd *random.RandomPassword - // Linux optional - Userdata string - Location string + // Only required if we need to set userdata + UserDataAsBase64 pulumi.StringPtrInput + Location string } type VirtualMachine = *compute.VirtualMachine @@ -67,6 +67,7 @@ func Create(ctx *pulumi.Context, mCtx *mc.Context, args *VirtualMachineArgs) (Vi VmName: pulumi.String(mCtx.RunID()), Location: pulumi.String(args.Location), ResourceGroupName: args.ResourceGroup.Name, + UserData: args.UserDataAsBase64, NetworkProfile: compute.NetworkProfileArgs{ NetworkInterfaces: compute.NetworkInterfaceReferenceArray{ compute.NetworkInterfaceReferenceArgs{ @@ -105,9 +106,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"), diff --git a/pkg/provider/util/cloud-config/rhel/cloud-config-base b/pkg/targets/host/rhel/cloud-config-base similarity index 100% rename from pkg/provider/util/cloud-config/rhel/cloud-config-base rename to pkg/targets/host/rhel/cloud-config-base diff --git a/pkg/provider/util/cloud-config/rhel/cloud-config-snc b/pkg/targets/host/rhel/cloud-config-snc similarity index 100% rename from pkg/provider/util/cloud-config/rhel/cloud-config-snc rename to pkg/targets/host/rhel/cloud-config-snc diff --git a/pkg/provider/util/cloud-config/rhel/rhel.go b/pkg/targets/host/rhel/cloud-config.go similarity index 82% rename from pkg/provider/util/cloud-config/rhel/rhel.go rename to pkg/targets/host/rhel/cloud-config.go index 373b5939a..f4003dfc7 100644 --- a/pkg/provider/util/cloud-config/rhel/rhel.go +++ b/pkg/targets/host/rhel/cloud-config.go @@ -10,7 +10,7 @@ import ( "github.com/redhat-developer/mapt/pkg/util/file" ) -type RequestArgs struct { +type CloudConfigArgs struct { SNCProfile bool SubsUsername, SubsPassword string Username string @@ -30,18 +30,18 @@ var CloudConfigBase []byte //go:embed cloud-config-snc var CloudConfigSNC []byte -func (r *RequestArgs) GetAsUserdata() (string, error) { +func (r *CloudConfigArgs) CloudConfig() (*string, error) { templateConfig := string(CloudConfigBase[:]) if r.SNCProfile { templateConfig = string(CloudConfigSNC[:]) } cirrusSnippet, err := integrations.GetIntegrationSnippetAsCloudInitWritableFile(cirrus.GetRunnerArgs(), r.Username) if err != nil { - return "", err + return nil, err } ghActionsRunnerSnippet, err := integrations.GetIntegrationSnippetAsCloudInitWritableFile(github.GetRunnerArgs(), r.Username) if err != nil { - return "", err + return nil, err } userdata, err := file.Template( userDataValues{ @@ -51,6 +51,9 @@ func (r *RequestArgs) GetAsUserdata() (string, error) { *ghActionsRunnerSnippet, *cirrusSnippet}, templateConfig) - // return pulumi.String(base64.StdEncoding.EncodeToString([]byte(userdata))), err - return base64.StdEncoding.EncodeToString([]byte(userdata)), err + if err != nil { + return nil, err + } + ccB64 := base64.StdEncoding.EncodeToString([]byte(userdata)) + return &ccB64, nil } diff --git a/pkg/targets/service/kind/api.go b/pkg/targets/service/kind/api.go new file mode 100644 index 000000000..4b2833091 --- /dev/null +++ b/pkg/targets/service/kind/api.go @@ -0,0 +1,51 @@ +package kind + +import ( + cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request" + spotTypes "github.com/redhat-developer/mapt/pkg/provider/api/spot" +) + +// TODO do some code to get this info from kind source code +type KindK8SImages struct { + KindVersion string + KindImage string +} + +var KindK8sVersions map[string]KindK8SImages = map[string]KindK8SImages{ + "v1.34": {"v0.30.0", "kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a"}, + "v1.33": {"v0.30.0", "kindest/node:v1.33.4@sha256:25a6018e48dfcaee478f4a59af81157a437f15e6e140bf103f85a2e7cd0cbbf2"}, + "v1.32": {"v0.30.0", "kindest/node:v1.32.8@sha256:abd489f042d2b644e2d033f5c2d900bc707798d075e8186cb65e3f1367a9d5a1"}, + "v1.31": {"v0.30.0", "kindest/node:v1.31.12@sha256:0f5cc49c5e73c0c2bb6e2df56e7df189240d83cf94edfa30946482eb08ec57d2"}, + "v1.30": {"v0.29.0", "kindest/node:v1.30.13@sha256:397209b3d947d154f6641f2d0ce8d473732bd91c87d9575ade99049aa33cd648"}, +} + +const ( + StackName = "stackKind" + KindID = "knd" +) + +// TODO check if allow customize this, specially ingress related ports +var ( + PortHTTP = 8888 + PortHTTPS = 9443 + PortAPI = 6443 +) + +type KindArgs struct { + Prefix string + ComputeRequest *cr.ComputeRequestArgs + Version string + Arch string + HostingPlace string + Spot *spotTypes.SpotArgs + Timeout string + ExtraPortMappings []PortMapping +} + +type KindResults struct { + Username *string `json:"username"` + PrivateKey *string `json:"private_key"` + Host *string `json:"host"` + Kubeconfig *string `json:"kubeconfig"` + SpotPrice *float64 `json:"spot_price,omitempty"` +} diff --git a/pkg/provider/util/cloud-config/kind/cloud-config b/pkg/targets/service/kind/cloud-config similarity index 100% rename from pkg/provider/util/cloud-config/kind/cloud-config rename to pkg/targets/service/kind/cloud-config diff --git a/pkg/provider/util/cloud-config/kind/kind.go b/pkg/targets/service/kind/cloud-config.go similarity index 90% rename from pkg/provider/util/cloud-config/kind/kind.go rename to pkg/targets/service/kind/cloud-config.go index d61814aa2..e56e3400e 100644 --- a/pkg/provider/util/cloud-config/kind/kind.go +++ b/pkg/targets/service/kind/cloud-config.go @@ -23,7 +23,7 @@ type PortMapping struct { Protocol string `json:"protocol"` } -type DataValues struct { +type CloudConfigArgs struct { Arch kindArch KindVersion string KindImage string @@ -35,11 +35,14 @@ type DataValues struct { //go:embed cloud-config var CloudConfigTemplate []byte -func CloudConfig(data *DataValues) (*string, error) { +func (args *CloudConfigArgs) CloudConfig() (*string, error) { templateConfig := string(CloudConfigTemplate[:]) - userdata, err := file.Template(data, templateConfig) + userdata, err := file.Template(args, templateConfig) + if err != nil { + return nil, err + } ccB64 := base64.StdEncoding.EncodeToString([]byte(userdata)) - return &ccB64, err + return &ccB64, nil } func ParseExtraPortMappings(extraPortMappingsJSON string) ([]PortMapping, error) { diff --git a/pkg/provider/util/cloud-config/kind/kind_test.go b/pkg/targets/service/kind/cloud-config_test.go similarity index 100% rename from pkg/provider/util/cloud-config/kind/kind_test.go rename to pkg/targets/service/kind/cloud-config_test.go diff --git a/pkg/targets/service/kind/util.go b/pkg/targets/service/kind/util.go new file mode 100644 index 000000000..007fe0ca8 --- /dev/null +++ b/pkg/targets/service/kind/util.go @@ -0,0 +1,71 @@ +package kind + +import ( + "fmt" + + "github.com/pulumi/pulumi/sdk/v3/go/auto" + mc "github.com/redhat-developer/mapt/pkg/manager/context" + "github.com/redhat-developer/mapt/pkg/provider/util/output" +) + +const ( + // Outputs + OKHost = "kndHost" + OKUsername = "kndUsername" + OKPrivateKey = "kndPrivatekey" + OKKubeconfig = "kndKubeconfig" + OKSpotPrice = "kndSpotPrice" +) + +func Results(mCtx *mc.Context, stackResult auto.UpResult, prefix *string) (*KindResults, error) { + username, err := get[string](OKUsername, stackResult, prefix) + if err != nil { + return nil, err + } + privateKey, err := get[string](OKPrivateKey, stackResult, prefix) + if err != nil { + return nil, err + } + host, err := get[string](OKHost, stackResult, prefix) + if err != nil { + return nil, err + } + kubeconfig, err := get[string](OKKubeconfig, stackResult, prefix) + if err != nil { + return nil, err + } + spotPrice, err := get[float64](OKSpotPrice, stackResult, prefix) + if err != nil { + return nil, err + } + if mCtx.GetResultsOutputPath() != "" { + if err := output.Write(stackResult, mCtx.GetResultsOutputPath(), map[string]string{ + fmt.Sprintf("%s-%s", *prefix, OKUsername): "username", + fmt.Sprintf("%s-%s", *prefix, OKPrivateKey): "id_rsa", + fmt.Sprintf("%s-%s", *prefix, OKHost): "host", + fmt.Sprintf("%s-%s", *prefix, OKKubeconfig): "kubeconfig", + }); err != nil { + return nil, fmt.Errorf("failed to write results: %w", err) + } + } + return &KindResults{ + Username: username, + PrivateKey: privateKey, + Host: host, + Kubeconfig: kubeconfig, + SpotPrice: spotPrice, + }, nil +} + +func get[T any](name string, sr auto.UpResult, prefix *string) (*T, error) { + key := fmt.Sprintf("%s-%s", *prefix, name) + output, ok := sr.Outputs[key] + if !ok { + return nil, fmt.Errorf("output not found: %s", key) + } + value, ok := output.Value.(T) + if !ok { + return nil, fmt.Errorf("output for %s is not the right type", key) + } + return &value, nil +}