diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index fb0f822..a5853c1 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -1,6 +1,7 @@ name: Check PR on: + workflow_dispatch: pull_request: branches: [ master ] @@ -9,21 +10,27 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Unshallow - run: git fetch --prune --unshallow + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - run: git tag $(cat VERSION) - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 + with: + go-version: 1.19 + - name: Setup `packer` + uses: hashicorp/setup-packer@main + id: setup with: - go-version: 1.18 + version: 1.9.1 - name: Describe plugin id: plugin_describe run: echo "::set-output name=api_version::$(go run . describe | jq -r '.api_version')" - name: Install packer-pdc run: go get github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@latest - name: Run GoReleaser build - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v4 with: version: latest args: build --single-target --snapshot --clean diff --git a/README.md b/README.md index 84208ef..ea45b3f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This is a [Packer](https://www.packer.io/) Plugin for building images that work packer { required_plugins { veertu-anka = { - version = "= v3.1.1" + version = "= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } diff --git a/VERSION b/VERSION index 50e47c8..a4f52a5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.1 \ No newline at end of file +3.2.0 \ No newline at end of file diff --git a/builder/anka/step_create_vm.go b/builder/anka/step_create_vm.go index 2776381..78bd246 100644 --- a/builder/anka/step_create_vm.go +++ b/builder/anka/step_create_vm.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "log" + "regexp" + "strconv" "github.com/hashicorp/packer-plugin-sdk/multistep" "github.com/hashicorp/packer-plugin-sdk/packer" @@ -31,20 +33,28 @@ func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis s.vmName = config.VMName if s.vmName == "" { - if config.HostArch == "arm64" { - installerData := util.InstallerIPSWPlist{} - installerData, err = ankaUtil.ObtainMacOSVersionFromInstallerIPSW(config.Installer) - if err != nil { - return onError(err) + matchInstaller, err := regexp.Match(".app(/?)$|.ipsw(/?)$", []byte(config.Installer)) + if err != nil { + return onError(err) + } + if matchInstaller { + if config.HostArch == "arm64" { + installerData := util.InstallerIPSWPlist{} + installerData, err = ankaUtil.ObtainMacOSVersionFromInstallerIPSW(config.Installer) + if err != nil { + return onError(err) + } + s.vmName = fmt.Sprintf("anka-packer-base-%s-%s", installerData.ProductVersion, installerData.ProductBuildVersion) + } else { + installerData := util.InstallerAppPlist{} + installerData, err = ankaUtil.ObtainMacOSVersionFromInstallerApp(config.Installer) + if err != nil { + return onError(err) + } + s.vmName = fmt.Sprintf("anka-packer-base-%s-%s", installerData.OSVersion, installerData.BundlerVersion) } - s.vmName = fmt.Sprintf("anka-packer-base-%s-%s", installerData.ProductVersion, installerData.ProductBuildVersion) } else { - installerData := util.InstallerAppPlist{} - installerData, err = ankaUtil.ObtainMacOSVersionFromInstallerApp(config.Installer) - if err != nil { - return onError(err) - } - s.vmName = fmt.Sprintf("anka-packer-base-%s-%s", installerData.OSVersion, installerData.BundlerVersion) + s.vmName = fmt.Sprintf("anka-packer-base-%s", config.Installer) } } @@ -70,6 +80,16 @@ func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis return onError(err) } + createdShow, err := s.client.Show(s.vmName) + if err != nil { + return onError(err) + } + + err = s.modifyVMProperties(createdShow, config, ui) + if err != nil { + return onError(err) + } + return multistep.ActionContinue } @@ -104,6 +124,70 @@ func (s *StepCreateVM) createFromInstaller(ui packer.Ui, config *Config) error { return nil } +func (s *StepCreateVM) modifyVMProperties(showResponse client.ShowResponse, config *Config, ui packer.Ui) error { + stopParams := client.StopParams{ + VMName: showResponse.Name, + } + + if len(config.PortForwardingRules) > 0 { + describeResponse, err := s.client.Describe(showResponse.Name) + if err != nil { + return err + } + existingForwardedPorts := make(map[int]struct{}) + for _, existingNetworkCard := range describeResponse.NetworkCards { + for _, existingPortForwardingRule := range existingNetworkCard.PortForwardingRules { + existingForwardedPorts[existingPortForwardingRule.HostPort] = struct{}{} + } + } + for _, wantedPortForwardingRule := range config.PortForwardingRules { + ui.Say(fmt.Sprintf("Ensuring %s port-forwarding (Guest Port: %s, Host Port: %s, Rule Name: %s)", showResponse.Name, strconv.Itoa(wantedPortForwardingRule.PortForwardingGuestPort), strconv.Itoa(wantedPortForwardingRule.PortForwardingHostPort), wantedPortForwardingRule.PortForwardingRuleName)) + if _, ok := existingForwardedPorts[wantedPortForwardingRule.PortForwardingHostPort]; ok { + if wantedPortForwardingRule.PortForwardingHostPort > 0 { + ui.Error(fmt.Sprintf("Found an existing host port rule (%s)! Skipping without setting...", strconv.Itoa(wantedPortForwardingRule.PortForwardingHostPort))) + continue + } + } + err := s.client.Stop(stopParams) + if err != nil { + return err + } + err = s.client.Modify(showResponse.Name, "add", "port-forwarding", "--host-port", strconv.Itoa(wantedPortForwardingRule.PortForwardingHostPort), "--guest-port", strconv.Itoa(wantedPortForwardingRule.PortForwardingGuestPort), wantedPortForwardingRule.PortForwardingRuleName) + if !config.PackerConfig.PackerForce { + if err != nil { + return err + } + } + } + } + + if config.HWUUID != "" { + err := s.client.Stop(stopParams) + if err != nil { + return err + } + ui.Say(fmt.Sprintf("Modifying VM custom-variable hw.uuid to %s", config.HWUUID)) + err = s.client.Modify(showResponse.Name, "set", "custom-variable", "hw.uuid", config.HWUUID) + if err != nil { + return err + } + } + + if config.DisplayController != "" { + err := s.client.Stop(stopParams) + if err != nil { + return err + } + ui.Say(fmt.Sprintf("Modifying VM display controller to %s", config.DisplayController)) + err = s.client.Modify(showResponse.Name, "set", "display", "-c", config.DisplayController) + if err != nil { + return err + } + } + + return nil +} + // Cleanup will delete the vm if there happens to be an error and handle anything failed states func (s *StepCreateVM) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) diff --git a/builder/anka/step_create_vm_test.go b/builder/anka/step_create_vm_test.go index 4a5fe4e..e6b0566 100644 --- a/builder/anka/step_create_vm_test.go +++ b/builder/anka/step_create_vm_test.go @@ -2,7 +2,9 @@ package anka import ( "context" + "encoding/json" "fmt" + "strconv" "testing" "github.com/golang/mock/gomock" @@ -15,8 +17,13 @@ import ( "gotest.tools/v3/assert" ) +var ( + createdShowResponse client.ShowResponse + createdDescribeResponse client.DescribeResponse +) + func TestCreateVMRun(t *testing.T) { - + createdVMUUID := "abcd-efgh-1234-5678" mockCtrl := gomock.NewController(t) @@ -37,13 +44,23 @@ func TestCreateVMRun(t *testing.T) { state.Put("client", ankaClient) state.Put("util", ankaUtil) + err = json.Unmarshal(json.RawMessage(`{ "Name": "anka-packer-base-11.2-16.4.06", "UUID": "1234-hijk-abcdef-5678" }`), &createdShowResponse) + if err != nil { + t.Fail() + } + + err = json.Unmarshal(json.RawMessage(`{ }`), &createdDescribeResponse) + if err != nil { + t.Fail() + } + t.Run("create vm", func(t *testing.T) { config := &Config{ - DiskSize: "500G", - VCPUCount: "32G", - RAMSize: "16G", + DiskSize: "500G", + VCPUCount: "32G", + RAMSize: "16G", Installer: "/fake/InstallApp.app/", - VMName: "foo", + VMName: "foo", PackerConfig: common.PackerConfig{ PackerBuilderType: "veertu-anka-vm-create", }, @@ -56,13 +73,16 @@ func TestCreateVMRun(t *testing.T) { createParams := client.CreateParams{ Installer: config.Installer, - Name: step.vmName, - DiskSize: config.DiskSize, - VCPUCount: config.VCPUCount, - RAMSize: config.RAMSize, + Name: step.vmName, + DiskSize: config.DiskSize, + VCPUCount: config.VCPUCount, + RAMSize: config.RAMSize, } - ankaClient.EXPECT().Create(createParams, gomock.Any()).Return(createdVMUUID, nil).Times(1) + gomock.InOrder( + ankaClient.EXPECT().Create(createParams, gomock.Any()).Return(createdVMUUID, nil).Times(1), + ankaClient.EXPECT().Show(step.vmName).Return(createdShowResponse, nil).Times(1), + ) mockui := packer.MockUi{} mockui.Say(fmt.Sprintf("Creating a new VM Template (%s) from installer, this will take a while", step.vmName)) @@ -74,13 +94,52 @@ func TestCreateVMRun(t *testing.T) { assert.Equal(t, multistep.ActionContinue, stepAction) }) + t.Run("create vm without .app or ipsw", func(t *testing.T) { + config := &Config{ + DiskSize: "500G", + VCPUCount: "32G", + RAMSize: "16G", + Installer: "13.5", + PackerConfig: common.PackerConfig{ + PackerBuilderType: "veertu-anka-vm-create", + }, + } + + state.Put("config", config) + + step.vmName = "anka-packer-base-13.5" + state.Put("vm_name", step.vmName) + + createParams := client.CreateParams{ + Installer: config.Installer, + Name: step.vmName, + DiskSize: config.DiskSize, + VCPUCount: config.VCPUCount, + RAMSize: config.RAMSize, + } + + gomock.InOrder( + ankaClient.EXPECT().Create(createParams, gomock.Any()).Return(createdVMUUID, nil).Times(1), + ankaClient.EXPECT().Show(step.vmName).Return(createdShowResponse, nil).Times(1), + ) + + mockui := packer.MockUi{} + mockui.Say(fmt.Sprintf("Creating a new VM Template (%s) from installer, this will take a while", step.vmName)) + mockui.Say(fmt.Sprintf("VM %s was created (%s)", step.vmName, createdVMUUID)) + + stepAction := step.Run(ctx, state) + assert.Equal(t, mockui.SayMessages[0].Message, fmt.Sprintf("Creating a new VM Template (%s) from installer, this will take a while", step.vmName)) + assert.Equal(t, mockui.SayMessages[1].Message, fmt.Sprintf("VM %s was created (abcd-efgh-1234-5678)", step.vmName)) + assert.Equal(t, multistep.ActionContinue, stepAction) + }) + t.Run("create vm with packer force", func(t *testing.T) { config := &Config{ - DiskSize: "500G", - VCPUCount: "32G", - RAMSize: "16G", + DiskSize: "500G", + VCPUCount: "32G", + RAMSize: "16G", Installer: "/fake/InstallApp.app/", - VMName: "foo", + VMName: "foo", PackerConfig: common.PackerConfig{ PackerForce: true, PackerBuilderType: "veertu-anka-vm-create", @@ -94,16 +153,17 @@ func TestCreateVMRun(t *testing.T) { createParams := client.CreateParams{ Installer: config.Installer, - Name: step.vmName, - DiskSize: config.DiskSize, - VCPUCount: config.VCPUCount, - RAMSize: config.RAMSize, + Name: step.vmName, + DiskSize: config.DiskSize, + VCPUCount: config.VCPUCount, + RAMSize: config.RAMSize, } gomock.InOrder( ankaClient.EXPECT().Exists(step.vmName).Return(true, nil).Times(1), ankaClient.EXPECT().Delete(client.DeleteParams{VMName: step.vmName}).Return(nil).Times(1), ankaClient.EXPECT().Create(createParams, gomock.Any()).Return(createdVMUUID, nil).Times(1), + ankaClient.EXPECT().Show(step.vmName).Return(createdShowResponse, nil).Times(1), ) mockui := packer.MockUi{} @@ -120,11 +180,11 @@ func TestCreateVMRun(t *testing.T) { t.Run("create vm and installer app does not exist", func(t *testing.T) { config := &Config{ - DiskSize: "500G", - VCPUCount: "32G", - RAMSize: "16G", + DiskSize: "500G", + VCPUCount: "32G", + RAMSize: "16G", Installer: "/does/not/exist/InstallApp.app/", - VMName: "foo", + VMName: "foo", PackerConfig: common.PackerConfig{ PackerBuilderType: "veertu-anka-vm-create", }, @@ -137,10 +197,10 @@ func TestCreateVMRun(t *testing.T) { createParams := client.CreateParams{ Installer: config.Installer, - Name: step.vmName, - DiskSize: config.DiskSize, - VCPUCount: config.VCPUCount, - RAMSize: config.RAMSize, + Name: step.vmName, + DiskSize: config.DiskSize, + VCPUCount: config.VCPUCount, + RAMSize: config.RAMSize, } gomock.InOrder( @@ -160,9 +220,9 @@ func TestCreateVMRun(t *testing.T) { t.Run("create vm when no vm_name is provided in config", func(t *testing.T) { config := &Config{ - DiskSize: "500G", - VCPUCount: "32G", - RAMSize: "16G", + DiskSize: "500G", + VCPUCount: "32G", + RAMSize: "16G", Installer: "/fake/InstallApp.app/", PackerConfig: common.PackerConfig{ PackerBuilderType: "veertu-anka-vm-create", @@ -176,15 +236,16 @@ func TestCreateVMRun(t *testing.T) { createParams := client.CreateParams{ Installer: config.Installer, - Name: step.vmName, - DiskSize: config.DiskSize, - VCPUCount: config.VCPUCount, - RAMSize: config.RAMSize, + Name: step.vmName, + DiskSize: config.DiskSize, + VCPUCount: config.VCPUCount, + RAMSize: config.RAMSize, } gomock.InOrder( ankaUtil.EXPECT().ObtainMacOSVersionFromInstallerApp(config.Installer).Return(InstallerInfo, nil).Times(1), ankaClient.EXPECT().Create(createParams, gomock.Any()).Return(createdVMUUID, nil).Times(1), + ankaClient.EXPECT().Show(step.vmName).Return(createdShowResponse, nil).Times(1), ) mockui := packer.MockUi{} @@ -196,4 +257,85 @@ func TestCreateVMRun(t *testing.T) { assert.Equal(t, mockui.SayMessages[1].Message, "VM anka-packer-base-11.2-16.4.06 was created (abcd-efgh-1234-5678)") assert.Equal(t, multistep.ActionContinue, stepAction) }) + + t.Run("create vm with modify vm properties", func(t *testing.T) { + + err = json.Unmarshal(json.RawMessage(`{ "Name": "anka-packer-base-latest", "UUID": "1234-hijk-abcdef-5678" }`), &createdShowResponse) + if err != nil { + t.Fail() + } + + var config Config + err = json.Unmarshal(json.RawMessage(` + { + "PortForwardingRules": [ + { + "PortForwardingGuestPort": 8080, + "PortForwardingHostPort": 80, + "PortForwardingRuleName": "rule1" + } + ], + "DiskSize": "500G", + "VCPUCount": "32G", + "RAMSize": "16G", + "Installer": "latest", + "HWUUID": "abcdefgh", + "DisplayController": "pg" + } + `), &config) + if err != nil { + t.Fail() + } + + config.PackerConfig = common.PackerConfig{ + PackerBuilderType: "veertu-anka-vm-create", + } + + state.Put("config", config) + + step.vmName = fmt.Sprintf("anka-packer-base-%s", config.Installer) + state.Put("vm_name", step.vmName) + + createParams := client.CreateParams{ + Installer: config.Installer, + Name: step.vmName, + DiskSize: config.DiskSize, + VCPUCount: config.VCPUCount, + RAMSize: config.RAMSize, + } + + stopParams := client.StopParams{ + VMName: createdShowResponse.Name, + } + + state.Put("config", &config) + + gomock.InOrder( + ankaClient.EXPECT().Create(createParams, gomock.Any()).Return(createdVMUUID, nil).Times(1), + ankaClient.EXPECT().Show(step.vmName).Return(createdShowResponse, nil).Times(1), + ankaClient.EXPECT().Describe(step.vmName).Return(client.DescribeResponse{}, nil).Times(1), + ankaClient.EXPECT().Stop(stopParams).Return(nil).Times(1), + ankaClient.EXPECT(). + Modify(createdShowResponse.Name, "add", "port-forwarding", "--host-port", strconv.Itoa(config.PortForwardingRules[0].PortForwardingHostPort), "--guest-port", strconv.Itoa(config.PortForwardingRules[0].PortForwardingGuestPort), "rule1"). + Return(nil). + Times(1), + ankaClient.EXPECT().Stop(stopParams).Return(nil).Times(1), + ankaClient.EXPECT().Modify(createdShowResponse.Name, "set", "custom-variable", "hw.uuid", config.HWUUID).Return(nil).Times(1), + ankaClient.EXPECT().Stop(stopParams).Return(nil).Times(1), + ankaClient.EXPECT().Modify(createdShowResponse.Name, "set", "display", "-c", config.DisplayController).Return(nil).Times(1), + ) + + mockui := packer.MockUi{} + mockui.Say(fmt.Sprintf("Creating a new VM Template (%s) from installer, this will take a while", step.vmName)) + mockui.Say(fmt.Sprintf("VM %s was created (%s)", step.vmName, createdVMUUID)) + mockui.Say(fmt.Sprintf("Ensuring %s port-forwarding (Guest Port: %s, Host Port: %s, Rule Name: %s)", createdShowResponse.Name, strconv.Itoa(config.PortForwardingRules[0].PortForwardingGuestPort), strconv.Itoa(config.PortForwardingRules[0].PortForwardingHostPort), config.PortForwardingRules[0].PortForwardingRuleName)) + mockui.Say(fmt.Sprintf("Modifying VM custom-variable hw.uuid to %s", config.HWUUID)) + mockui.Say(fmt.Sprintf("Modifying VM display controller to %s", config.DisplayController)) + + stepAction := step.Run(ctx, state) + assert.Equal(t, mockui.SayMessages[0].Message, "Creating a new VM Template (anka-packer-base-latest) from installer, this will take a while") + assert.Equal(t, mockui.SayMessages[1].Message, "VM anka-packer-base-latest was created (abcd-efgh-1234-5678)") + assert.Equal(t, multistep.ActionContinue, stepAction) + + }) } diff --git a/docs/builders/vm-clone.mdx b/docs/builders/vm-clone.mdx index 61e8af1..009d90f 100644 --- a/docs/builders/vm-clone.mdx +++ b/docs/builders/vm-clone.mdx @@ -20,7 +20,7 @@ This builder is part of the [Veertu Anka plugin](https://github.com/veertuinc/pa packer { required_plugins { veertu-anka = { - version = "= 3.1.1" + version = "= 3.2.0" source = "github.com/veertuinc/veertu-anka" } } diff --git a/docs/builders/vm-create.mdx b/docs/builders/vm-create.mdx index 5326b20..ebb8432 100644 --- a/docs/builders/vm-create.mdx +++ b/docs/builders/vm-create.mdx @@ -26,7 +26,7 @@ your hcl template: packer { required_plugins { veertu-anka = { - version = "= 3.1.1" + version = "= 3.2.0" source = "github.com/veertuinc/veertu-anka" } } @@ -41,6 +41,7 @@ segmented below into two categories: required and optional parameters. #### Required Configuration * `installer` (String) The path to a macOS installer. This process takes about 20 minutes. + - Starting in 3.1.2: This can also be set to 'latest' or a specific macOS version in order to have Anka attempt downloading the installer for you (`vm_name` will be set to `anka-packer-base-${installer}`). * `type` (String) Must be `veertu-anka-vm-create`. @@ -70,6 +71,18 @@ segmented below into two categories: required and optional parameters. * `log_level` (String) The log level for Anka. This currently only supports `debug` and is only useful for VM creation failures. +* `hw_uuid` (String) (Anka 2 only) The Hardware UUID you wish to set (usually generated with `uuidgen`). + +* `port_forwarding_rules` (Struct) + + > If port forwarding rules are already set and you want to not have them fail the packer build, use `packer build --force`. + + * `port_forwarding_guest_port` (Int) + * `port_forwarding_host_port` (Int) + * `port_forwarding_rule_name` (String) + +* `display_controller` (string) The display controller to set (run `anka modify VMNAME set display --help` to see available options). + ## Example Here is an example: diff --git a/docs/post-processors/anka-registry-push.mdx b/docs/post-processors/anka-registry-push.mdx index 17a0727..739b29a 100644 --- a/docs/post-processors/anka-registry-push.mdx +++ b/docs/post-processors/anka-registry-push.mdx @@ -17,7 +17,7 @@ This post-processor is part of the [Veertu Anka plugin](https://github.com/veert packer { required_plugins { veertu-anka = { - version = "= 3.1.1" + version = "= 3.2.0" source = "github.com/veertuinc/veertu-anka" } } diff --git a/examples/create-from-installer-with-port-forwarding-rules.pkr.hcl b/examples/create-from-installer-with-port-forwarding-rules.pkr.hcl new file mode 100644 index 0000000..fe84a22 --- /dev/null +++ b/examples/create-from-installer-with-port-forwarding-rules.pkr.hcl @@ -0,0 +1,41 @@ +variable "vm_name" { + type = string + default = "anka-packer-base-macos" +} + +variable "installer" { + type = string + default = "/Applications/Install macOS Big Sur.app/" +} + +variable "vcpu_count" { + type = string + default = "" +} + +source "veertu-anka-vm-create" "base" { + installer = "${var.installer}" + vm_name = "${var.vm_name}" + vcpu_count = "${var.vcpu_count}" + port_forwarding_rules { + port_forwarding_guest_port = 80 + port_forwarding_host_port = 12345 + port_forwarding_rule_name = "website" + } + port_forwarding_rules { + port_forwarding_guest_port = 8080 + } +} + +build { + sources = [ + "source.veertu-anka-vm-create.base" + ] + + provisioner "shell" { + inline = [ + "echo hello world", + "echo llamas rock" + ] + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index a917a6d..a720d5c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veertuinc/packer-plugin-veertu-anka -go 1.18 +go 1.19 require ( github.com/golang/mock v1.6.0