From 762b69120e344cf3bd9aab2d154923ce5fea7139 Mon Sep 17 00:00:00 2001 From: Ramkumar Chinchani <rchincha@cisco.com> Date: Wed, 9 Nov 2022 21:18:28 +0000 Subject: [PATCH] feat: support writing os/arch info image config Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com> --- build.go | 5 ++--- cache_test.go | 2 +- doc/stacker_yaml.md | 11 +++++++++++ test/multi-arch.bats | 44 ++++++++++++++++++++++++++++++++++++++++++++ types/layer.go | 23 +++++++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 test/multi-arch.bats diff --git a/build.go b/build.go index 1c3ba49f..2e980a78 100644 --- a/build.go +++ b/build.go @@ -8,7 +8,6 @@ import ( "os/exec" "os/user" "path" - "runtime" "strings" "time" @@ -210,8 +209,8 @@ func (b *Builder) updateOCIConfigForOutput(sf *types.Stackerfile, s types.Storag author := fmt.Sprintf("%s@%s", username, host) meta.Created = time.Now() - meta.Architecture = runtime.GOARCH - meta.OS = runtime.GOOS + meta.Architecture = *l.Arch + meta.OS = *l.OS meta.Author = author annotations, err := mutator.Annotations(context.Background()) diff --git a/cache_test.go b/cache_test.go index bb09249f..a6df77f0 100644 --- a/cache_test.go +++ b/cache_test.go @@ -126,5 +126,5 @@ func TestCacheEntryChanged(t *testing.T) { // This test works because the type information is included in the // hashstructure hash above, so using a zero valued CacheEntry is // enough to capture changes in types. - assert.Equal(uint64(0x98ab47c70ff0b70), h) + assert.Equal(uint64(0x1ec739cbb7ee4e77), h) } diff --git a/doc/stacker_yaml.md b/doc/stacker_yaml.md index 11d69e71..7530fde0 100644 --- a/doc/stacker_yaml.md +++ b/doc/stacker_yaml.md @@ -233,3 +233,14 @@ While `config` section supports a similar `labels`, it is more pertitent to the image runtime. On the other hand, `annotations` is intended to be image-specific metadata aligned with the [annotations in the image spec](https://github.com/opencontainers/image-spec/blob/main/annotations.md). + +##### `os` + +`os` is a user-specified string value indicating which _operating system_ this image is being +built for, for example, `linux`, `darwin`, etc. It is an optional field and it +defaults to the host operating system if not specified. + +##### `arch` +`arch` is a user-specified string value indicating which machine _architecture_ this image is being +built for, for example, `amd64`, `arm64`, etc. It is an optional field and it +defaults to the host machine architecture if not specified. diff --git a/test/multi-arch.bats b/test/multi-arch.bats new file mode 100644 index 00000000..3755b0d5 --- /dev/null +++ b/test/multi-arch.bats @@ -0,0 +1,44 @@ +load helpers + +function setup() { + stacker_setup +} + +function teardown() { + cleanup +} + +@test "multi-arch/os support" { + cat > stacker.yaml <<EOF +centos: + os: darwin + arch: arm64 + from: + type: oci + url: $CENTOS_OCI + import: + - https://www.cisco.com/favicon.ico +EOF + stacker build + + # check OCI image generation + manifest=$(cat oci/index.json | jq -r .manifests[0].digest | cut -f2 -d:) + layer=$(cat oci/blobs/sha256/$manifest | jq -r .layers[0].digest) + config=$(cat oci/blobs/sha256/$manifest | jq -r .config.digest | cut -f2 -d:) + [ "$(cat oci/blobs/sha256/$config | jq -r '.architecture')" = "arm64" ] + [ "$(cat oci/blobs/sha256/$config | jq -r '.os')" = "darwin" ] +} + +@test "multi-arch/os bad config fails" { + cat > stacker.yaml <<EOF +centos: + os: + from: + type: oci + url: $CENTOS_OCI + import: + - https://www.cisco.com/favicon.ico +EOF + bad_stacker build + [ "$status" -eq 1 ] +} diff --git a/types/layer.go b/types/layer.go index fe4886dc..8747a036 100644 --- a/types/layer.go +++ b/types/layer.go @@ -6,6 +6,7 @@ import ( "path/filepath" "reflect" "regexp" + "runtime" "strings" "github.com/anmitsu/go-shlex" @@ -192,6 +193,8 @@ type Layer struct { Binds Binds `yaml:"binds"` RuntimeUser string `yaml:"runtime_user"` Annotations map[string]string `yaml:"annotations"` + OS *string `yaml:"os"` + Arch *string `yaml:"arch"` } func parseLayers(referenceDirectory string, lms yaml.MapSlice, requireHash bool) (map[string]Layer, error) { @@ -227,6 +230,12 @@ func parseLayers(referenceDirectory string, lms yaml.MapSlice, requireHash bool) } } } + + if directive.Key.(string) == "os" || directive.Key.(string) == "arch" { + if directive.Value == nil { + return nil, errors.Errorf("stackerfile: %q value cannot be empty", directive.Key.(string)) + } + } } } @@ -257,6 +266,18 @@ func parseLayers(referenceDirectory string, lms yaml.MapSlice, requireHash bool) } } + if layer.OS == nil { + // if not specified, default to runtime + os := runtime.GOOS + layer.OS = &os + } + + if layer.Arch == nil { + // if not specified, default to runtime + arch := runtime.GOARCH + layer.Arch = &arch + } + ret[name], err = layer.absolutify(referenceDirectory) if err != nil { return nil, err @@ -467,6 +488,8 @@ func init() { layerType := reflect.TypeOf(Layer{}) for i := 0; i < layerType.NumField(); i++ { tag := layerType.Field(i).Tag.Get("yaml") + // some fields are ",omitempty" + tag = strings.Split(tag, ",")[0] layerFields = append(layerFields, tag) } }