Skip to content

Commit

Permalink
initial commit to support custom amis as a new action
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianriobo committed Apr 18, 2024
1 parent dff250b commit 9f09ad4
Show file tree
Hide file tree
Showing 5 changed files with 414 additions and 2 deletions.
3 changes: 2 additions & 1 deletion cmd/cmd/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func GetCmd() *cobra.Command {
hosts.GetMacCmd(),
hosts.GetWindowsCmd(),
hosts.GetRHELCmd(),
hosts.GetFedoraCmd())
hosts.GetFedoraCmd(),
hosts.GetCustomCmd())
return c
}
112 changes: 112 additions & 0 deletions cmd/cmd/aws/hosts/custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package hosts

import (
params "github.com/adrianriobo/qenvs/cmd/cmd/constants"
qenvsContext "github.com/adrianriobo/qenvs/pkg/manager/context"
"github.com/adrianriobo/qenvs/pkg/provider/aws/action/custom"
"github.com/adrianriobo/qenvs/pkg/provider/aws/action/fedora"
"github.com/adrianriobo/qenvs/pkg/util/logging"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

const (
cmdCustom = "fedora"
cmdCustomDesc = "manage "

amiID string = "ami"
amiIDDesc string = "ID for the custom ami"
instanceType string = "instance-type"
instanceTypeDesc string = "type of instance"
productDescription string = "product-description"
productDescriptionDesc string = "Product description for the custom AMI: (Linux/UNIX, Windows or Red Hat Enterprise Linux)"
productDescriptionDefault string = "Linux/UNIX"
)

func GetCustomCmd() *cobra.Command {
c := &cobra.Command{
Use: cmdCustom,
Short: cmdCustomDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
return nil
},
}
c.AddCommand(getCustomCreate(), getCustomDestroy())
return c
}

func getCustomCreate() *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
}

// Initialize context
qenvsContext.Init(
viper.GetString(params.ProjectName),
viper.GetString(params.BackedURL),
viper.GetString(params.ConnectionDetailsOutput),
viper.GetStringMapString(params.Tags))

// Run create
if err := custom.Create(
&custom.Request{
Prefix: "main",
AMI: viper.GetString(amiID),
InstanceType: viper.GetString(instanceType),
ProductDescription: viper.GetString(productDescription),
Spot: viper.IsSet(spot),
Airgap: viper.IsSet(airgap)}); err != nil {
logging.Error(err)
}
return nil
},
}
flagSet := pflag.NewFlagSet(params.CreateCmdName, pflag.ExitOnError)
flagSet.StringP(params.ConnectionDetailsOutput, "", "", params.ConnectionDetailsOutputDesc)
flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc)
flagSet.StringP(amiID, "", "", amiIDDesc)
flagSet.StringP(instanceType, "", "", instanceTypeDesc)
flagSet.StringP(productDescription, "", productDescriptionDefault, productDescriptionDesc)
flagSet.Bool(airgap, false, airgapDesc)
flagSet.Bool(spot, false, spotDesc)
c.PersistentFlags().AddFlagSet(flagSet)
err := c.MarkPersistentFlagRequired(amiID)
if err != nil {
logging.Error(err)
}
err = c.MarkPersistentFlagRequired(instanceType)
if err != nil {
logging.Error(err)
}
return c
}

func getCustomDestroy() *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
}

qenvsContext.InitBase(
viper.GetString(params.ProjectName),
viper.GetString(params.BackedURL))

if err := fedora.Destroy(); err != nil {
logging.Error(err)
}
return nil
},
}
return c
}
18 changes: 18 additions & 0 deletions pkg/provider/aws/action/custom/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package custom

var (
stackName = "stackCustom"
awsCustomID = "ac"

diskSize int = 200

// amiRegex = "Fedora-Cloud-Base-%s*"
// amiOwner = "125523088429"
// amiUserDefault = "fedora"

// requiredInstanceTypes = []string{"c5.metal", "c5d.metal", "c5n.metal"}

outputHost = "acdHost"
outputUsername = "acUsername"
outputUserPrivateKey = "acPrivatekey"
)
238 changes: 238 additions & 0 deletions pkg/provider/aws/action/custom/custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package custom

