Skip to content

Commit bb726f8

Browse files
author
Wilken Rivera
authored
Add registry image pkg (#76)
* Add registryimage pkg In order to assist in the creation of HCP Packer Registry Image metadata for various Artifacts. A new package is being added to assist in the creation of a registryimage.Image from an existing Artifact. The FromArtifact function provides a simple approach of extracting the required bits from an Artifact needed by the HCP Packer registry. The default values, which can be overwritten by the use of a ArtifactOverrideFunc , were selected to provide some standard defaults that are set to be available for most plugins were bundled with Packer prior to v1.7.0. When those defaults are not viable the package provider override funcs that can be used by the consumer for setting the appropriate values. For those plugins that generate more than one registryimage.Image per Artifact one can construct a []registryimage.Image and return that information as the response of Packer core calling artifact.State(registryimage.ArtifactStateURI). * Add FromMappedData func * Move pkg around to remove a bit of the stutter. * Add some example tests for a few of the functions provided by the package. * Init map if nil * Add validate method to Image type * update go mod files * Add stringer for image * Drop mapstructure tags * Bump go modules
1 parent eb221dc commit bb726f8

6 files changed

Lines changed: 456 additions & 7 deletions

File tree

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,10 @@ require (
4242
github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493
4343
github.com/imdario/mergo v0.3.12 // indirect
4444
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
45-
github.com/kr/fs v0.1.0 // indirect
4645
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
4746
github.com/masterzen/winrm v0.0.0-20210623064412-3b76017826b0
48-
github.com/mattn/go-colorable v0.1.8 // indirect
4947
github.com/mattn/go-isatty v0.0.13 // indirect
5048
github.com/mitchellh/cli v1.1.2
51-
github.com/mitchellh/copystructure v1.0.0 // indirect
5249
github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff
5350
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
5451
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@@ -99,6 +96,9 @@ require (
9996
github.com/hashicorp/vault/sdk v0.2.1 // indirect
10097
github.com/huandu/xstrings v1.3.2 // indirect
10198
github.com/jmespath/go-jmespath v0.4.0 // indirect
99+
github.com/kr/fs v0.1.0 // indirect
100+
github.com/mattn/go-colorable v0.1.8 // indirect
101+
github.com/mitchellh/copystructure v1.0.0 // indirect
102102
github.com/mitchellh/go-homedir v1.1.0 // indirect
103103
github.com/pmezard/go-difflib v1.0.0 // indirect
104104
github.com/posener/complete v1.2.3 // indirect

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,6 @@ github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw=
433433
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
434434
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
435435
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
436-
github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0=
437-
github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
438436
github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff h1:bFJ74ac7ZK/jyislqiWdzrnENesFt43sNEBRh1xk/+g=
439437
github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU=
440438
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -457,8 +455,6 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd
457455
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
458456
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
459457
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
460-
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
461-
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
462458
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
463459
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
464460
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package image_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
7+
)
8+
9+
type simpleArtifact struct {
10+
image_id string
11+
}
12+
13+
func (a *simpleArtifact) BuilderId() string {
14+
return "example.happycloud"
15+
}
16+
17+
func (a *simpleArtifact) Files() []string {
18+
return nil
19+
}
20+
21+
func (a *simpleArtifact) Id() string {
22+
return a.image_id
23+
}
24+
25+
func (a *simpleArtifact) String() string {
26+
return fmt.Sprintf("Imported image URL: %s", a.Id())
27+
}
28+
29+
func (a *simpleArtifact) State(name string) interface{} {
30+
return nil
31+
}
32+
33+
func (a *simpleArtifact) Destroy() error {
34+
return nil
35+
}
36+
37+
func ExampleFromArtifact() {
38+
39+
a := &simpleArtifact{
40+
image_id: "service-id-123",
41+
}
42+
43+
hcimage, _ := image.FromArtifact(a)
44+
fmt.Printf("%#v", *hcimage)
45+
// Output:
46+
// image.Image{ImageID:"service-id-123", ProviderName:"example.happycloud", ProviderRegion:"", Labels:map[string]string{}}
47+
}
48+
49+
func ExampleWithProvider() {
50+
a := &simpleArtifact{
51+
image_id: "service-id-123",
52+
}
53+
54+
// This example also includes an override for the ProviderRegion to illustrate how ArtifactOverrideFunc(s) can be chained.
55+
hcimage, _ := image.FromArtifact(a, image.WithProvider("happycloud"), image.WithRegion("west"))
56+
fmt.Printf("%#v", *hcimage)
57+
// Output:
58+
// image.Image{ImageID:"service-id-123", ProviderName:"happycloud", ProviderRegion:"west", Labels:map[string]string{}}
59+
}
60+
61+
func ExampleSetLabels() {
62+
a := &simpleArtifact{
63+
image_id: "service-id-123",
64+
}
65+
66+
hcimage, _ := image.FromArtifact(a, image.SetLabels(map[string]interface{}{"kernel": "4.0", "python": "3.5"}))
67+
fmt.Printf("%v", hcimage.Labels)
68+
// Unordered output:
69+
// map[kernel:4.0 python:3.5]
70+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package image_test
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"sort"
7+
"strings"
8+
9+
"github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
10+
)
11+
12+
type artifact struct {
13+
images map[string]string
14+
}
15+
16+
func (a *artifact) BuilderId() string {
17+
return "example.happycloud"
18+
}
19+
20+
func (a *artifact) Files() []string {
21+
return nil
22+
}
23+
24+
func (a *artifact) Id() string {
25+
parts := make([]string, 0, len(a.images))
26+
for loc, id := range a.images {
27+
parts = append(parts, loc+":"+id)
28+
}
29+
sort.Strings(parts)
30+
return strings.Join(parts, ",")
31+
}
32+
33+
func (a *artifact) String() string {
34+
return a.Id()
35+
}
36+
37+
func (a *artifact) State(name string) interface{} {
38+
return nil
39+
}
40+
41+
func (a *artifact) Destroy() error {
42+
return nil
43+
}
44+
45+
func ExampleFromMappedData() {
46+
a := &artifact{
47+
images: map[string]string{
48+
"west": "happycloud-1",
49+
"east": "happycloud-2",
50+
},
51+
}
52+
53+
f := func(key, value interface{}) (*image.Image, error) {
54+
v, ok := value.(string)
55+
if !ok {
56+
return nil, errors.New("for happycloud maps value should always be string")
57+
}
58+
k, ok := key.(string)
59+
if !ok {
60+
return nil, errors.New("for happycloud maps key should always be string")
61+
}
62+
63+
img := image.Image{ProviderName: "happycloud", ProviderRegion: k, ImageID: v}
64+
return &img, nil
65+
}
66+
67+
hcimages, _ := image.FromMappedData(a.images, f)
68+
for _, hcimage := range hcimages {
69+
fmt.Printf("%#v\n", *hcimage)
70+
}
71+
// Unordered output:
72+
// image.Image{ImageID:"happycloud-1", ProviderName:"happycloud", ProviderRegion:"west", Labels:map[string]string(nil)}
73+
// image.Image{ImageID:"happycloud-2", ProviderName:"happycloud", ProviderRegion:"east", Labels:map[string]string(nil)}
74+
}

packer/registry/image/image.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/* Package image allows for the management of image metadata that can be stored in a HCP Packer registry.
2+
*/
3+
package image
4+
5+
import (
6+
"errors"
7+
"fmt"
8+
"reflect"
9+
10+
"github.com/hashicorp/packer-plugin-sdk/packer"
11+
)
12+
13+
// ArtifactStateURI represents the key used by Packer when querying a packersdk.Artifact
14+
// for Image metadata that a particular component would like to have stored on the HCP Packer Registry.
15+
const ArtifactStateURI = "par.artifact.metadata"
16+
17+
// ArtifactOverrideFunc represents a transformation func that can be applied to a
18+
// non-nil *Image. See WithID, WithRegion functions for examples.
19+
type ArtifactOverrideFunc func(*Image) error
20+
21+
// Image represents the metadata for some Artifact in the HCP Packer Registry.
22+
type Image struct {
23+
// ImageID is a unique reference identifier stored on the HCP Packer registry
24+
// that can be used to get back the built artifact of a builder or post-processor.
25+
ImageID string
26+
// ProviderName represents the name of the top level cloud or service where the built artifact resides.
27+
// For example "aws, azure, docker, gcp, and vsphere".
28+
ProviderName string
29+
// ProviderRegion represents the location of the built artifact.
30+
// For cloud providers region usually maps to a cloud region or zone, but for things like the file builder,
31+
// S3 bucket or vsphere cluster region can represent a path on the upstream datastore, or cluster.
32+
ProviderRegion string
33+
// Labels represents additional details about an image that a builder or post-processor may with to provide for a given build.
34+
// Any additional metadata will be made available as build labels within a HCP Packer registry iteration.
35+
Labels map[string]string
36+
}
37+
38+
// Validate checks that the Image i contains a non-empty ImageID and ProviderName.
39+
func (i *Image) Validate() error {
40+
if i.ImageID == "" {
41+
return errors.New("error registry image does not contain a valid ImageId")
42+
}
43+
44+
if i.ProviderName == "" {
45+
return errors.New("error registry image does not contain a valid ProviderName")
46+
}
47+
48+
return nil
49+
}
50+
51+
// String returns string representation of Image
52+
func (i *Image) String() string {
53+
return fmt.Sprintf("provider:%s, image:%s, region:%s", i.ProviderName, i.ImageID, i.ProviderRegion)
54+
}
55+
56+
// FromMappedData calls f sequentially for each key and value present in mappedData to create a []*Image
57+
// as the final return value. If there is an error in calling f, FromMappedData will stop processing mapped items
58+
// and result in a nil slice, with the said error.
59+
//
60+
// FromMappedData will make its best attempt to convert the input map into map[interface{}]interface{} before
61+
// calling f(k,v). The func f is responsible for type asserting the expected type for the key and value before
62+
// trying to create an Image from it.
63+
func FromMappedData(mappedData interface{}, f func(key, value interface{}) (*Image, error)) ([]*Image, error) {
64+
65+
mapValue := reflect.ValueOf(mappedData)
66+
if mapValue.Kind() != reflect.Map {
67+
return nil, errors.New("error the incoming mappedData does not appear to be a map; found type to be" + mapValue.Kind().String())
68+
}
69+
70+
keys := mapValue.MapKeys()
71+
var images []*Image
72+
for _, k := range keys {
73+
v := mapValue.MapIndex(k)
74+
i, err := f(k.Interface(), v.Interface())
75+
if err != nil {
76+
return nil, err
77+
}
78+
images = append(images, i)
79+
}
80+
return images, nil
81+
}
82+
83+
// FromArtifact returns an *Image that can be used by Packer core for publishing to the HCP Packer Registry.
84+
// By default FromArtifact will use the a.BuilderID() as the ProviderName, and the a.Id() as the ImageID that
85+
// should be tracked within the HCP Packer Registry. No Region is selected by default as region varies per builder.
86+
// The use of one or more ArtifactOverrideFunc can be used to override any of the defaults used.
87+
func FromArtifact(a packer.Artifact, opts ...ArtifactOverrideFunc) (*Image, error) {
88+
if a == nil {
89+
return nil, errors.New("unable to create Image from nil artifact")
90+
}
91+
92+
img := Image{
93+
ProviderName: a.BuilderId(),
94+
ImageID: a.Id(),
95+
Labels: make(map[string]string),
96+
}
97+
98+
for _, opt := range opts {
99+
err := opt(&img)
100+
if err != nil {
101+
return nil, err
102+
}
103+
}
104+
105+
return &img, nil
106+
}
107+
108+
// WithProvider takes a name, and returns a ArtifactOverrideFunc that can be
109+
// used to override the ProviderName for an existing Image.
110+
func WithProvider(name string) func(*Image) error {
111+
return func(img *Image) error {
112+
if img == nil {
113+
return errors.New("no go on empty image")
114+
}
115+
116+
img.ProviderName = name
117+
return nil
118+
}
119+
}
120+
121+
// WithID takes a id, and returns a ArtifactOverrideFunc that can be
122+
// used to override the ImageId for an existing Image.
123+
func WithID(id string) func(*Image) error {
124+
return func(img *Image) error {
125+
if img == nil {
126+
return errors.New("no go on empty image")
127+
}
128+
129+
img.ImageID = id
130+
return nil
131+
}
132+
}
133+
134+
// WithRegion takes a region, and returns a ArtifactOverrideFunc that can be
135+
// used to override the ProviderRegion for an existing Image.
136+
func WithRegion(region string) func(*Image) error {
137+
return func(img *Image) error {
138+
if img == nil {
139+
return errors.New("no go on empty image")
140+
}
141+
142+
img.ProviderRegion = region
143+
return nil
144+
}
145+
}
146+
147+
// SetLabels takes metadata, and returns a ArtifactOverrideFunc that can be
148+
// used to set metadata for an existing Image. The incoming metadata `md`
149+
// will be filtered only for keys whose values are of type string.
150+
// If you wish to override this behavior you may create your own ArtifactOverrideFunc
151+
// for manipulating and setting Image metadata.
152+
func SetLabels(md map[string]interface{}) func(*Image) error {
153+
return func(img *Image) error {
154+
if img.Labels == nil {
155+
img.Labels = make(map[string]string)
156+
}
157+
158+
for k, v := range md {
159+
v, ok := v.(string)
160+
if !ok {
161+
continue
162+
}
163+
img.Labels[k] = v
164+
}
165+
166+
return nil
167+
}
168+
}

0 commit comments

Comments
 (0)