Skip to content

Commit d973e37

Browse files
author
Wilken Rivera
committed
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.
1 parent 6543148 commit d973e37

6 files changed

Lines changed: 437 additions & 187 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package image_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
7+
)
8+
9+
func ExampleFromArtifact() {
10+
11+
a := &simpleArtifact{
12+
image_id: "service-id-123",
13+
}
14+
15+
hcimage, _ := image.FromArtifact(a)
16+
fmt.Printf("%#v", *hcimage)
17+
// Output:
18+
// image.Image{ImageID:"service-id-123", ProviderName:"example.happycloud", ProviderRegion:"", Labels:map[string]string{}}
19+
}
20+
21+
func ExampleWithProvider() {
22+
a := &simpleArtifact{
23+
image_id: "service-id-123",
24+
}
25+
26+
// This example also includes an override for the ProviderRegion to illustrate how ArtifactOverrideFunc(s) can be chained.
27+
hcimage, _ := image.FromArtifact(a, image.WithProvider("happycloud"), image.WithRegion("west"))
28+
fmt.Printf("%#v", *hcimage)
29+
// Output:
30+
// image.Image{ImageID:"service-id-123", ProviderName:"happycloud", ProviderRegion:"west", Labels:map[string]string{}}
31+
}
32+
33+
func ExampleSetLabels() {
34+
a := &simpleArtifact{
35+
image_id: "service-id-123",
36+
}
37+
38+
// This example also includes an override for the ProviderRegion to illustrate how ArtifactOverrideFunc(s) can be chained.
39+
hcimage, _ := image.FromArtifact(a, image.SetLabels(map[string]interface{}{"kernel": "4.0", "python": "3.5"}))
40+
fmt.Printf("%v", hcimage.Labels)
41+
// Unordered output:
42+
// map[kernel:4.0 python:3.5]
43+
}
44+
45+
type simpleArtifact struct {
46+
image_id string
47+
}
48+
49+
func (a *simpleArtifact) BuilderId() string {
50+
return "example.happycloud"
51+
}
52+
53+
func (a *simpleArtifact) Files() []string {
54+
return nil
55+
}
56+
57+
func (a *simpleArtifact) Id() string {
58+
return a.image_id
59+
}
60+
61+
func (a *simpleArtifact) String() string {
62+
return fmt.Sprintf("Imported image URL: %s", a.Id())
63+
}
64+
65+
func (a *simpleArtifact) State(name string) interface{} {
66+
return nil
67+
}
68+
69+
func (a *simpleArtifact) Destroy() error {
70+
return nil
71+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
func ExampleFromMappedData() {
13+
14+
a := &artifact{
15+
images: map[string]string{
16+
"west": "happycloud-1",
17+
"east": "happycloud-2",
18+
},
19+
}
20+
21+
f := func(key, value interface{}) (*image.Image, error) {
22+
v, ok := value.(string)
23+
if !ok {
24+
return nil, errors.New("for happycloud maps value should always be string")
25+
}
26+
k, ok := key.(string)
27+
if !ok {
28+
return nil, errors.New("for happycloud maps key should always be string")
29+
}
30+
31+
img := image.Image{ProviderName: "happycloud", ProviderRegion: k, ImageID: v}
32+
return &img, nil
33+
}
34+
35+
hcimages, _ := image.FromMappedData(a.images, f)
36+
fmt.Println("Image count:", len(hcimages))
37+
for _, hcimage := range hcimages {
38+
fmt.Printf("%#v\n", *hcimage)
39+
}
40+
// Unordered output:
41+
// Image count: 2
42+
// image.Image{ImageID:"happycloud-1", ProviderName:"happycloud", ProviderRegion:"west", Labels:map[string]string(nil)}
43+
// image.Image{ImageID:"happycloud-2", ProviderName:"happycloud", ProviderRegion:"east", Labels:map[string]string(nil)}
44+
45+
}
46+
47+
type artifact struct {
48+
images map[string]string
49+
}
50+
51+
func (a *artifact) BuilderId() string {
52+
return "example.happycloud"
53+
}
54+
55+
func (a *artifact) Files() []string {
56+
return nil
57+
}
58+
59+
func (a *artifact) Id() string {
60+
parts := make([]string, 0, len(a.images))
61+
for loc, id := range a.images {
62+
parts = append(parts, loc+":"+id)
63+
}
64+
sort.Strings(parts)
65+
return strings.Join(parts, ",")
66+
}
67+
68+
func (a *artifact) String() string {
69+
return a.Id()
70+
}
71+
72+
func (a *artifact) State(name string) interface{} {
73+
return nil
74+
}
75+
76+
func (a *artifact) Destroy() error {
77+
return nil
78+
}

packer/registry/image/image.go

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

0 commit comments

Comments
 (0)