diff --git a/coreos/fcos-pulumi-aws/Pulumi.yaml b/coreos/fcos-pulumi-aws/Pulumi.yaml new file mode 100644 index 0000000..291fc1f --- /dev/null +++ b/coreos/fcos-pulumi-aws/Pulumi.yaml @@ -0,0 +1,3 @@ +name: fcos-pulumi-aws +runtime: go +description: A Go Pulumi program to stand up a Fedora CoreOS (FCOS) instance on AWS diff --git a/coreos/fcos-pulumi-aws/README.md b/coreos/fcos-pulumi-aws/README.md new file mode 100644 index 0000000..3c89cc5 --- /dev/null +++ b/coreos/fcos-pulumi-aws/README.md @@ -0,0 +1,47 @@ +# Fedora CoreOS on AWS Using Pulumi + +These files were created to allow users to quickly and easily deploy a Fedora CoreOS instance on Amazon EC2 using [Pulumi](https://www.pulumi.com). This Pulumi program was written in [Go](https://go.dev). + +While not complex, the Pulumi program here does illustrate a few things that might be useful for newer users: + +* Use of the `slices` library to verify that a configuration value passed in from the user is in an allowed list of values +* Supporting both X86_64/AMD64- as well as ARM64-based configurations +* Dynamically looking up an AMI +* Modifying the default values for an AWSX VPC to create only public subnets +* Creating an SSH key (and associated AWS key pair) + +## Contents + +* `go.mod`: This file contains dependencies used by this Go program. + +* `go.sum`: This file contains checksums for each of the direct and indirect dependencies. The checksum is used to validate that none of them has been modified. + +* `main.go`: This Go file is the Pulumi program executed by the `pulumi` CLI, and contains the resource definitions to create a VPC with only public subnets, a security group to allow SSH access, and a Debian-based EC2 instance in one of the public subnets. + +* `Pulumi.yaml`: This is the Pulumi project file. + +* `README.md`: This file you're currently reading. + +## Instructions + +These instructions assume you've already installed and configured Pulumi and all necessary dependencies (Go, for this example). Please refer to the Pulumi documentation for more details on installation or configuration. + +1. Copy the contents of this directory down to a directory on your system, or clone the entire repository and then change into the directory where this section of the cloned repository resides. + +1. Run `pulumi stack init` to create a new stack. + +1. Run `pulumi config set aws:region ` to set the AWS region where the Pulumi program should create resources. _This is a required configuration value; CLI operations will fail if you don't set this value._ + +1. (Optional) Run `pulumi config set` to set configuration values that affect the behavior of the Pulumi program. The optional configuration values are: + + * `architecture`: Set this to "amd64" or "arm64". The values "x86_64" and "x64" are also supported and will have the same effect as "amd64". The default value is "arm64". + * `networkcidr`: Set this to control the CIDR that will be used when the VPC is created. The default value is "10.0.0.0/16". + * `channel`: Set this to "stable", "testing", or "next" to control the release stream deployed on EC2. The default value is "stable". + +1. Run `pulumi up` to instantiate the resources. + +Enjoy! When you're finished, run `pulumi destroy` to tear down all the provisioned resources. + +## License + +This content is licensed under the MIT License. diff --git a/coreos/fcos-pulumi-aws/go.mod b/coreos/fcos-pulumi-aws/go.mod new file mode 100644 index 0000000..1f42f4f --- /dev/null +++ b/coreos/fcos-pulumi-aws/go.mod @@ -0,0 +1,91 @@ +module ubuntu-pulumi-aws + +go 1.21 + +require ( + github.com/pulumi/pulumi-aws/sdk/v6 v6.8.0 + github.com/pulumi/pulumi-awsx/sdk/v2 v2.1.1 + github.com/pulumi/pulumi-tls/sdk/v4 v4.11.1 + github.com/pulumi/pulumi/sdk/v3 v3.91.1 +) + +require ( + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect + github.com/charmbracelet/bubbletea v0.24.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/cheggaaa/pb v1.0.29 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.4.0 // indirect + github.com/go-git/go-git/v5 v5.6.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl/v2 v2.17.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/pulumi/esc v0.5.6 // indirect + github.com/pulumi/pulumi-docker/sdk/v4 v4.4.3 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/skeema/knownhosts v1.1.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.13.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect + google.golang.org/grpc v1.57.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/frand v1.4.2 // indirect + sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 // indirect +) diff --git a/coreos/fcos-pulumi-aws/main.go b/coreos/fcos-pulumi-aws/main.go new file mode 100644 index 0000000..6b7034f --- /dev/null +++ b/coreos/fcos-pulumi-aws/main.go @@ -0,0 +1,143 @@ +package main + +import ( + "fmt" + "log" + "slices" + + "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2" + awsx "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2" + "github.com/pulumi/pulumi-tls/sdk/v4/go/tls" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + // Set up maps that are used later + streams := []string{"stable", "testing", "next"} + typeMap := map[string]string{"amd64": "t3a.small", "arm64": "t4g.small", "x86_64": "t3a.small", "x64": "t3a.small"} + + // Retrieve configuration values + instanceCpuArch, err := config.Try(ctx, "architecture") + if err != nil { + instanceCpuArch = "arm64" + } + instanceType, ok := typeMap[instanceCpuArch] + if !ok { + instanceCpuArch = "arm64" + instanceType = "t4g.small" + } + if instanceCpuArch == "amd64" || instanceCpuArch == "x64" { + instanceCpuArch = "x86_64" + } + vpcNetworkCidr, err := config.Try(ctx, "networkcidr") + if err != nil { + vpcNetworkCidr = "10.0.0.0/16" + } + channel, err := config.Try(ctx, "channel") + if err != nil { + channel = "stable" + } + if !slices.Contains(streams, channel) { + channel = "stable" + } + + // Create a new VPC, subnets, and associated infrastructure + fcosVpc, err := awsx.NewVpc(ctx, "fcos-vpc", &awsx.VpcArgs{ + CidrBlock: &vpcNetworkCidr, + EnableDnsHostnames: pulumi.Bool(true), + EnableDnsSupport: pulumi.Bool(true), + NatGateways: &awsx.NatGatewayConfigurationArgs{ + Strategy: awsx.NatGatewayStrategyNone, + }, + SubnetSpecs: []awsx.SubnetSpecArgs{ + { + Type: awsx.SubnetTypePublic, + }, + }, + }) + if err != nil { + log.Printf("error creating VPC: %s", err.Error()) + } + + // Create a Security Group that we can use to connect to our instance + fcosSg, err := ec2.NewSecurityGroup(ctx, "fcos-sg", &ec2.SecurityGroupArgs{ + VpcId: fcosVpc.VpcId, + Egress: ec2.SecurityGroupEgressArray{ + ec2.SecurityGroupEgressArgs{ + Protocol: pulumi.String("-1"), + FromPort: pulumi.Int(0), + ToPort: pulumi.Int(0), + CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}, + }, + }, + Ingress: ec2.SecurityGroupIngressArray{ + ec2.SecurityGroupIngressArgs{ + Protocol: pulumi.String("tcp"), + FromPort: pulumi.Int(22), + ToPort: pulumi.Int(22), + CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}, + }, + }, + }) + if err != nil { + log.Printf("error creating security group: %s", err.Error()) + } + + // Get AMI ID for Fedora CoreOS instance + descValue := fmt.Sprintf("*%s*", channel) + fcosAmi, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{ + Owners: []string{"125523088429"}, + MostRecent: pulumi.BoolRef(true), + Filters: []ec2.GetAmiFilter{ + {Name: "name", Values: []string{"fedora-coreos-*"}}, + {Name: "root-device-type", Values: []string{"ebs"}}, + {Name: "virtualization-type", Values: []string{"hvm"}}, + {Name: "architecture", Values: []string{instanceCpuArch}}, + {Name: "description", Values: []string{descValue}}, + }, + }) + if err != nil { + log.Printf("error looking up AMI: %s", err.Error()) + } + + // Create an SSH key + sshKey, err := tls.NewPrivateKey(ctx, "ssh-key", &tls.PrivateKeyArgs{ + Algorithm: pulumi.String("ED25519"), + }) + if err != nil { + log.Printf("error creating SSH key: %s", err.Error()) + } + + // Create an AWS key pair + fcosKeyPair, err := ec2.NewKeyPair(ctx, "fcos-key-pair", &ec2.KeyPairArgs{ + PublicKey: sshKey.PublicKeyOpenssh, + }) + if err != nil { + log.Printf("error creating AWS key pair: %s", err.Error()) + } + + // Launch an instance using Fedora CoreOS AMI + fcosInstance, err := ec2.NewInstance(ctx, "fcos-instance", &ec2.InstanceArgs{ + Ami: pulumi.String(fcosAmi.Id), + InstanceType: pulumi.String(instanceType), + AssociatePublicIpAddress: pulumi.Bool(true), + KeyName: fcosKeyPair.KeyName, + SubnetId: fcosVpc.PublicSubnetIds.Index(pulumi.Int(0)), + VpcSecurityGroupIds: pulumi.StringArray{fcosSg.ID()}, + Tags: pulumi.StringMap{ + "Name": pulumi.String("fcos-instance"), + }, + }) + if err != nil { + log.Printf("error launching instance: %s", err.Error()) + } + ctx.Export("instanceId", fcosInstance.ID()) + ctx.Export("instancePublicIpAddress", fcosInstance.PublicIp) + ctx.Export("instancePrivateIpAddress", fcosInstance.PrivateIp) + ctx.Export("privateKey", sshKey.PrivateKeyOpenssh) + + return nil + }) +}