Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add HCP packer support #122

Merged
merged 5 commits into from
Jun 4, 2024
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
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,6 @@ jobs:
with:
version: "1.10.0" # renovate: datasource=github-releases depName=hashicorp/packer extractVersion=v(?<version>.+)

- run: packer init .
- run: packer validate .
- run: packer build .
- run: packer init build.pkr.hcl
- run: packer validate build.pkr.hcl
- run: packer build build.pkr.hcl
6 changes: 6 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ linters:
- unparam
- unused
- whitespace

issues:
exclude-rules:
- path: _test\.go
linters:
- dupl
32 changes: 32 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,39 @@ 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
}

img := &registryimage.Image{
ImageID: a.Id(),
ProviderName: "hetznercloud", // Use explicit name over the builder ID
Labels: labels,
}

sourceImageID, ok := a.StateData["source_image_id"].(int64)
if ok {
img.SourceImageID = strconv.FormatInt(sourceImageID, 10)
}

return img
}

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
34 changes: 34 additions & 0 deletions builder/hcloud/artifact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
"testing"

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

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

func TestArtifactState_hcpPackerRegistryMetadata(t *testing.T) {
artifact := &Artifact{
snapshotId: 167438588,
snapshotName: "test-image",
StateData: map[string]interface{}{
"source_image": "ubuntu-24.04",
"source_image_id": int64(161547269),
"server_type": "cpx11",
},
}

result := artifact.State(registryimage.ArtifactStateURI)
require.NotNil(t, result)

var image registryimage.Image
if err := mapstructure.Decode(result, &image); err != nil {
t.Errorf("unexpected error when trying to decode state into registryimage.Image %v", err)
}

assert.Equal(t, registryimage.Image{
ImageID: "167438588",
ProviderName: "hetznercloud",
SourceImageID: "161547269",
Labels: map[string]string{
"source_image": "ubuntu-24.04",
"server_type": "cpx11",
},
}, 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,
"source_image_id": state.Get(StateSourceImageID),
"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
57 changes: 53 additions & 4 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,13 +145,19 @@ 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" }]
}`,
},
{"GET", "/primary_ips?name=permanent-packer-ipv4", nil,
200, `{
"primary_ips": [
Expand Down Expand Up @@ -168,7 +187,7 @@ func TestStepCreateServer(t *testing.T) {
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 @@ -214,13 +233,19 @@ 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" }]
}`,
},
{"GET", "/primary_ips?name=127.0.0.1", nil,
200, `{ "primary_ips": [] }`,
},
Expand Down Expand Up @@ -254,7 +279,7 @@ func TestStepCreateServer(t *testing.T) {
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 @@ -299,13 +324,19 @@ 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" }]
}`,
},
{"GET", "/primary_ips?name=127.0.0.1", nil,
200, `{ "primary_ips": [] }`,
},
Expand All @@ -329,13 +360,19 @@ 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" }]
}`,
},
{"GET", "/primary_ips?name=127.0.0.1", nil,
200, `{ "primary_ips": [] }`,
},
Expand All @@ -359,13 +396,19 @@ 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" }]
}`,
},
{"GET", "/primary_ips?name=127.0.0.1", nil,
200, `{ "primary_ips": [] }`,
},
Expand All @@ -389,13 +432,19 @@ 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" }]
}`,
},
{"GET", "/primary_ips?name=127.0.0.1", nil,
200, `{ "primary_ips": [] }`,
},
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
Loading