Skip to content

Commit a9a95c4

Browse files
committed
merge #640 into opencontainers/umoci:main
Aleksa Sarai (7): umoci new: include host ARM variant by default config: add support for platform variants oci: config: fix annotation precedence test: config: add umoci-stat verification checks test: config: add tests for config.json annotation conversions test: check annotations for config --{os,architecture} config: add "platform" prefix to "os" and "architecture" setting names LGTMs: cyphar
2 parents 1f0fbe5 + 0242283 commit a9a95c4

35 files changed

+2472
-70
lines changed

CHANGELOG.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ explicitly use any of the newer features, this is mostly a quality-of-life
2626
update to move away from our ancient pinned version of the runtime-spec.
2727

2828
### Breaking ###
29-
* The existing `ConfigExposedPorts` and `ConfigVolumes` methods of
30-
`github.com/opencontainers/umoci/oci/config/generate.Generator` now return a
31-
sorted `[]string` instead of a map.
29+
* `github.com/opencontainers/umoci/oci/config/generate.Generator` has had the
30+
following breaking API changes made to it:
31+
- The existing `ConfigExposedPorts` and `ConfigVolumes` methods now return a
32+
sorted `[]string` instead of a `map`.
33+
- The `(Set)OS` and `(Set)Architecture` methods have been renamed to have a
34+
`Platform` prefix (to match image-spec v1.1's organisational changes). They
35+
now read as `(Set)PlatformOS` and `(Set)PlatformArchitecture` respectively.
3236

3337
### Added ###
3438
* `umoci stat` now includes information about the manifest and configuration of
@@ -59,6 +63,11 @@ update to move away from our ancient pinned version of the runtime-spec.
5963
better off just using `gomtree`.
6064
* `umoci --version` now provides more information about the specification
6165
versions supported by the `umoci` binary as well as the Go version used.
66+
* `umoci config` now supports specifying the architecture variant of the image
67+
with `--platform.variant`. In addition, `--os` and `--architecture` can now
68+
be set using `--platform.os` and `--platform.arch` respectively.
69+
* `umoci new` will not automatically fill the architecture variant on ARM
70+
systems to match the host CPU.
6271

6372
### Changed ###
6473
* The output format of `umoci stat` has had some minor changes made to how
@@ -71,6 +80,10 @@ update to move away from our ancient pinned version of the runtime-spec.
7180
archives. Previously, we would defer to the Go stdlib's `archive/tar` which
7281
rounds to the nearest second (which is incompatible with `gomtree` and so in
7382
theory could lead to inconsistent results).
83+
* Previously, when generating the runtime-spec `config.json`, `umoci unpack`
84+
would incorrectly prioritise the automatically generated annotations over
85+
explicitly configured labels. This precdence was the opposite of what the
86+
image-spec requires, and has now been resolved.
7487

7588
[source-date-epoch]: https://reproducible-builds.org/docs/source-date-epoch/
7689
[obs-gomtree]: https://build.opensuse.org/package/show/Virtualization:containers/go-mtree

cmd/umoci/config.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ image.`,
9191
cli.StringFlag{Name: "config.stopsignal"},
9292
cli.StringFlag{Name: "created"}, // FIXME: Implement TimeFlag.
9393
cli.StringFlag{Name: "author"},
94-
cli.StringFlag{Name: "architecture"},
95-
cli.StringFlag{Name: "os"},
94+
cli.StringFlag{Name: "platform.os,os"},
95+
cli.StringFlag{Name: "platform.arch,architecture"},
96+
cli.StringFlag{Name: "platform.variant"},
97+
// TODO: platform.os.{version,features}
9698
cli.StringSliceFlag{Name: "manifest.annotation"},
9799
cli.StringSliceFlag{Name: "clear"},
98100
},
@@ -107,8 +109,9 @@ func toImage(config ispec.ImageConfig, meta mutate.Meta) ispec.Image {
107109
Created: &created,
108110
Author: meta.Author,
109111
Platform: ispec.Platform{
110-
Architecture: meta.Architecture,
111112
OS: meta.OS,
113+
Architecture: meta.Architecture,
114+
Variant: meta.Variant,
112115
},
113116
}
114117
}
@@ -121,8 +124,9 @@ func fromImage(image ispec.Image) (ispec.ImageConfig, mutate.Meta) {
121124
return image.Config, mutate.Meta{
122125
Created: created,
123126
Author: image.Author,
124-
Architecture: image.Architecture,
125127
OS: image.OS,
128+
Architecture: image.Architecture,
129+
Variant: image.Variant,
126130
}
127131
}
128132

@@ -234,11 +238,14 @@ func config(ctx *cli.Context) (Err error) {
234238
if ctx.IsSet("author") {
235239
g.SetAuthor(ctx.String("author"))
236240
}
237-
if ctx.IsSet("architecture") {
238-
g.SetArchitecture(ctx.String("architecture"))
241+
if ctx.IsSet("platform.os") {
242+
g.SetPlatformOS(ctx.String("platform.os"))
243+
}
244+
if ctx.IsSet("platform.arch") {
245+
g.SetPlatformArchitecture(ctx.String("platform.arch"))
239246
}
240-
if ctx.IsSet("os") {
241-
g.SetOS(ctx.String("os"))
247+
if ctx.IsSet("platform.variant") {
248+
g.SetPlatformVariant(ctx.String("platform.variant"))
242249
}
243250
if ctx.IsSet("config.user") {
244251
g.SetConfigUser(ctx.String("config.user"))

doc/man/umoci-config.1.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ umoci config - Modifies the configuration of an OCI image
2424
[**--config.workingdir**=*value*]
2525
[**--created**=*value*]
2626
[**--author**=*value*]
27-
[**--architecture**=*value*]
28-
[**--os**=*value*]
27+
[**--platform.os**=*value*]
28+
[**--platform.arch**=*value*]
2929
[**--manifest.annotation**=*value*]
3030

3131
# DESCRIPTION
@@ -100,8 +100,8 @@ or image manifest. For more information see [the OCI image specification][1].
100100
* **--config.workingdir**=*value*
101101
* **--created**=*value*
102102
* **--author**=*value*
103-
* **--architecture**=*value*
104-
* **--os**=*value*
103+
* **--platform.os**=*value* (can also be set with **--os**)
104+
* **--platform.arch**=*value* (can also be set with **--architecture**)
105105
* **--manifest.annotation**=*value*
106106

107107
# EXAMPLE
@@ -113,7 +113,7 @@ overwrites the original tag with the new image.
113113
% umoci config --image image:tag --clear=config.env --config.env="VARIABLE=true" \
114114
--config.user="user:group" --config.entrypoint=cat --config.cmd=/proc/self/stat \
115115
--config.label="com.cyphar.umoci=true" --author="Aleksa Sarai <[email protected]>" \
116-
--os="gnu/hurd" --architecture="lisp" --created="$(date --iso-8601=seconds)"
116+
--platform.os="linux" --platform.arch="amd64" --created="$(date --iso-8601=seconds)"
117117
```
118118

119119
# SEE ALSO

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1
2424
github.com/apex/log v1.9.0
2525
github.com/blang/semver/v4 v4.0.0
26+
github.com/containerd/platforms v0.2.1
2627
github.com/cyphar/filepath-securejoin v0.5.0
2728
github.com/docker/go-units v0.5.0
2829
github.com/klauspost/compress v1.11.3
@@ -42,6 +43,7 @@ require (
4243
)
4344

4445
require (
46+
github.com/containerd/log v0.1.0 // indirect
4547
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
4648
github.com/davecgh/go-spew v1.1.1 // indirect
4749
github.com/fatih/color v1.18.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
1010
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
1111
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
1212
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
13+
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
14+
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
15+
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
16+
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
1317
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
1418
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
1519
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=

mutate/mutate.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,15 @@ type Meta struct {
9595
// are built to run on.
9696
Architecture string `json:"architecture"`
9797

98+
// Variant is the variant of the CPU architecture which the binaries in
99+
// this image are built to run on.
100+
Variant string `json:"variant"`
101+
98102
// OS is the name of the operating system which the image is built to run
99103
// on.
100104
OS string `json:"os"`
105+
106+
// TODO: Should we embed ispec.Platform?
101107
}
102108

103109
// cache ensures that the cached versions of the related configurations have
@@ -232,6 +238,7 @@ func (m *Mutator) Set(ctx context.Context, config ispec.ImageConfig, meta Meta,
232238
m.config.Created = timePtr(meta.Created)
233239
m.config.Author = meta.Author
234240
m.config.Architecture = meta.Architecture
241+
m.config.Variant = meta.Variant
235242
m.config.OS = meta.OS
236243

237244
// Append history.

new.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ package umoci
2121
import (
2222
"context"
2323
"fmt"
24-
"runtime"
2524
"time"
2625

2726
"github.com/apex/log"
27+
"github.com/containerd/platforms"
2828
imeta "github.com/opencontainers/image-spec/specs-go"
2929
ispec "github.com/opencontainers/image-spec/specs-go/v1"
3030

@@ -46,12 +46,14 @@ func NewImage(engineExt casext.Engine, tagName string, sourceDateEpoch *time.Tim
4646
createTime = *sourceDateEpoch
4747
}
4848

49-
// Set all of the defaults we need.
5049
g.SetCreated(createTime)
51-
g.SetOS(runtime.GOOS)
52-
g.SetArchitecture(runtime.GOARCH)
5350
g.ClearHistory()
5451

52+
hostPlatform := platforms.DefaultSpec()
53+
g.SetPlatformOS(hostPlatform.OS)
54+
g.SetPlatformArchitecture(hostPlatform.Architecture)
55+
g.SetPlatformVariant(hostPlatform.Variant)
56+
5557
// Make sure we have no diffids.
5658
g.SetRootfsType("layers")
5759
g.ClearRootfsDiffIDs()

oci/config/convert/runtime.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ import (
3737
// in an image configuration that do not have a native representation in the
3838
// runtime-spec).
3939
const (
40-
osAnnotation = "org.opencontainers.image.os"
41-
archAnnotation = "org.opencontainers.image.architecture"
42-
authorAnnotation = "org.opencontainers.image.author"
43-
createdAnnotation = "org.opencontainers.image.created"
44-
stopSignalAnnotation = "org.opencontainers.image.stopSignal"
45-
exposedPortsAnnotation = "org.opencontainers.image.exposedPorts"
40+
platformOsAnnotation = "org.opencontainers.image.os"
41+
platformArchAnnotation = "org.opencontainers.image.architecture"
42+
platformVariantAnnotation = "org.opencontainers.image.variant"
43+
authorAnnotation = "org.opencontainers.image.author"
44+
createdAnnotation = "org.opencontainers.image.created"
45+
stopSignalAnnotation = "org.opencontainers.image.stopSignal"
46+
exposedPortsAnnotation = "org.opencontainers.image.exposedPorts"
4647
)
4748

4849
// ToRuntimeSpec converts the given OCI image configuration to a runtime
@@ -107,14 +108,16 @@ func allocateNilStruct(spec *rspec.Spec) {
107108
}
108109

109110
// MutateRuntimeSpec mutates a given runtime configuration with the image
110-
// configuration provided.
111+
// configuration provided in accordance with the image specification's
112+
// conversion mechanism (for more information, see
113+
// <https://github.com/opencontainers/image-spec/blob/main/conversion.md>).
111114
func MutateRuntimeSpec(spec *rspec.Spec, rootfs string, image ispec.Image) error {
112115
ig, err := igen.NewFromImage(image)
113116
if err != nil {
114117
return fmt.Errorf("creating image generator: %w", err)
115118
}
116119

117-
if ig.OS() != "linux" {
120+
if ig.PlatformOS() != "linux" {
118121
return fmt.Errorf("unsupported OS: %s", image.OS)
119122
}
120123

@@ -164,13 +167,23 @@ func MutateRuntimeSpec(spec *rspec.Spec, rootfs string, image ispec.Image) error
164167
spec.Process.Args = args
165168
}
166169

167-
// Set annotations fields
170+
// Set the "annotation fields".
171+
setAnnotation := func(name, value string) {
172+
if value != "" {
173+
spec.Annotations[name] = value
174+
} else {
175+
delete(spec.Annotations, name)
176+
}
177+
}
178+
setAnnotation(platformOsAnnotation, ig.PlatformOS())
179+
setAnnotation(platformArchAnnotation, ig.PlatformArchitecture())
180+
setAnnotation(platformVariantAnnotation, ig.PlatformVariant())
181+
setAnnotation(authorAnnotation, ig.Author())
182+
setAnnotation(createdAnnotation, ig.Created().Format(igen.ISO8601))
183+
setAnnotation(stopSignalAnnotation, image.Config.StopSignal)
184+
setAnnotation(exposedPortsAnnotation, strings.Join(ig.ConfigExposedPorts(), ","))
185+
// Config.Labels need to be applied after the auto-applied labels.
168186
maps.Copy(spec.Annotations, ig.ConfigLabels())
169-
spec.Annotations[osAnnotation] = ig.OS()
170-
spec.Annotations[archAnnotation] = ig.Architecture()
171-
spec.Annotations[authorAnnotation] = ig.Author()
172-
spec.Annotations[createdAnnotation] = ig.Created().Format(igen.ISO8601)
173-
spec.Annotations[stopSignalAnnotation] = image.Config.StopSignal
174187

175188
// Set parsed fields
176189
// Get the *actual* uid and gid of the user. If the image doesn't contain
@@ -204,10 +217,6 @@ func MutateRuntimeSpec(spec *rspec.Spec, rootfs string, image ispec.Image) error
204217
appendEnv(&spec.Process.Env, "HOME", execUser.Home)
205218
}
206219

207-
// Set optional fields
208-
ports := ig.ConfigExposedPorts()
209-
spec.Annotations[exposedPortsAnnotation] = strings.Join(ports, ",")
210-
211220
for _, vol := range ig.ConfigVolumes() {
212221
// XXX: This is _fine_ but might cause some issues in the future.
213222
spec.Mounts = append(spec.Mounts, rspec.Mount{

oci/config/generate/spec.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -319,22 +319,32 @@ func (g *Generator) Author() string {
319319
return g.image.Author
320320
}
321321

322-
// SetArchitecture is the CPU architecture which the binaries in this image are built to run on.
323-
func (g *Generator) SetArchitecture(arch string) {
322+
// SetPlatformOS sets the name of the operating system which the image is built to run on.
323+
func (g *Generator) SetPlatformOS(os string) {
324+
g.image.OS = os
325+
}
326+
327+
// PlatformOS returns the name of the operating system which the image is built to run on.
328+
func (g *Generator) PlatformOS() string {
329+
return g.image.OS
330+
}
331+
332+
// SetPlatformArchitecture is the CPU architecture which the binaries in this image are built to run on.
333+
func (g *Generator) SetPlatformArchitecture(arch string) {
324334
g.image.Architecture = arch
325335
}
326336

327-
// Architecture returns the CPU architecture which the binaries in this image are built to run on.
328-
func (g *Generator) Architecture() string {
337+
// PlatformArchitecture returns the CPU architecture which the binaries in this image are built to run on.
338+
func (g *Generator) PlatformArchitecture() string {
329339
return g.image.Architecture
330340
}
331341

332-
// SetOS sets the name of the operating system which the image is built to run on.
333-
func (g *Generator) SetOS(os string) {
334-
g.image.OS = os
342+
// SetPlatformVariant is the CPU architecture variant which the binaries in this image are built to run on.
343+
func (g *Generator) SetPlatformVariant(variant string) {
344+
g.image.Variant = variant
335345
}
336346

337-
// OS returns the name of the operating system which the image is built to run on.
338-
func (g *Generator) OS() string {
339-
return g.image.OS
347+
// PlatformVariant returns the CPU architecture variant which the binaries in this image are built to run on.
348+
func (g *Generator) PlatformVariant() string {
349+
return g.image.Variant
340350
}

oci/config/generate/spec_test.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,34 @@ func TestConfigWorkingDir(t *testing.T) {
6868
assert.Equal(t, expected, got, "ConfigWorkingDir get/set should match")
6969
}
7070

71-
func TestArchitecture(t *testing.T) {
71+
func TestPlatformOS(t *testing.T) {
7272
g := New()
7373
expected := "some_value"
7474

75-
g.SetArchitecture(expected)
76-
got := g.Architecture()
75+
g.SetPlatformOS(expected)
76+
got := g.PlatformOS()
7777

78-
assert.Equal(t, expected, got, "Architecture get/set should match")
78+
assert.Equal(t, expected, got, "PlatformOS get/set should match")
7979
}
8080

81-
func TestOS(t *testing.T) {
81+
func TestPlatformArchitecture(t *testing.T) {
8282
g := New()
8383
expected := "some_value"
8484

85-
g.SetOS(expected)
86-
got := g.OS()
85+
g.SetPlatformArchitecture(expected)
86+
got := g.PlatformArchitecture()
8787

88-
assert.Equal(t, expected, got, "OS get/set should match")
88+
assert.Equal(t, expected, got, "PlatformArchitecture get/set should match")
89+
}
90+
91+
func TestPlatformVariant(t *testing.T) {
92+
g := New()
93+
expected := "some_value"
94+
95+
g.SetPlatformVariant(expected)
96+
got := g.PlatformVariant()
97+
98+
assert.Equal(t, expected, got, "PlatformVariant get/set should match")
8999
}
90100

91101
func TestAuthor(t *testing.T) {

0 commit comments

Comments
 (0)