Skip to content

Commit

Permalink
feat: Add HCP Packer support
Browse files Browse the repository at this point in the history
Co-authored-by: Jonas Lammler <[email protected]>
Co-authored-by: Julian Tölle <[email protected]>
  • Loading branch information
3 people committed May 29, 2024
1 parent e9a3916 commit 0a39933
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 8 deletions.
28 changes: 28 additions & 0 deletions builder/hcloud/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"log"
"strconv"

registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"

"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

Expand Down Expand Up @@ -44,9 +46,35 @@ func (a *Artifact) String() string {
}

func (a *Artifact) State(name string) interface{} {
if name == registryimage.ArtifactStateURI {
return a.stateHCPPackerRegistryMetadata()
}
return a.StateData[name]
}

func (a *Artifact) stateHCPPackerRegistryMetadata() interface{} {
labels := make(map[string]string)

// Those labels contains the value the user specified in their template
sourceImage, ok := a.StateData["source_image"].(string)
if ok {
labels["source_image"] = sourceImage
}
serverType, ok := a.StateData["server_type"].(string)
if ok {
labels["server_type"] = serverType
}

sourceImageID := a.StateData["source_image_id"].(int64)

return &registryimage.Image{
ImageID: a.Id(),
ProviderName: "hetznercloud", // Use explicit name over the builder ID
Labels: labels,
SourceImageID: strconv.FormatInt(sourceImageID, 10),
}
}

func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
_, err := a.hcloudClient.Image.Delete(context.TODO(), &hcloud.Image{ID: a.snapshotId})
Expand Down
44 changes: 44 additions & 0 deletions builder/hcloud/artifact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
package hcloud

import (
"reflect"
"testing"

packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
"github.com/mitchellh/mapstructure"
)

func TestArtifact_Impl(t *testing.T) {
Expand Down Expand Up @@ -58,3 +61,44 @@ func TestArtifactState_StateData(t *testing.T) {
t.Fatalf("Bad: State should be nil for nil StateData")
}
}

func TestArtifactState_hcpPackerRegistryMetadata(t *testing.T) {
region := "nbg1"
artifact := &Artifact{
snapshotId: 1111111111,
snapshotName: "test-image",
StateData: map[string]interface{}{
"source_image": "ubuntu-22.04",
"region": region,
"server_type": "cx11",
},
}
// result should contain "something"
result := artifact.State(registryimage.ArtifactStateURI)
if result == nil {
t.Fatalf("Bad: HCP Packer registry image data was nil")
}

// check for proper decoding of result into slice of registryimage.Image
var image registryimage.Image
err := mapstructure.Decode(result, &image)
if err != nil {
t.Errorf("Bad: unexpected error when trying to decode state into registryimage.Image %v", err)
}

// check that all properties of the images were set correctly
expected := registryimage.Image{
ImageID: "1111111111",
ProviderName: "hetznercloud",
ProviderRegion: region,
SourceImageID: "ubuntu-22.04",
Labels: map[string]string{
"source_image": "ubuntu-22.04",
"region": region,
"server_type": "cx11",
},
}
if !reflect.DeepEqual(image, expected) {
t.Fatalf("Bad: expected %#v got %#v", expected, image)
}
}
7 changes: 6 additions & 1 deletion builder/hcloud/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
snapshotName: state.Get(StateSnapshotName).(string),
snapshotId: state.Get(StateSnapshotID).(int64),
hcloudClient: b.hcloudClient,
StateData: map[string]interface{}{"generated_data": state.Get(StateGeneratedData)},
StateData: map[string]interface{}{
"generated_data": state.Get(StateGeneratedData),
"source_image": b.config.Image,
"region": b.config.Location,
"server_type": b.config.ServerType,
},
}

return artifact, nil
Expand Down
2 changes: 2 additions & 0 deletions builder/hcloud/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (
StateSnapshotIDOld = "snapshot_id_old"
StateSnapshotName = "snapshot_name"
StateSSHKeyID = "ssh_key_id"

StateSourceImageID = "source_image_id"
)

func UnpackState(state multistep.StateBag) (*Config, packersdk.Ui, *hcloud.Client) {
Expand Down
12 changes: 8 additions & 4 deletions builder/hcloud/step_create_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
c, ui, client := UnpackState(state)

sshKeyId := state.Get(StateSSHKeyID).(int64)
serverType := state.Get(StateServerType).(*hcloud.ServerType)

// Create the server based on configuration
ui.Say("Creating server...")
Expand Down Expand Up @@ -50,17 +51,20 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
}

