Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: build & test
on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: build
run: make build
- name: lint
run: make lint
- name: generate
run: |
make generate
if ! git diff --exit-code; then
echo 'Generated files are out of date. Run `make generate` and commit the result.'
exit 1
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Go dependencies.
vendor/

# Build artifacts.
main
packer-plugin-oxide
Expand Down
18 changes: 18 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "2"

linters:
default: standard
disable:
- errcheck

formatters:
enable:
- goimports
- golines
settings:
golines:
max-len: 100
shorten-comments: true

run:
timeout: 5m
12 changes: 5 additions & 7 deletions .web-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ packer plugins install github.com/oxidecomputer/oxide
[`oxide-instance`](/packer/integrations/oxidecomputer/oxide/latest/components/builder/instance)
<!-- Code generated from the comments of the Builder struct in component/builder/instance/builder.go; DO NOT EDIT MANUALLY -->

The `oxide-instance` builder creates custom images for use with
[Oxide](https://oxide.computer). The builder launches a temporary instance
from an existing source image, connects to the instance using its external
IP, provisions the instance, and then creates a new image from the instance's
The `oxide-instance` builder creates custom images for use with [Oxide](https://oxide.computer).
The builder launches a temporary instance from an existing source image, connects to the instance
using its external IP, provisions the instance, and then creates a new image from the instance's
boot disk. The resulting image can be used to launch new instances on Oxide.

The builder does not manage images. Once it creates an image, it is up to you
Expand All @@ -47,9 +46,8 @@ to use it or delete it.
[`oxide-image`](/packer/integrations/oxidecomputer/oxide/latest/components/data-source/image)
<!-- Code generated from the comments of the Datasource struct in component/data-source/image/data_source.go; DO NOT EDIT MANUALLY -->

The `oxide-image` data source fetches [Oxide](https://oxide.computer) image
information for use in a Packer build. The image can be a project image or
silo image.
The `oxide-image` data source fetches [Oxide](https://oxide.computer) image information for use
in a Packer build. The image can be a project image or silo image.

<!-- End of code generated from the comments of the Datasource struct in component/data-source/image/data_source.go; -->

Expand Down
10 changes: 4 additions & 6 deletions .web-docs/components/builder/instance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ Type: `oxide-instance`

<!-- Code generated from the comments of the Builder struct in component/builder/instance/builder.go; DO NOT EDIT MANUALLY -->

The `oxide-instance` builder creates custom images for use with
[Oxide](https://oxide.computer). The builder launches a temporary instance
from an existing source image, connects to the instance using its external
IP, provisions the instance, and then creates a new image from the instance's
The `oxide-instance` builder creates custom images for use with [Oxide](https://oxide.computer).
The builder launches a temporary instance from an existing source image, connects to the instance
using its external IP, provisions the instance, and then creates a new image from the instance's
boot disk. The resulting image can be used to launch new instances on Oxide.

The builder does not manage images. Once it creates an image, it is up to you
Expand All @@ -18,8 +17,7 @@ to use it or delete it.

<!-- Code generated from the comments of the Config struct in component/builder/instance/config.go; DO NOT EDIT MANUALLY -->

The configuration arguments for the builder. Arguments can either be required
or optional.
The configuration arguments for the builder. Arguments can either be required or optional.

<!-- End of code generated from the comments of the Config struct in component/builder/instance/config.go; -->

Expand Down
8 changes: 3 additions & 5 deletions .web-docs/components/data-source/image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ Type: `oxide-image`

<!-- Code generated from the comments of the Datasource struct in component/data-source/image/data_source.go; DO NOT EDIT MANUALLY -->

The `oxide-image` data source fetches [Oxide](https://oxide.computer) image
information for use in a Packer build. The image can be a project image or
silo image.
The `oxide-image` data source fetches [Oxide](https://oxide.computer) image information for use
in a Packer build. The image can be a project image or silo image.

<!-- End of code generated from the comments of the Datasource struct in component/data-source/image/data_source.go; -->

Expand All @@ -13,8 +12,7 @@ silo image.

<!-- Code generated from the comments of the Config struct in component/data-source/image/config.go; DO NOT EDIT MANUALLY -->

The configuration arguments for the data source. Arguments can either be
required or optional.
The configuration arguments for the data source. Arguments can either be required or optional.

<!-- End of code generated from the comments of the Config struct in component/data-source/image/config.go; -->

Expand Down
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ generate: install-packer-sdc
test:
go test -race -count $(TEST_COUNT) $(TEST_PACKAGES) -timeout=3m

.PHONY: fmt
fmt:
@ go tool -modfile=tools/go.mod golangci-lint fmt

.PHONY: lint
lint:
@ go tool -modfile=tools/go.mod golangci-lint run

.PHONY: testacc
testacc: dev
PACKER_ACC=1 OXIDE_PROJECT=$(OXIDE_PROJECT) OXIDE_BOOT_DISK_IMAGE_NAME=$(OXIDE_BOOT_DISK_IMAGE_NAME) go test -count $(TEST_COUNT) -v $(TEST_PACKAGES) -timeout=120m
15 changes: 9 additions & 6 deletions component/builder/instance/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ const BuilderID = "oxide.instance"

var _ packer.Builder = (*Builder)(nil)

// The `oxide-instance` builder creates custom images for use with
// [Oxide](https://oxide.computer). The builder launches a temporary instance
// from an existing source image, connects to the instance using its external
// IP, provisions the instance, and then creates a new image from the instance's
// The `oxide-instance` builder creates custom images for use with [Oxide](https://oxide.computer).
// The builder launches a temporary instance from an existing source image, connects to the instance
// using its external IP, provisions the instance, and then creates a new image from the instance's
// boot disk. The resulting image can be used to launch new instances on Oxide.
//
// The builder does not manage images. Once it creates an image, it is up to you
Expand All @@ -51,7 +50,11 @@ func (b *Builder) Prepare(args ...any) ([]string, []string, error) {
}

// Run executes the builder steps to create an Oxide image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
func (b *Builder) Run(
ctx context.Context,
ui packer.Ui,
hook packer.Hook,
) (packer.Artifact, error) {
opts := make([]oxide.ClientOption, 0)
if b.config.Host != "" {
opts = append(opts, oxide.WithHost(b.config.Host))
Expand Down Expand Up @@ -79,7 +82,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&stepImageView{},
multistep.If(genTempSSHKeyPair, &communicator.StepSSHKeyGen{
CommConf: &b.config.Comm,
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
SSHTemporaryKeyPair: b.config.Comm.SSHTemporaryKeyPair,
}),
multistep.If(genTempSSHKeyPair, &stepSSHKeyCreate{}),
&stepInstanceCreate{},
Expand Down
14 changes: 5 additions & 9 deletions component/builder/instance/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"embed"
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
Expand Down Expand Up @@ -241,7 +240,10 @@ func TestAccBuilder_Instance(t *testing.T) {
Image: oxide.NameOrId(artifactName),
Project: oxide.NameOrId(oxideProject),
}); err != nil {
joinedError = errors.Join(joinedError, fmt.Errorf("failed deleting oxide image %s: %v", artifactName, err))
joinedError = errors.Join(
joinedError,
fmt.Errorf("failed deleting oxide image %s: %v", artifactName, err),
)
}

if joinedError != nil {
Expand All @@ -267,13 +269,7 @@ func TestAccBuilder_Instance(t *testing.T) {
}

func assertFileContains(t *testing.T, filename string, expected string) {
f, err := os.Open(filename)
if err != nil {
t.Fatal(err)
}
defer f.Close()

b, err := io.ReadAll(f)
b, err := os.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
Expand Down
23 changes: 17 additions & 6 deletions component/builder/instance/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import (
"github.com/mitchellh/mapstructure"
)

// The configuration arguments for the builder. Arguments can either be required
// or optional.
// The configuration arguments for the builder. Arguments can either be required or optional.
type Config struct {
common.PackerConfig `mapstructure:",squash"`

Expand Down Expand Up @@ -118,7 +117,10 @@ func (c *Config) Prepare(args ...any) ([]string, error) {
if c.Name == "" {
name, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
return nil, fmt.Errorf("failed rendering default name, this bug should be reported: %w", err)
return nil, fmt.Errorf(
"failed rendering default name, this bug should be reported: %w",
err,
)
}

c.Name = name
Expand All @@ -127,7 +129,10 @@ func (c *Config) Prepare(args ...any) ([]string, error) {
if c.Hostname == "" {
hostname, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
return nil, fmt.Errorf("failed rendering default hostname, this bug should be reported: %w", err)
return nil, fmt.Errorf(
"failed rendering default hostname, this bug should be reported: %w",
err,
)
}

c.Hostname = hostname
Expand Down Expand Up @@ -165,7 +170,10 @@ func (c *Config) Prepare(args ...any) ([]string, error) {
if c.Comm.SSHTemporaryKeyPairName == "" {
sshTemporaryKeyPairName, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
return nil, fmt.Errorf("failed rendering default ssh temporary key pair name, this bug should be reported: %w", err)
return nil, fmt.Errorf(
"failed rendering default ssh temporary key pair name, this bug should be reported: %w",
err,
)
}

c.Comm.SSHTemporaryKeyPairName = sshTemporaryKeyPairName
Expand All @@ -178,7 +186,10 @@ func (c *Config) Prepare(args ...any) ([]string, error) {
}

if c.BootDiskImageID == "" {
multiErr = packer.MultiErrorAppend(multiErr, errors.New("boot_disk_image_id is required"))
multiErr = packer.MultiErrorAppend(
multiErr,
errors.New("boot_disk_image_id is required"),
)
}

if multiErr != nil && len(multiErr.Errors) > 0 {
Expand Down
5 changes: 4 additions & 1 deletion component/builder/instance/step_image_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ var _ multistep.Step = (*stepImageCreate)(nil)
type stepImageCreate struct{}

// Run creates an Oxide image and stores its information in stateBag.
func (s *stepImageCreate) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
func (s *stepImageCreate) Run(
ctx context.Context,
stateBag multistep.StateBag,
) multistep.StepAction {
oxideClient := stateBag.Get("client").(*oxide.Client)
ui := stateBag.Get("ui").(packer.Ui)
config := stateBag.Get("config").(*Config)
Expand Down
37 changes: 30 additions & 7 deletions component/builder/instance/step_instance_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ var _ multistep.Step = (*stepInstanceCreate)(nil)
type stepInstanceCreate struct{}

// Run creates an Oxide instance and stores its information in stateBag.
func (o *stepInstanceCreate) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
func (o *stepInstanceCreate) Run(
ctx context.Context,
stateBag multistep.StateBag,
) multistep.StepAction {
oxideClient := stateBag.Get("client").(*oxide.Client)
ui := stateBag.Get("ui").(packer.Ui)
config := stateBag.Get("config").(*Config)
Expand Down Expand Up @@ -159,7 +162,10 @@ func (o *stepInstanceCreate) Cleanup(stateBag multistep.StateBag) {

ui.Sayf("Checking if Oxide instance is stopped: %s", instanceID)

instanceStopCtx, instanceStopCtxCancel := context.WithTimeout(context.TODO(), 30*time.Second)
instanceStopCtx, instanceStopCtxCancel := context.WithTimeout(
context.TODO(),
30*time.Second,
)
defer instanceStopCtxCancel()

instance, err := oxideClient.InstanceView(instanceStopCtx, oxide.InstanceViewParams{
Expand All @@ -179,7 +185,10 @@ func (o *stepInstanceCreate) Cleanup(stateBag multistep.StateBag) {
if _, err := oxideClient.InstanceStop(instanceStopCtx, oxide.InstanceStopParams{
Instance: oxide.NameOrId(instanceID),
}); err != nil {
ui.Errorf("Failed stopping Oxide instance during cleanup. Please delete it manually: %v", err)
ui.Errorf(
"Failed stopping Oxide instance during cleanup. Please delete it manually: %v",
err,
)
return
}

Expand All @@ -204,20 +213,31 @@ func (o *stepInstanceCreate) Cleanup(stateBag multistep.StateBag) {
break
}

ui.Say(fmt.Sprintf("Waiting for instance to stop. Instance is currently %s.", instance.RunState))
ui.Say(
fmt.Sprintf(
"Waiting for instance to stop. Instance is currently %s.",
instance.RunState,
),
)
time.Sleep(3 * time.Second)
}

deleteInstance:
ui.Sayf("Deleting Oxide instance: %s", instanceID)

instanceDeleteCtx, instanceDeleteCtxCancel := context.WithTimeout(context.TODO(), 30*time.Second)
instanceDeleteCtx, instanceDeleteCtxCancel := context.WithTimeout(
context.TODO(),
30*time.Second,
)
defer instanceDeleteCtxCancel()

if err := oxideClient.InstanceDelete(instanceDeleteCtx, oxide.InstanceDeleteParams{
Instance: oxide.NameOrId(instanceID),
}); err != nil {
ui.Errorf("Failed deleting Oxide instance during cleanup. Please delete it manually: %v", err)
ui.Errorf(
"Failed deleting Oxide instance during cleanup. Please delete it manually: %v",
err,
)
return
}
}
Expand All @@ -233,7 +253,10 @@ func (o *stepInstanceCreate) Cleanup(stateBag multistep.StateBag) {
if err := oxideClient.DiskDelete(diskDeleteCtx, oxide.DiskDeleteParams{
Disk: oxide.NameOrId(bootDiskID),
}); err != nil {
ui.Errorf("Failed deleting Oxide disk during cleanup. Please delete it manually: %v", err)
ui.Errorf(
"Failed deleting Oxide disk during cleanup. Please delete it manually: %v",
err,
)
return
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ type stepInstanceExternalIPList struct{}

// Run lists the external IP addresses for an Oxide instance and stores its
// information in stateBag.
func (s *stepInstanceExternalIPList) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
func (s *stepInstanceExternalIPList) Run(
ctx context.Context,
stateBag multistep.StateBag,
) multistep.StepAction {
oxideClient := stateBag.Get("client").(*oxide.Client)
ui := stateBag.Get("ui").(packer.Ui)

Expand Down Expand Up @@ -52,7 +55,9 @@ func (s *stepInstanceExternalIPList) Run(ctx context.Context, stateBag multistep
}

if len(validExternalIPs) == 0 {
ui.Error("Instance does not have any valid external IPs. Packer will be unable to connect to this instance.")
ui.Error(
"Instance does not have any valid external IPs. Packer will be unable to connect to this instance.",
)
return multistep.ActionHalt
}

Expand Down
Loading