import (
"fmt"

"github.com/adrianriobo/qenvs/pkg/manager"
qenvsContext "github.com/adrianriobo/qenvs/pkg/manager/context"
infra "github.com/adrianriobo/qenvs/pkg/provider"
"github.com/adrianriobo/qenvs/pkg/provider/aws"
"github.com/adrianriobo/qenvs/pkg/provider/aws/data"
"github.com/adrianriobo/qenvs/pkg/provider/aws/modules/bastion"
"github.com/adrianriobo/qenvs/pkg/provider/aws/modules/ec2/compute"
"github.com/adrianriobo/qenvs/pkg/provider/aws/modules/network"
"github.com/adrianriobo/qenvs/pkg/provider/aws/modules/spot"
amiSVC "github.com/adrianriobo/qenvs/pkg/provider/aws/services/ec2/ami"
"github.com/adrianriobo/qenvs/pkg/provider/aws/services/ec2/keypair"
securityGroup "github.com/adrianriobo/qenvs/pkg/provider/aws/services/ec2/security-group"
"github.com/adrianriobo/qenvs/pkg/provider/util/command"
"github.com/adrianriobo/qenvs/pkg/provider/util/output"
"github.com/adrianriobo/qenvs/pkg/util"
"github.com/adrianriobo/qenvs/pkg/util/logging"
resourcesUtil "github.com/adrianriobo/qenvs/pkg/util/resources"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/auto"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type Request struct {
Prefix string
AMI string
InstanceType string
ProductDescription string
Spot bool
Airgap bool
// internal management
// For airgap scenario there is an orchestation of
// a phase with connectivity on the machine (allowing bootstraping)
// a pahase with connectivyt off where the subnet for the target lost the nat gateway
airgapPhaseConnectivity network.Connectivity
// location and price (if Spot is enable)
region string
az string
spotPrice float64
}

// Create orchestrate 2 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(r *Request) error {
// if r.Spot {
// sr := spot.SpotOptionRequest{
// Prefix: r.Prefix,
// ProductDescription: "Linux/UNIX",
// InstaceTypes: ,
// AMIName: fmt.Sprintf(amiRegex, r.Version),
// AMIArch: "x86_64",
// }
// so, err := sr.Create()
// if err != nil {
// return err
// }
// r.region = so.Region
// r.az = so.AvailabilityZone
// r.spotPrice = so.MaxPrice
// } else {
// r.region = os.Getenv("AWS_DEFAULT_REGION")
// az, err := data.GetRandomAvailabilityZone(r.region, nil)
// if err != nil {
// return err
// }
// r.az = *az
// }

// // if not only host the mac machine will be created
// if !r.Airgap {
// return r.createMachine()
// }
// // Airgap scneario requires orchestration
// return r.createAirgapMachine()
sit, err := data.GetSimilarInstaceTypes(r.InstanceType, "")
logging.Debugf("similar types %v", sit)
return err
}

// Will destroy resources related to machine
func Destroy() (err error) {
if err := aws.DestroyStack(
aws.DestroyStackRequest{
Stackname: stackName,
}); err != nil {
return err
}
if spot.Exist() {
return spot.Destroy()
}
return nil
}

func (r *Request) createMachine() error {
cs := manager.Stack{
StackName: qenvsContext.StackNameByProject(stackName),
ProjectName: qenvsContext.ProjectName(),
BackedURL: qenvsContext.BackedURL(),
ProviderCredentials: aws.GetClouProviderCredentials(
map[string]string{
aws.CONFIG_AWS_REGION: r.region}),
DeployFunc: r.deploy,
}

sr, _ := manager.UpStack(cs)
return r.manageResults(sr)
}

// Abstract this with a stackAirgapHandle receives a fn (connectivty on / off) err executes
// first on then off
func (r *Request) createAirgapMachine() error {
r.airgapPhaseConnectivity = network.ON
err := r.createMachine()
if err != nil {
return nil
}
r.airgapPhaseConnectivity = network.OFF
return r.createMachine()
}

