From d48fe0204905c0e60a55b938c92cacb2655ead70 Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Sat, 13 Jan 2024 21:14:36 -0700 Subject: [PATCH] Add program for Debian on Azure Add Pulumi program in Go for running Debian on Azure Signed-off-by: Scott Lowe --- debian/debian-pulumi-azure/Pulumi.yaml | 3 + debian/debian-pulumi-azure/README.md | 43 ++++++ debian/debian-pulumi-azure/go.mod | 98 ++++++++++++ debian/debian-pulumi-azure/main.go | 201 +++++++++++++++++++++++++ 4 files changed, 345 insertions(+) create mode 100644 debian/debian-pulumi-azure/Pulumi.yaml create mode 100644 debian/debian-pulumi-azure/README.md create mode 100644 debian/debian-pulumi-azure/go.mod create mode 100644 debian/debian-pulumi-azure/main.go diff --git a/debian/debian-pulumi-azure/Pulumi.yaml b/debian/debian-pulumi-azure/Pulumi.yaml new file mode 100644 index 0000000..804dd99 --- /dev/null +++ b/debian/debian-pulumi-azure/Pulumi.yaml @@ -0,0 +1,3 @@ +name: debian-pulumi-azure +runtime: go +description: An Azure Native Go program to stand up a Debian VM diff --git a/debian/debian-pulumi-azure/README.md b/debian/debian-pulumi-azure/README.md new file mode 100644 index 0000000..51f39ab --- /dev/null +++ b/debian/debian-pulumi-azure/README.md @@ -0,0 +1,43 @@ +# Debian on Azure Using Pulumi + +These files were created to allow users to quickly and easily deploy a Debian VM on Azure 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 `value, ok := map[key]` idiom for checking configuration values passed in from the user +* Creating and assigning a public IP to a VM + +## 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 Vnet, a single subnet, a security group to allow SSH access, and a Debian-based VM with a public IP address. + +* `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 azure-native:location ` to set the Azure 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". + * `versionname`: Set this to "buster", "bullseye", or "bookworm" to control the version of Debian used in the VM. These version names correspond to version numbers 10, 11, and 12, respectively. The default value is "bookworm". + +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/debian/debian-pulumi-azure/go.mod b/debian/debian-pulumi-azure/go.mod new file mode 100644 index 0000000..f08b866 --- /dev/null +++ b/debian/debian-pulumi-azure/go.mod @@ -0,0 +1,98 @@ +module debian-pulumi-azure + +go 1.21 + +toolchain go1.21.5 + +require ( + github.com/pulumi/pulumi-azure-native-sdk/compute/v2 v2.24.0 + github.com/pulumi/pulumi-azure-native-sdk/network/v2 v2.24.0 + github.com/pulumi/pulumi-azure-native-sdk/resources/v2 v2.24.0 + github.com/pulumi/pulumi-tls/sdk/v4 v4.11.1 + github.com/pulumi/pulumi/sdk/v3 v3.100.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // 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/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/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.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // 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-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pgavlin/fx v0.1.6 // 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/appdash v0.0.0-20231130102222-75f619a67231 // indirect + github.com/pulumi/esc v0.6.2 // indirect + github.com/pulumi/pulumi-azure-native-sdk/v2 v2.24.0 // 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.2.1 // 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.17.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // 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 +) diff --git a/debian/debian-pulumi-azure/main.go b/debian/debian-pulumi-azure/main.go new file mode 100644 index 0000000..3e06c53 --- /dev/null +++ b/debian/debian-pulumi-azure/main.go @@ -0,0 +1,201 @@ +package main + +import ( + "fmt" + "strconv" + + "github.com/pulumi/pulumi-azure-native-sdk/compute/v2" + "github.com/pulumi/pulumi-azure-native-sdk/network/v2" + "github.com/pulumi/pulumi-azure-native-sdk/resources/v2" + tls "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 + versionMap := map[string]int{"buster": 10, "bullseye": 11, "bookworm": 12} + + // Import the program's configuration settings + cfg := config.New(ctx, "") + vmSize, err := cfg.Try("vmSize") + if err != nil { + vmSize = "Standard_A1_v2" + } + versionName, err := config.Try(ctx, "version") + if err != nil { + versionName = "bookworm" + } + versionNum, ok := versionMap[versionName] + if !ok { + versionNum = 12 + } + + // Set parameters for looking up the VM image + osImagePublisher := "Debian" + osImageOffer := fmt.Sprintf("debian-%d", versionNum) + osImageSku := strconv.Itoa(versionNum) + osImageVersion := "latest" + + // Create an SSH key + sshKey, err := tls.NewPrivateKey(ctx, "ssh-key", &tls.PrivateKeyArgs{ + Algorithm: pulumi.String("RSA"), + RsaBits: pulumi.Int(4096), + }) + if err != nil { + return err + } + + // Create a resource group + debianRg, err := resources.NewResourceGroup(ctx, "debian-rg", nil) + if err != nil { + return err + } + + // Create a virtual network + debianVnet, err := network.NewVirtualNetwork(ctx, "debian-vnet", &network.VirtualNetworkArgs{ + ResourceGroupName: debianRg.Name, + AddressSpace: network.AddressSpaceArgs{ + AddressPrefixes: pulumi.ToStringArray([]string{ + "10.0.0.0/16", + }), + }, + }) + if err != nil { + return err + } + + // Create a subnet within the virtual network + debianSubnet, err := network.NewSubnet(ctx, "debian-subnet", &network.SubnetArgs{ + AddressPrefix: pulumi.String("10.0.1.0/24"), + ResourceGroupName: debianRg.Name, + VirtualNetworkName: debianVnet.Name, + }) + if err != nil { + return err + } + + // Create a public IP address for the VM + debianPubIp, err := network.NewPublicIPAddress(ctx, "debian-pub-ip", &network.PublicIPAddressArgs{ + ResourceGroupName: debianRg.Name, + PublicIPAllocationMethod: pulumi.StringPtr("Dynamic"), + }) + if err != nil { + return err + } + + // Create a security group allowing inbound access over ports 80 (for HTTP) and 22 (for SSH) + debianSg, err := network.NewNetworkSecurityGroup(ctx, "debian-sg", &network.NetworkSecurityGroupArgs{ + ResourceGroupName: debianRg.Name, + SecurityRules: network.SecurityRuleTypeArray{ + network.SecurityRuleTypeArgs{ + Name: pulumi.StringPtr("debian-allow-ssh"), + Priority: pulumi.Int(1000), + Direction: pulumi.String("Inbound"), + Access: pulumi.String("Allow"), + Protocol: pulumi.String("Tcp"), + SourcePortRange: pulumi.StringPtr("*"), + SourceAddressPrefix: pulumi.StringPtr("*"), + DestinationAddressPrefix: pulumi.StringPtr("*"), + DestinationPortRanges: pulumi.ToStringArray([]string{ + "22", + }), + }, + }, + }) + if err != nil { + return err + } + + // Create a network interface with the virtual network, IP address, and security group + debianNetIface, err := network.NewNetworkInterface(ctx, "debian-net-iface", &network.NetworkInterfaceArgs{ + ResourceGroupName: debianRg.Name, + NetworkSecurityGroup: &network.NetworkSecurityGroupTypeArgs{ + Id: debianSg.ID(), + }, + IpConfigurations: network.NetworkInterfaceIPConfigurationArray{ + &network.NetworkInterfaceIPConfigurationArgs{ + Name: pulumi.String("debian-ipconfiguration"), + PrivateIPAllocationMethod: pulumi.String("Dynamic"), + Subnet: &network.SubnetTypeArgs{ + Id: debianSubnet.ID(), + }, + PublicIPAddress: &network.PublicIPAddressTypeArgs{ + Id: debianPubIp.ID(), + }, + }, + }, + }) + if err != nil { + return err + } + + // Create the virtual machine + debianVm, err := compute.NewVirtualMachine(ctx, "debian-vm", &compute.VirtualMachineArgs{ + ResourceGroupName: debianRg.Name, + NetworkProfile: &compute.NetworkProfileArgs{ + NetworkInterfaces: compute.NetworkInterfaceReferenceArray{ + &compute.NetworkInterfaceReferenceArgs{ + Id: debianNetIface.ID(), + Primary: pulumi.Bool(true), + }, + }, + }, + HardwareProfile: &compute.HardwareProfileArgs{ + VmSize: pulumi.String(vmSize), + }, + OsProfile: &compute.OSProfileArgs{ + ComputerName: pulumi.String("debian"), + AdminUsername: pulumi.String("azureuser"), + LinuxConfiguration: &compute.LinuxConfigurationArgs{ + DisablePasswordAuthentication: pulumi.Bool(true), + Ssh: &compute.SshConfigurationArgs{ + PublicKeys: compute.SshPublicKeyTypeArray{ + &compute.SshPublicKeyTypeArgs{ + KeyData: sshKey.PublicKeyOpenssh, + Path: pulumi.String("/home/azureuser/.ssh/authorized_keys"), + }, + }, + }, + }, + }, + StorageProfile: &compute.StorageProfileArgs{ + OsDisk: &compute.OSDiskArgs{ + Name: pulumi.String("debian-osdisk"), + CreateOption: pulumi.String("FromImage"), + }, + ImageReference: &compute.ImageReferenceArgs{ + Publisher: pulumi.String(osImagePublisher), + Offer: pulumi.String(osImageOffer), + Sku: pulumi.String(osImageSku), + Version: pulumi.String(osImageVersion), + }, + }, + }) + if err != nil { + return err + } + + // Once the machine is created, fetch its IP address and DNS hostname + address := debianVm.ID().ApplyT(func(_ pulumi.ID) network.LookupPublicIPAddressResultOutput { + return network.LookupPublicIPAddressOutput(ctx, network.LookupPublicIPAddressOutputArgs{ + ResourceGroupName: debianRg.Name, + PublicIpAddressName: debianPubIp.Name, + }) + }) + + // Export the VM's hostname, public IP address, HTTP URL, and SSH private key + ctx.Export("ip", address.ApplyT(func(addr network.LookupPublicIPAddressResult) (string, error) { + return *addr.IpAddress, nil + }).(pulumi.StringOutput)) + + // ctx.Export("hostname", address.ApplyT(func(addr network.LookupPublicIPAddressResult) (string, error) { + // return *addr.DnsSettings.Fqdn, nil + // }).(pulumi.StringOutput)) + + ctx.Export("privatekey", sshKey.PrivateKeyOpenssh) + + return nil + }) +}