From a199cb06d29e76d2aec6f61eae0af1e9857b4e89 Mon Sep 17 00:00:00 2001 From: Michael Riley Date: Mon, 6 Jun 2022 16:42:35 -0400 Subject: [PATCH 1/2] Add artifact support for HCP Packer registry Working off of the official documentation (https://www.packer.io/docs/plugins/hcp-support). This should now allow utilization of the hcp_packer_registry block (https://www.packer.io/docs/templates/hcl_templates/blocks/build/hcp_packer_registry) and support adding Vultr snapshots to the HCP Packer registry. --- builder/vultr/artifact.go | 25 ++- builder/vultr/builder.go | 1 + .../packer/registry/image/image.go | 169 ++++++++++++++++++ vendor/modules.txt | 1 + 4 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/hashicorp/packer-plugin-sdk/packer/registry/image/image.go diff --git a/builder/vultr/artifact.go b/builder/vultr/artifact.go index 0603370b..3b29401b 100644 --- a/builder/vultr/artifact.go +++ b/builder/vultr/artifact.go @@ -5,6 +5,8 @@ import ( "fmt" "log" + registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" + "github.com/vultr/govultr/v2" ) @@ -15,8 +17,14 @@ type Artifact struct { // The Description of the snapshot Description string - // The client for making + // The client for making changes client *govultr.Client + + // config definition from the builder + config *Config + + // State data used by HCP container registry + StateData map[string]interface{} } func (a *Artifact) BuilderId() string { @@ -36,7 +44,20 @@ func (a *Artifact) String() string { } func (a *Artifact) State(name string) interface{} { - return nil + if name == registryimage.ArtifactStateURI { + img, err := registryimage.FromArtifact(a, + registryimage.WithID(a.SnapshotID), + registryimage.WithProvider("Vultr"), + registryimage.WithRegion(a.config.RegionID), + ) + + if err != nil { + log.Printf("[DEBUG] error encountered when creating a registry image %v", err) + return nil + } + return img + } + return a.StateData[name] } func (a *Artifact) Destroy() error { diff --git a/builder/vultr/builder.go b/builder/vultr/builder.go index 3e987774..9e4c1082 100644 --- a/builder/vultr/builder.go +++ b/builder/vultr/builder.go @@ -85,6 +85,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (ret artifact := &Artifact{ SnapshotID: snapshot.ID, Description: snapshot.Description, + config: &b.config, client: client, } diff --git a/vendor/github.com/hashicorp/packer-plugin-sdk/packer/registry/image/image.go b/vendor/github.com/hashicorp/packer-plugin-sdk/packer/registry/image/image.go new file mode 100644 index 00000000..9c86e16b --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-sdk/packer/registry/image/image.go @@ -0,0 +1,169 @@ +/* Package image allows for the management of image metadata that can be stored in a HCP Packer registry. + */ +package image + +import ( + "errors" + "fmt" + "reflect" + + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// ArtifactStateURI represents the key used by Packer when querying a packersdk.Artifact +// for Image metadata that a particular component would like to have stored on the HCP Packer Registry. +const ArtifactStateURI = "par.artifact.metadata" + +// ArtifactOverrideFunc represents a transformation func that can be applied to a +// non-nil *Image. See WithID, WithRegion functions for examples. +type ArtifactOverrideFunc func(*Image) error + +// Image represents the metadata for some Artifact in the HCP Packer Registry. +type Image struct { + // ImageID is a unique reference identifier stored on the HCP Packer registry + // that can be used to get back the built artifact of a builder or post-processor. + ImageID string + // ProviderName represents the name of the top level cloud or service where the built artifact resides. + // For example "aws, azure, docker, gcp, and vsphere". + ProviderName string + // ProviderRegion represents the location of the built artifact. + // For cloud providers region usually maps to a cloud region or zone, but for things like the file builder, + // S3 bucket or vsphere cluster region can represent a path on the upstream datastore, or cluster. + ProviderRegion string + // Labels represents additional details about an image that a builder or post-processor may with to provide for a given build. + // Any additional metadata will be made available as build labels within a HCP Packer registry iteration. + Labels map[string]string + // SourceImageID is the cloud image id of the image that was used as the + // source for this image. If set, the HCP Packer registry will be able + // link the parent and child images for ancestry visualizations and + // dependency tracking. + SourceImageID string +} + +// Validate checks that the Image i contains a non-empty ImageID and ProviderName. +func (i *Image) Validate() error { + if i.ImageID == "" { + return errors.New("error registry image does not contain a valid ImageId") + } + + if i.ProviderName == "" { + return errors.New("error registry image does not contain a valid ProviderName") + } + + return nil +} + +// String returns string representation of Image +func (i *Image) String() string { + return fmt.Sprintf("provider:%s, image:%s, region:%s", i.ProviderName, i.ImageID, i.ProviderRegion) +} + +// FromMappedData calls f sequentially for each key and value present in mappedData to create a []*Image +// as the final return value. If there is an error in calling f, FromMappedData will stop processing mapped items +// and result in a nil slice, with the said error. +// +// FromMappedData will make its best attempt to convert the input map into map[interface{}]interface{} before +// calling f(k,v). The func f is responsible for type asserting the expected type for the key and value before +// trying to create an Image from it. +func FromMappedData(mappedData interface{}, f func(key, value interface{}) (*Image, error)) ([]*Image, error) { + mapValue := reflect.ValueOf(mappedData) + if mapValue.Kind() != reflect.Map { + return nil, errors.New("error the incoming mappedData does not appear to be a map; found type to be" + mapValue.Kind().String()) + } + + keys := mapValue.MapKeys() + var images []*Image + for _, k := range keys { + v := mapValue.MapIndex(k) + i, err := f(k.Interface(), v.Interface()) + if err != nil { + return nil, err + } + images = append(images, i) + } + return images, nil +} + +// FromArtifact returns an *Image that can be used by Packer core for publishing to the HCP Packer Registry. +// By default FromArtifact will use the a.BuilderID() as the ProviderName, and the a.Id() as the ImageID that +// should be tracked within the HCP Packer Registry. No Region is selected by default as region varies per builder. +// The use of one or more ArtifactOverrideFunc can be used to override any of the defaults used. +func FromArtifact(a packer.Artifact, opts ...ArtifactOverrideFunc) (*Image, error) { + if a == nil { + return nil, errors.New("unable to create Image from nil artifact") + } + + img := Image{ + ProviderName: a.BuilderId(), + ImageID: a.Id(), + Labels: make(map[string]string), + } + + for _, opt := range opts { + err := opt(&img) + if err != nil { + return nil, err + } + } + + return &img, nil +} + +// WithProvider takes a name, and returns a ArtifactOverrideFunc that can be +// used to override the ProviderName for an existing Image. +func WithProvider(name string) func(*Image) error { + return func(img *Image) error { + img.ProviderName = name + return nil + } +} + +// WithID takes a id, and returns a ArtifactOverrideFunc that can be +// used to override the ImageId for an existing Image. +func WithID(id string) func(*Image) error { + return func(img *Image) error { + img.ImageID = id + return nil + } +} + +// WithSourceID takes a id, and returns a ArtifactOverrideFunc that can be +// used to override the SourceImageId for an existing Image. +func WithSourceID(id string) func(*Image) error { + return func(img *Image) error { + img.SourceImageID = id + return nil + } +} + +// WithRegion takes a region, and returns a ArtifactOverrideFunc that can be +// used to override the ProviderRegion for an existing Image. +func WithRegion(region string) func(*Image) error { + return func(img *Image) error { + img.ProviderRegion = region + return nil + } +} + +// SetLabels takes metadata, and returns a ArtifactOverrideFunc that can be +// used to set metadata for an existing Image. The incoming metadata `md` +// will be filtered only for keys whose values are of type string. +// If you wish to override this behavior you may create your own ArtifactOverrideFunc +// for manipulating and setting Image metadata. +func SetLabels(md map[string]interface{}) func(*Image) error { + return func(img *Image) error { + if img.Labels == nil { + img.Labels = make(map[string]string) + } + + for k, v := range md { + v, ok := v.(string) + if !ok { + continue + } + img.Labels[k] = v + } + + return nil + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5c53ba35..fa3bc07d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -216,6 +216,7 @@ github.com/hashicorp/packer-plugin-sdk/multistep github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps github.com/hashicorp/packer-plugin-sdk/net github.com/hashicorp/packer-plugin-sdk/packer +github.com/hashicorp/packer-plugin-sdk/packer/registry/image github.com/hashicorp/packer-plugin-sdk/packerbuilderdata github.com/hashicorp/packer-plugin-sdk/pathing github.com/hashicorp/packer-plugin-sdk/plugin From 4dcd1e78ae71a7cf8f1bb01866b913af62acdab5 Mon Sep 17 00:00:00 2001 From: Michael Riley Date: Mon, 6 Jun 2022 17:09:06 -0400 Subject: [PATCH 2/2] Make artifact tests pass --- builder/vultr/artifact_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 builder/vultr/artifact_test.go diff --git a/builder/vultr/artifact_test.go b/builder/vultr/artifact_test.go old mode 100644 new mode 100755 index 50f12df6..d613585a --- a/builder/vultr/artifact_test.go +++ b/builder/vultr/artifact_test.go @@ -15,7 +15,7 @@ func TestArtifact_Impl(t *testing.T) { } func TestArtifactId(t *testing.T) { - a := &Artifact{"d455d0246e8e6", "packer-test", nil} + a := &Artifact{"d455d0246e8e6", "packer-test", nil, nil, nil} expected := "d455d0246e8e6" if a.Id() != expected { @@ -24,7 +24,7 @@ func TestArtifactId(t *testing.T) { } func TestArtifactString(t *testing.T) { - a := &Artifact{"d455d0246e8e6", "packer-test", nil} + a := &Artifact{"d455d0246e8e6", "packer-test", nil, nil, nil} expected := "Vultr Snapshot: packer-test (d455d0246e8e6)" if a.String() != expected {