// function wil all the logic to deploy resources required by windows
// * create AMI Copy if needed
// * networking
// * key
// * security group
// * compute
// * checks
func (r *Request) deploy(ctx *pulumi.Context) error {
// Get AMI
ami, err := amiSVC.GetAMIByName(ctx,
fmt.Sprintf(amiRegex, r.Version),

Check failure on line 136 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: amiRegex

Check failure on line 136 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

r.Version undefined (type *Request has no field or method Version)
amiOwner,

Check failure on line 137 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: amiOwner
map[string]string{
"architecture": "x86_64"})
if err != nil {
return err
}
// Networking
nr := network.NetworkRequest{
Prefix: r.Prefix,
ID: awsFedoraDedicatedID,

Check failure on line 146 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: awsFedoraDedicatedID
Region: r.region,
AZ: r.az,
// LB is required if we use as which is used for spot feature
CreateLoadBalancer: &r.Spot,
Airgap: r.Airgap,
AirgapPhaseConnectivity: r.airgapPhaseConnectivity,
}
vpc, targetSubnet, _, bastion, lb, err := nr.Network(ctx)
if err != nil {
return err
}
// Create Keypair
kpr := keypair.KeyPairRequest{
Name: resourcesUtil.GetResourceName(
r.Prefix, awsFedoraDedicatedID, "pk")}

Check failure on line 161 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: awsFedoraDedicatedID
keyResources, err := kpr.Create(ctx)
if err != nil {
return err
}
ctx.Export(fmt.Sprintf("%s-%s", r.Prefix, outputUserPrivateKey),
keyResources.PrivateKey.PrivateKeyPem)
// Security groups
securityGroups, err := r.securityGroups(ctx, vpc)
if err != nil {
return err
}
cr := compute.ComputeRequest{
Prefix: r.Prefix,
ID: awsFedoraDedicatedID,

Check failure on line 175 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: awsFedoraDedicatedID
VPC: vpc,
Subnet: targetSubnet,
AMI: ami,
KeyResources: keyResources,
SecurityGroups: securityGroups,
InstaceTypes: requiredInstanceTypes,

Check failure on line 181 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: requiredInstanceTypes
DiskSize: &diskSize,
Airgap: r.Airgap,
LB: lb,
LBTargetGroups: []int{22},
Spot: r.Spot}
c, err := cr.NewCompute(ctx)
if err != nil {
return err
}
ctx.Export(fmt.Sprintf("%s-%s", r.Prefix, outputUsername),
pulumi.String(amiUserDefault))

Check failure on line 192 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: amiUserDefault
ctx.Export(fmt.Sprintf("%s-%s", r.Prefix, outputHost),
c.GetHostIP(!r.Airgap))
return c.Readiness(ctx, command.CommandPing, r.Prefix, awsFedoraDedicatedID,

Check failure on line 195 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: awsFedoraDedicatedID
keyResources.PrivateKey, amiUserDefault, bastion, []pulumi.Resource{})

Check failure on line 196 in pkg/provider/aws/action/custom/custom.go

View workflow job for this annotation

GitHub Actions / build

undefined: amiUserDefault
}

// Write exported values in context to files o a selected target folder
func (r *Request) manageResults(stackResult auto.UpResult) error {
results := 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",
}
if r.Airgap {
err := bastion.WriteOutputs(stackResult, r.Prefix, qenvsContext.GetResultsOutputPath())
if err != nil {
return err
}
}
return output.Write(stackResult, qenvsContext.GetResultsOutputPath(), results)
}

// security group for mac machine with ingress rules for ssh and vnc
func (r *Request) securityGroups(ctx *pulumi.Context,
vpc *ec2.Vpc) (pulumi.StringArray, error) {
// ingress for ssh access from 0.0.0.0
sshIngressRule := securityGroup.SSH_TCP
sshIngressRule.CidrBlocks = infra.NETWORKING_CIDR_ANY_IPV4
// Create SG with ingress rules
sg, err := securityGroup.SGRequest{
Name: resourcesUtil.GetResourceName(r.Prefix, awsFedoraDedicatedID, "sg"),
VPC: vpc,
Description: fmt.Sprintf("sg for %s", awsFedoraDedicatedID),
IngressRules: []securityGroup.IngressRules{
sshIngressRule},
}.Create(ctx)
if err != nil {
return nil, err
}
// Convert to an array of IDs
sgs := util.ArrayConvert([]*ec2.SecurityGroup{sg.SG},
func(sg *ec2.SecurityGroup) pulumi.StringInput {
return sg.ID()
})
return pulumi.StringArray(sgs[:]), nil
}
Loading

0 comments on commit 9f09ad4

Please sign in to comment.