-
Notifications
You must be signed in to change notification settings - Fork 59
Add registry image pkg #76
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7b13739
Add registryimage pkg
7ab6bae
Add FromMappedData func
22040a1
Init map if nil
ddf6a33
Add validate method to Image type
f86c7cc
update go mod files
ca76e04
Add stringer for image
8464b03
Drop mapstructure tags
edcb734
Bump go modules
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package image_test | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" | ||
| ) | ||
|
|
||
| type simpleArtifact struct { | ||
| image_id string | ||
| } | ||
|
|
||
| func (a *simpleArtifact) BuilderId() string { | ||
| return "example.happycloud" | ||
| } | ||
|
|
||
| func (a *simpleArtifact) Files() []string { | ||
| return nil | ||
| } | ||
|
|
||
| func (a *simpleArtifact) Id() string { | ||
| return a.image_id | ||
| } | ||
|
|
||
| func (a *simpleArtifact) String() string { | ||
| return fmt.Sprintf("Imported image URL: %s", a.Id()) | ||
| } | ||
|
|
||
| func (a *simpleArtifact) State(name string) interface{} { | ||
| return nil | ||
| } | ||
|
|
||
| func (a *simpleArtifact) Destroy() error { | ||
| return nil | ||
| } | ||
|
|
||
| func ExampleFromArtifact() { | ||
|
|
||
| a := &simpleArtifact{ | ||
| image_id: "service-id-123", | ||
| } | ||
|
|
||
| hcimage, _ := image.FromArtifact(a) | ||
| fmt.Printf("%#v", *hcimage) | ||
| // Output: | ||
| // image.Image{ImageID:"service-id-123", ProviderName:"example.happycloud", ProviderRegion:"", Labels:map[string]string{}} | ||
| } | ||
|
|
||
| func ExampleWithProvider() { | ||
| a := &simpleArtifact{ | ||
| image_id: "service-id-123", | ||
| } | ||
|
|
||
| // This example also includes an override for the ProviderRegion to illustrate how ArtifactOverrideFunc(s) can be chained. | ||
| hcimage, _ := image.FromArtifact(a, image.WithProvider("happycloud"), image.WithRegion("west")) | ||
| fmt.Printf("%#v", *hcimage) | ||
| // Output: | ||
| // image.Image{ImageID:"service-id-123", ProviderName:"happycloud", ProviderRegion:"west", Labels:map[string]string{}} | ||
| } | ||
|
|
||
| func ExampleSetLabels() { | ||
| a := &simpleArtifact{ | ||
| image_id: "service-id-123", | ||
| } | ||
|
|
||
| hcimage, _ := image.FromArtifact(a, image.SetLabels(map[string]interface{}{"kernel": "4.0", "python": "3.5"})) | ||
| fmt.Printf("%v", hcimage.Labels) | ||
| // Unordered output: | ||
| // map[kernel:4.0 python:3.5] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package image_test | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "sort" | ||
| "strings" | ||
|
|
||
| "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" | ||
| ) | ||
|
|
||
| type artifact struct { | ||
| images map[string]string | ||
| } | ||
|
|
||
| func (a *artifact) BuilderId() string { | ||
| return "example.happycloud" | ||
| } | ||
|
|
||
| func (a *artifact) Files() []string { | ||
| return nil | ||
| } | ||
|
|
||
| func (a *artifact) Id() string { | ||
| parts := make([]string, 0, len(a.images)) | ||
| for loc, id := range a.images { | ||
| parts = append(parts, loc+":"+id) | ||
| } | ||
| sort.Strings(parts) | ||
| return strings.Join(parts, ",") | ||
| } | ||
|
|
||
| func (a *artifact) String() string { | ||
| return a.Id() | ||
| } | ||
|
|
||
| func (a *artifact) State(name string) interface{} { | ||
| return nil | ||
| } | ||
|
|
||
| func (a *artifact) Destroy() error { | ||
| return nil | ||
| } | ||
|
|
||
| func ExampleFromMappedData() { | ||
| a := &artifact{ | ||
| images: map[string]string{ | ||
| "west": "happycloud-1", | ||
| "east": "happycloud-2", | ||
| }, | ||
| } | ||
|
|
||
| f := func(key, value interface{}) (*image.Image, error) { | ||
| v, ok := value.(string) | ||
| if !ok { | ||
| return nil, errors.New("for happycloud maps value should always be string") | ||
| } | ||
| k, ok := key.(string) | ||
| if !ok { | ||
| return nil, errors.New("for happycloud maps key should always be string") | ||
| } | ||
|
|
||
| img := image.Image{ProviderName: "happycloud", ProviderRegion: k, ImageID: v} | ||
| return &img, nil | ||
| } | ||
|
|
||
| hcimages, _ := image.FromMappedData(a.images, f) | ||
| for _, hcimage := range hcimages { | ||
| fmt.Printf("%#v\n", *hcimage) | ||
| } | ||
| // Unordered output: | ||
| // image.Image{ImageID:"happycloud-1", ProviderName:"happycloud", ProviderRegion:"west", Labels:map[string]string(nil)} | ||
| // image.Image{ImageID:"happycloud-2", ProviderName:"happycloud", ProviderRegion:"east", Labels:map[string]string(nil)} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| /* 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 | ||
| } | ||
|
|
||
| // 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 { | ||
| if img == nil { | ||
| return errors.New("no go on empty image") | ||
| } | ||
|
|
||
| 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 { | ||
| if img == nil { | ||
| return errors.New("no go on empty image") | ||
| } | ||
|
|
||
| img.ImageID = 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 { | ||
| if img == nil { | ||
| return errors.New("no go on empty image") | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an alternative to this implementation which would allow for a map of any type. I'm considering just supporting a mappedData input of map[string]string, as this seems to be the std. map being used across most plugins that return multi-region artifacts.
It also removes the need for the consumer to do type assertions in the iteration function, which seems a bit complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any specific reason you didn't go to
map[string]stringfrom the beginning?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flexibility really. I thought maybe it would be useful to others if I didn't define the concrete type.