var image *hcloud.Image
var err error
if c.Image != "" {
image = &hcloud.Image{Name: c.Image}
image, _, err = client.Image.GetForArchitecture(ctx, c.Image, serverType.Architecture)
if err != nil {
return errorHandler(state, ui, "Could not find image", err)
}
} else {
serverType := state.Get(StateServerType).(*hcloud.ServerType)
var err error
image, err = getImageWithSelectors(ctx, client, c, serverType)
if err != nil {
return errorHandler(state, ui, "Could not find image", err)
}
ui.Message(fmt.Sprintf("Using image %s with ID %d", image.Description, image.ID))
}
ui.Message(fmt.Sprintf("Using image '%d'", image.ID))
state.Put(StateSourceImageID, image.ID)

var networks []*hcloud.Network
for _, k := range c.Networks {
Expand Down
23 changes: 21 additions & 2 deletions builder/hcloud/step_create_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/stretchr/testify/assert"

"github.com/hetznercloud/hcloud-go/v2/hcloud"
"github.com/hetznercloud/hcloud-go/v2/hcloud/schema"
)

Expand All @@ -21,19 +22,25 @@ func TestStepCreateServer(t *testing.T) {
Step: &stepCreateServer{},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
200, `{
"ssh_key": { "id": 1 }
}`,
},
{"GET", "/images?architecture=x86&include_deprecated=true&name=debian-12", nil,
200, `{
"images": [{ "id": 114690387, "name": "debian-12", "description": "Debian 12", "architecture": "x86" }]
}`,
},
{"POST", "/servers",
func(t *testing.T, r *http.Request, body []byte) {
payload := schema.ServerCreateRequest{}
assert.NoError(t, json.Unmarshal(body, &payload))
assert.Equal(t, "dummy-server", payload.Name)
assert.Equal(t, "debian-12", payload.Image)
assert.Equal(t, int64(114690387), int64(payload.Image.(float64)))
assert.Equal(t, "nbg1", payload.Location)
assert.Equal(t, "cpx11", payload.ServerType)
assert.Nil(t, payload.Networks)
Expand Down Expand Up @@ -76,19 +83,25 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
200, `{
"ssh_key": { "id": 1 }
}`,
},
{"GET", "/images?architecture=x86&include_deprecated=true&name=debian-12", nil,
200, `{
"images": [{ "id": 114690387, "name": "debian-12", "description": "Debian 12", "architecture": "x86" }]
}`,
},
{"POST", "/servers",
func(t *testing.T, r *http.Request, body []byte) {
payload := schema.ServerCreateRequest{}
assert.NoError(t, json.Unmarshal(body, &payload))
assert.Equal(t, "dummy-server", payload.Name)
assert.Equal(t, "debian-12", payload.Image)
assert.Equal(t, int64(114690387), int64(payload.Image.(float64)))
assert.Equal(t, "nbg1", payload.Location)
assert.Equal(t, "cpx11", payload.ServerType)
assert.Equal(t, []int64{12}, payload.Networks)
Expand Down Expand Up @@ -132,6 +145,7 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
Expand Down Expand Up @@ -214,6 +228,7 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
Expand Down Expand Up @@ -299,6 +314,7 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
Expand Down Expand Up @@ -329,6 +345,7 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
Expand Down Expand Up @@ -359,6 +376,7 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
Expand Down Expand Up @@ -389,6 +407,7 @@ func TestStepCreateServer(t *testing.T) {
},
SetupStateFunc: func(state multistep.StateBag) {
state.Put(StateSSHKeyID, int64(1))
state.Put(StateServerType, &hcloud.ServerType{ID: 9, Name: "cpx11", Architecture: "x86"})
},
WantRequests: []Request{
{"GET", "/ssh_keys/1", nil,
Expand Down
2 changes: 1 addition & 1 deletion builder/hcloud/step_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func NewTestServer(t *testing.T, requests []Request) *httptest.Server {

return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if testing.Verbose() {
t.Logf("request %d: %s %s\n", index, r.Method, r.URL.Path)
t.Logf("request %d: %s %s\n", index, r.Method, r.RequestURI)
}

if index >= len(requests) {
Expand Down
53 changes: 53 additions & 0 deletions example/build_hcp.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

packer {
required_plugins {
hcloud = {
source = "github.com/hetznercloud/hcloud"
version = ">=1.1.0"
}
}
}

variable "hcloud_token" {
type = string
sensitive = true
default = "${env("HCLOUD_TOKEN")}"
}

source "hcloud" "example" {
token = var.hcloud_token

location = "hel1"
image = "ubuntu-24.04"
server_type = "cpx11"
server_name = "hcloud-example"

ssh_username = "root"

snapshot_name = "hcloud-example"
snapshot_labels = {
app = "hcloud-example"
}
}

build {
hcp_packer_registry {
description = "A nice test description"
bucket_name = "hcloud-hcp-test"
bucket_labels = {
"packer version" = packer.version
}
}

sources = ["source.hcloud.example"]

provisioner "shell" {
inline = ["cloud-init status --wait || test $? -eq 2"]
}

provisioner "shell" {
inline = ["echo 'Hello World!' > /var/log/packer.log"]
}
}

0 comments on commit 0a39933

Please sign in to comment.