diff --git a/cmd/osbuild-playground/my-container.go b/cmd/osbuild-playground/my-container.go index 9883f6c8f2..ae19af7beb 100644 --- a/cmd/osbuild-playground/my-container.go +++ b/cmd/osbuild-playground/my-container.go @@ -3,6 +3,7 @@ package main import ( "math/rand" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/artifact" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/platform" @@ -54,7 +55,7 @@ func (img *MyContainer) InstantiateManifest(m *manifest.Manifest, os.ExtraBasePackages = []string{"@core"} os.OSCustomizations.Language = "en_US.UTF-8" os.OSCustomizations.Hostname = "my-host" - os.OSCustomizations.Timezone = "UTC" + os.OSCustomizations.Timezone = types.Some("UTC") // create an OCI container containing the OS tree created above container := manifest.NewOCIContainer(build, os) diff --git a/internal/types/LICENSE b/internal/types/LICENSE new file mode 100644 index 0000000000..56752adb8a --- /dev/null +++ b/internal/types/LICENSE @@ -0,0 +1,8 @@ +Copyright 2021 Taiki Kawakami (a.k.a. moznion) https://moznion.net + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/internal/types/option.go b/internal/types/option.go new file mode 100644 index 0000000000..1efb8a2df6 --- /dev/null +++ b/internal/types/option.go @@ -0,0 +1,117 @@ +package types + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Option is a more constrained subset of the code in +// https://github.com/moznion/go-optional +// +// It is not using an external go-optional lib directly because none +// has toml unmarshal support and also because there is no support for +// complex types in UnmarshalTOML (it only gives a single +// reflect.Value of type any) so our optional "lib" must come with +// limitations for this. +// +// Unfortunately there is no way I could find to import the code and +// limit the supported types. So this is a copy of the subset. +// Fortunatly the code is small, targeted and easy to follow so it +// should not be too bad. + +type OptionTomlTypes interface { + ~int | ~bool | ~string +} + +// Option is a subset of github.com/moznion/go-optional for use with toml + +// Option is a data type that must be Some (i.e. having a value) or None (i.e. doesn't have a value). +// This type implements database/sql/driver.Valuer and database/sql.Scanner. +type Option[T OptionTomlTypes] []T + +const ( + value = iota +) + +// Some is a function to make an Option type value with the actual value. +func Some[T OptionTomlTypes](v T) Option[T] { + return Option[T]{ + value: v, + } +} + +// None is a function to make an Option type value that doesn't have a value. +func None[T OptionTomlTypes]() Option[T] { + return nil +} + +// IsNone returns True if the Option *doesn't* have a value +func (o Option[T]) IsNone() bool { + return o == nil +} + +// IsSome returns whether the Option has a value or not. +func (o Option[T]) IsSome() bool { + return o != nil +} + +// Unwrap returns the value regardless of Some/None status. +// If the Option value is Some, this method returns the actual value. +// On the other hand, if the Option value is None, this method returns the *default* value according to the type. +func (o Option[T]) Unwrap() T { + if o.IsNone() { + var defaultValue T + return defaultValue + } + return o[value] +} + +// TakeOr returns the actual value if the Option has a value. +// On the other hand, this returns fallbackValue. +func (o Option[T]) TakeOr(fallbackValue T) T { + if o.IsNone() { + return fallbackValue + } + return o[value] +} + +var jsonNull = []byte("null") + +func (o Option[T]) MarshalJSON() ([]byte, error) { + if o.IsNone() { + return jsonNull, nil + } + + marshal, err := json.Marshal(o.Unwrap()) + if err != nil { + return nil, err + } + return marshal, nil +} + +func (o *Option[T]) UnmarshalJSON(data []byte) error { + if len(data) <= 0 || bytes.Equal(data, jsonNull) { + *o = None[T]() + return nil + } + + var v T + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + *o = Some(v) + + return nil +} + +// not part of github.com/moznion/go-optional +func (o *Option[T]) UnmarshalTOML(data any) error { + b, ok := data.(T) + if !ok { + return fmt.Errorf("cannot use %[1]v (%[1]T) as bool", data) + } + *o = Some(b) + return nil +} diff --git a/internal/types/option_test.go b/internal/types/option_test.go new file mode 100644 index 0000000000..8bf94812a4 --- /dev/null +++ b/internal/types/option_test.go @@ -0,0 +1,80 @@ +package types_test + +import ( + "testing" + + "github.com/BurntSushi/toml" + "github.com/stretchr/testify/assert" + + "github.com/osbuild/images/internal/types" +) + +type s1 struct { + Name string `toml:"name"` + Sub s2 `toml:"sub"` +} + +type s2 struct { + ValBool types.Option[bool] `toml:"val-bool"` + ValString types.Option[string] `toml:"val-string"` + ValString2 types.Option[string] `toml:"val-string2"` +} + +func TestTomlParseOption(t *testing.T) { + testTomlStr := ` +name = "some-name" +[sub] +val-bool = true +val-string = "opt-string" +` + + var bp s1 + err := toml.Unmarshal([]byte(testTomlStr), &bp) + assert.NoError(t, err) + assert.Equal(t, bp.Name, "some-name") + assert.Equal(t, types.Some(true), bp.Sub.ValBool) + assert.Equal(t, types.Some("opt-string"), bp.Sub.ValString) + assert.EqualValues(t, types.None[string](), bp.Sub.ValString2) +} + +func TestTomlParseOptionBad(t *testing.T) { + testTomlStr := ` +[sub] +val-bool = 1234 +` + + var bp s1 + err := toml.Unmarshal([]byte(testTomlStr), &bp) + assert.ErrorContains(t, err, "cannot use 1234 (int64) as bool") +} + +// taken from https://github.com/moznion/go-optional +func TestOption_IsNone(t *testing.T) { + assert.True(t, types.None[int]().IsNone()) + assert.False(t, types.Some[int](123).IsNone()) + + var nilValue types.Option[int] = nil + assert.True(t, nilValue.IsNone()) +} + +func TestOption_IsSome(t *testing.T) { + assert.False(t, types.None[int]().IsSome()) + assert.True(t, types.Some[int](123).IsSome()) + + var nilValue types.Option[int] = nil + assert.False(t, nilValue.IsSome()) +} + +func TestOption_Unwrap(t *testing.T) { + assert.Equal(t, "foo", types.Some[string]("foo").Unwrap()) + assert.Equal(t, "", types.None[string]().Unwrap()) + assert.Equal(t, false, types.None[bool]().Unwrap()) +} + +func TestOption_TakeOr(t *testing.T) { + v := types.Some[int](123).TakeOr(666) + assert.Equal(t, 123, v) + + v = types.None[int]().TakeOr(666) + assert.Equal(t, 666, v) +} diff --git a/pkg/blueprint/blueprint.go b/pkg/blueprint/blueprint.go index 6fdf205f6d..b01289016b 100644 --- a/pkg/blueprint/blueprint.go +++ b/pkg/blueprint/blueprint.go @@ -1,6 +1,10 @@ // Package blueprint contains primitives for representing weldr blueprints package blueprint +import ( + "github.com/osbuild/images/internal/types" +) + // A Blueprint is a high-level description of an image. type Blueprint struct { Name string `json:"name" toml:"name"` @@ -32,8 +36,8 @@ type Container struct { Source string `json:"source" toml:"source"` Name string `json:"name,omitempty" toml:"name,omitempty"` - TLSVerify *bool `json:"tls-verify,omitempty" toml:"tls-verify,omitempty"` - LocalStorage bool `json:"local-storage,omitempty" toml:"local-storage,omitempty"` + TLSVerify types.Option[bool] `json:"tls-verify,omitempty" toml:"tls-verify,omitempty"` + LocalStorage bool `json:"local-storage,omitempty" toml:"local-storage,omitempty"` } // packages, modules, and groups all resolve to rpm packages right now. This diff --git a/pkg/blueprint/blueprint_test.go b/pkg/blueprint/blueprint_test.go index 549d114173..cf9f289ea0 100644 --- a/pkg/blueprint/blueprint_test.go +++ b/pkg/blueprint/blueprint_test.go @@ -160,3 +160,29 @@ func TestKernelNameCustomization(t *testing.T) { } } } + +func TestBlueprintParseSetHostnameOptional(t *testing.T) { + blueprint := ` +name = "test" + +[customizations] +hostname = "my-hostname" +` + + var bp Blueprint + err := toml.Unmarshal([]byte(blueprint), &bp) + require.Nil(t, err) + assert.Equal(t, bp.Name, "test") + assert.Equal(t, "my-hostname", bp.Customizations.GetHostname().Unwrap()) + + blueprint = `{ + "name": "test", + "customizations": { + "hostname": "my-hostname" + } + }` + err = json.Unmarshal([]byte(blueprint), &bp) + require.Nil(t, err) + assert.Equal(t, bp.Name, "test") + assert.Equal(t, "my-hostname", bp.Customizations.GetHostname().Unwrap()) +} diff --git a/pkg/blueprint/customizations.go b/pkg/blueprint/customizations.go index d361626f53..0d55d97252 100644 --- a/pkg/blueprint/customizations.go +++ b/pkg/blueprint/customizations.go @@ -6,11 +6,12 @@ import ( "slices" "strings" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/anaconda" ) type Customizations struct { - Hostname *string `json:"hostname,omitempty" toml:"hostname,omitempty"` + Hostname types.Option[string] `json:"hostname,omitempty" toml:"hostname,omitempty"` Kernel *KernelCustomization `json:"kernel,omitempty" toml:"kernel,omitempty"` SSHKey []SSHKeyCustomization `json:"sshkey,omitempty" toml:"sshkey,omitempty"` User []UserCustomization `json:"user,omitempty" toml:"user,omitempty"` @@ -27,7 +28,7 @@ type Customizations struct { Directories []DirectoryCustomization `json:"directories,omitempty" toml:"directories,omitempty"` Files []FileCustomization `json:"files,omitempty" toml:"files,omitempty"` Repositories []RepositoryCustomization `json:"repositories,omitempty" toml:"repositories,omitempty"` - FIPS *bool `json:"fips,omitempty" toml:"fips,omitempty"` + FIPS types.Option[bool] `json:"fips,omitempty" toml:"fips,omitempty"` ContainersStorage *ContainerStorageCustomization `json:"containers-storage,omitempty" toml:"containers-storage,omitempty"` Installer *InstallerCustomization `json:"installer,omitempty" toml:"installer,omitempty"` RPM *RPMCustomization `json:"rpm,omitempty" toml:"rpm,omitempty"` @@ -68,17 +69,17 @@ type SSHKeyCustomization struct { } type UserCustomization struct { - Name string `json:"name" toml:"name"` - Description *string `json:"description,omitempty" toml:"description,omitempty"` - Password *string `json:"password,omitempty" toml:"password,omitempty"` - Key *string `json:"key,omitempty" toml:"key,omitempty"` - Home *string `json:"home,omitempty" toml:"home,omitempty"` - Shell *string `json:"shell,omitempty" toml:"shell,omitempty"` - Groups []string `json:"groups,omitempty" toml:"groups,omitempty"` - UID *int `json:"uid,omitempty" toml:"uid,omitempty"` - GID *int `json:"gid,omitempty" toml:"gid,omitempty"` - ExpireDate *int `json:"expiredate,omitempty" toml:"expiredate,omitempty"` - ForcePasswordReset *bool `json:"force_password_reset,omitempty" toml:"force_password_reset,omitempty"` + Name string `json:"name" toml:"name"` + Description types.Option[string] `json:"description,omitempty" toml:"description,omitempty"` + Password types.Option[string] `json:"password,omitempty" toml:"password,omitempty"` + Key types.Option[string] `json:"key,omitempty" toml:"key,omitempty"` + Home types.Option[string] `json:"home,omitempty" toml:"home,omitempty"` + Shell types.Option[string] `json:"shell,omitempty" toml:"shell,omitempty"` + Groups []string `json:"groups,omitempty" toml:"groups,omitempty"` + UID types.Option[int] `json:"uid,omitempty" toml:"uid,omitempty"` + GID types.Option[int] `json:"gid,omitempty" toml:"gid,omitempty"` + ExpireDate types.Option[int] `json:"expiredate,omitempty" toml:"expiredate,omitempty"` + ForcePasswordReset types.Option[bool] `json:"force_password_reset,omitempty" toml:"force_password_reset,omitempty"` } type GroupCustomization struct { @@ -87,8 +88,8 @@ type GroupCustomization struct { } type TimezoneCustomization struct { - Timezone *string `json:"timezone,omitempty" toml:"timezone,omitempty"` - NTPServers []string `json:"ntpservers,omitempty" toml:"ntpservers,omitempty"` + Timezone types.Option[string] `json:"timezone,omitempty" toml:"timezone,omitempty"` + NTPServers []string `json:"ntpservers,omitempty" toml:"ntpservers,omitempty"` } type LocaleCustomization struct { @@ -197,7 +198,7 @@ func (c *Customizations) CheckAllowed(allowed ...string) error { return nil } -func (c *Customizations) GetHostname() *string { +func (c *Customizations) GetHostname() types.Option[string] { if c == nil { return nil } @@ -217,7 +218,7 @@ func (c *Customizations) GetPrimaryLocale() (*string, *string) { return &c.Locale.Languages[0], c.Locale.Keyboard } -func (c *Customizations) GetTimezoneSettings() (*string, []string) { +func (c *Customizations) GetTimezoneSettings() (types.Option[string], []string) { if c == nil { return nil, nil } @@ -240,7 +241,7 @@ func (c *Customizations) GetUsers() []UserCustomization { keyc := c.SSHKey[idx] users = append(users, UserCustomization{ Name: keyc.User, - Key: &keyc.Key, + Key: types.Some(keyc.Key), }) } } @@ -252,8 +253,8 @@ func (c *Customizations) GetUsers() []UserCustomization { for idx := range users { u := users[idx] if u.Home != nil { - homedir := strings.TrimRight(*u.Home, "/") - u.Home = &homedir + homedir := strings.TrimRight(u.Home.Unwrap(), "/") + u.Home = types.Some(homedir) users[idx] = u } } @@ -383,10 +384,10 @@ func (c *Customizations) GetRepositories() ([]RepositoryCustomization, error) { } func (c *Customizations) GetFIPS() bool { - if c == nil || c.FIPS == nil { + if c == nil { return false } - return *c.FIPS + return c.FIPS.Unwrap() } func (c *Customizations) GetContainerStorage() *ContainerStorageCustomization { diff --git a/pkg/blueprint/customizations_test.go b/pkg/blueprint/customizations_test.go index ba91b6ba62..3d20e6718f 100644 --- a/pkg/blueprint/customizations_test.go +++ b/pkg/blueprint/customizations_test.go @@ -3,9 +3,11 @@ package blueprint import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/anaconda" - "github.com/stretchr/testify/assert" ) func TestCheckAllowed(t *testing.T) { @@ -23,20 +25,20 @@ func TestCheckAllowed(t *testing.T) { expectedUsers := []UserCustomization{ { Name: "John", - Description: &Desc, - Password: &Pass, - Key: &Key, - Home: &Home, - Shell: &Shell, + Description: types.Some(Desc), + Password: types.Some(Pass), + Key: types.Some(Key), + Home: types.Some(Home), + Shell: types.Some(Shell), Groups: Groups, - UID: &UID, - GID: &GID, + UID: types.Some(UID), + GID: types.Some(GID), }, } - expectedHostname := "Hostname" + expectedHostname := types.Some("Hostname") - x := Customizations{Hostname: &expectedHostname, User: expectedUsers} + x := Customizations{Hostname: expectedHostname, User: expectedUsers} err := x.CheckAllowed("Hostname", "User") assert.NoError(t, err) @@ -51,14 +53,14 @@ func TestCheckAllowed(t *testing.T) { } func TestGetHostname(t *testing.T) { - expectedHostname := "Hostname" + expectedHostname := types.Some("Hostname") TestCustomizations := Customizations{ - Hostname: &expectedHostname, + Hostname: expectedHostname, } retHostname := TestCustomizations.GetHostname() - assert.Equal(t, &expectedHostname, retHostname) + assert.Equal(t, expectedHostname, retHostname) } func TestGetKernel(t *testing.T) { @@ -88,7 +90,7 @@ func TestSSHKey(t *testing.T) { } retUser := TestCustomizations.GetUsers()[0].Name - retKey := *TestCustomizations.GetUsers()[0].Key + retKey := TestCustomizations.GetUsers()[0].Key.Unwrap() assert.Equal(t, expectedSSHKeys[0].User, retUser) assert.Equal(t, expectedSSHKeys[0].Key, retKey) @@ -111,16 +113,16 @@ func TestGetUsers(t *testing.T) { expectedUsers := []UserCustomization{ { Name: "John", - Description: &Desc, - Password: &Pass, - Key: &Key, - Home: &Home, - Shell: &Shell, + Description: types.Some(Desc), + Password: types.Some(Pass), + Key: types.Some(Key), + Home: types.Some(Home), + Shell: types.Some(Shell), Groups: Groups, - UID: &UID, - GID: &GID, - ExpireDate: &ExpireDate, - ForcePasswordReset: &ForcePasswordReset, + UID: types.Some(UID), + GID: types.Some(GID), + ExpireDate: types.Some(ExpireDate), + ForcePasswordReset: types.Some(ForcePasswordReset), }, } @@ -152,13 +154,13 @@ func TestGetGroups(t *testing.T) { } func TestGetTimezoneSettings(t *testing.T) { - expectedTimezone := "testZONE" + expectedTimezone := types.Some("testZONE") expectedNTPServers := []string{ "server", } expectedTimezoneCustomization := TimezoneCustomization{ - Timezone: &expectedTimezone, + Timezone: expectedTimezone, NTPServers: expectedNTPServers, } @@ -168,7 +170,7 @@ func TestGetTimezoneSettings(t *testing.T) { retTimezone, retNTPServers := TestCustomizations.GetTimezoneSettings() - assert.Equal(t, expectedTimezone, *retTimezone) + assert.Equal(t, expectedTimezone, retTimezone) assert.Equal(t, expectedNTPServers, retNTPServers) } diff --git a/pkg/container/client.go b/pkg/container/client.go index 4aea31ddab..0f8feb3195 100644 --- a/pkg/container/client.go +++ b/pkg/container/client.go @@ -30,7 +30,7 @@ import ( imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/osbuild/images/internal/common" + otypes "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" ) @@ -222,31 +222,31 @@ func (cl *Client) SetDockerCertPath(path string) { // SetSkipTLSVerify controls if TLS verification happens when // making requests. If nil is passed it falls back to the default. -func (cl *Client) SetTLSVerify(verify *bool) { - if verify == nil { +func (cl *Client) SetTLSVerify(verify otypes.Option[bool]) { + if verify.IsNone() { cl.sysCtx.DockerInsecureSkipTLSVerify = types.OptionalBoolUndefined } else { - cl.sysCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!*verify) + cl.sysCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!verify.Unwrap()) } } // GetSkipTLSVerify returns current TLS verification state. -func (cl *Client) GetTLSVerify() *bool { +func (cl *Client) GetTLSVerify() otypes.Option[bool] { skip := cl.sysCtx.DockerInsecureSkipTLSVerify if skip == types.OptionalBoolUndefined { - return nil + return otypes.None[bool]() } // NB: we invert the state, i.e. verify == (skip == false) - return common.ToPtr(skip == types.OptionalBoolFalse) + return otypes.Some(skip == types.OptionalBoolFalse) } // SkipTLSVerify is a convenience helper that internally calls // SetTLSVerify with false func (cl *Client) SkipTLSVerify() { - cl.SetTLSVerify(common.ToPtr(false)) + cl.SetTLSVerify(otypes.Some(false)) } func parseImageName(name string) (types.ImageReference, error) { diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index cda3752c76..38d34b6bc9 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -17,7 +17,7 @@ import ( "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" - "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/container" ) @@ -376,7 +376,7 @@ func (reg *Registry) Resolve(target string, imgArch arch.Arch) (container.Spec, Digest: checksum, ImageID: mf.ConfigDescriptor.Digest.String(), LocalName: target, - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), ListDigest: listDigest, Arch: imgArch, }, nil diff --git a/pkg/container/resolver.go b/pkg/container/resolver.go index 054cd8bd9a..ce698f9b91 100644 --- a/pkg/container/resolver.go +++ b/pkg/container/resolver.go @@ -5,6 +5,8 @@ import ( "fmt" "sort" "strings" + + "github.com/osbuild/images/internal/types" ) type resolveResult struct { @@ -28,7 +30,7 @@ type SourceSpec struct { Source string Name string Digest *string - TLSVerify *bool + TLSVerify types.Option[bool] Local bool } diff --git a/pkg/container/resolver_test.go b/pkg/container/resolver_test.go index 0f24b5a14d..55ab977bee 100644 --- a/pkg/container/resolver_test.go +++ b/pkg/container/resolver_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/container" ) @@ -49,7 +50,7 @@ func TestResolver(t *testing.T) { Source: r, Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: false, }) } @@ -77,7 +78,7 @@ func TestResolverFail(t *testing.T) { Source: "invalid-reference@${IMAGE_DIGEST}", Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: false, }) specs, err := resolver.Finish() @@ -91,7 +92,7 @@ func TestResolverFail(t *testing.T) { Source: registry.GetRef("repo"), Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: false, }) specs, err = resolver.Finish() @@ -102,7 +103,7 @@ func TestResolverFail(t *testing.T) { Source: registry.GetRef("repo"), Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: false, }) specs, err = resolver.Finish() @@ -113,7 +114,7 @@ func TestResolverFail(t *testing.T) { Source: registry.GetRef("repo"), Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: false, }) specs, err = resolver.Finish() @@ -178,7 +179,7 @@ func TestResolverLocalManifest(t *testing.T) { Source: "localhost/multi-arch", Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: true, }) specs, err := resolver.Finish() @@ -196,7 +197,7 @@ func TestResolverLocalManifest(t *testing.T) { Source: "localhost/multi-arch", Name: "", Digest: common.ToPtr(""), - TLSVerify: common.ToPtr(false), + TLSVerify: types.Some(false), Local: true, }) specs, err = resolver.Finish() diff --git a/pkg/container/spec.go b/pkg/container/spec.go index fc0dc5e688..39d629e3bd 100644 --- a/pkg/container/spec.go +++ b/pkg/container/spec.go @@ -4,6 +4,7 @@ import ( "github.com/containers/image/v5/docker/reference" "github.com/opencontainers/go-digest" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" ) @@ -13,12 +14,12 @@ import ( // at the Source via Digest and ImageID. The latter one // should remain the same in the target image as well. type Spec struct { - Source string // does not include the manifest digest - Digest string // digest of the manifest at the Source - TLSVerify *bool // controls TLS verification - ImageID string // container image identifier - LocalName string // name to use inside the image - ListDigest string // digest of the list manifest at the Source (optional) + Source string // does not include the manifest digest + Digest string // digest of the manifest at the Source + TLSVerify types.Option[bool] // controls TLS verification + ImageID string // container image identifier + LocalName string // name to use inside the image + ListDigest string // digest of the list manifest at the Source (optional) LocalStorage bool Arch arch.Arch // the architecture of the image @@ -27,7 +28,7 @@ type Spec struct { // NewSpec creates a new Spec from the essential information. // It also converts is the transition point from container // specific types (digest.Digest) to generic types (string). -func NewSpec(source reference.Named, digest, imageID digest.Digest, tlsVerify *bool, listDigest string, localName string, localStorage bool) Spec { +func NewSpec(source reference.Named, digest, imageID digest.Digest, tlsVerify types.Option[bool], listDigest string, localName string, localStorage bool) Spec { if localName == "" { localName = source.String() } diff --git a/pkg/customizations/kickstart/kickstart.go b/pkg/customizations/kickstart/kickstart.go index 4b5897d590..39a135fb8b 100644 --- a/pkg/customizations/kickstart/kickstart.go +++ b/pkg/customizations/kickstart/kickstart.go @@ -3,6 +3,7 @@ package kickstart import ( "fmt" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/blueprint" "github.com/osbuild/images/pkg/customizations/users" ) @@ -36,7 +37,7 @@ type Options struct { Language *string Keyboard *string - Timezone *string + Timezone types.Option[string] // Users to create during installation Users []users.User diff --git a/pkg/customizations/users/users.go b/pkg/customizations/users/users.go index efacd1b80d..06ba63e22b 100644 --- a/pkg/customizations/users/users.go +++ b/pkg/customizations/users/users.go @@ -1,19 +1,22 @@ package users -import "github.com/osbuild/images/pkg/blueprint" +import ( + "github.com/osbuild/images/internal/types" + "github.com/osbuild/images/pkg/blueprint" +) type User struct { Name string - Description *string - Password *string - Key *string - Home *string - Shell *string + Description types.Option[string] + Password types.Option[string] + Key types.Option[string] + Home types.Option[string] + Shell types.Option[string] Groups []string - UID *int - GID *int - ExpireDate *int - ForcePasswordReset *bool + UID types.Option[int] + GID types.Option[int] + ExpireDate types.Option[int] + ForcePasswordReset types.Option[bool] } type Group struct { diff --git a/pkg/distro/distro_test.go b/pkg/distro/distro_test.go index 56cc771c30..10b6a2e9d2 100644 --- a/pkg/distro/distro_test.go +++ b/pkg/distro/distro_test.go @@ -4,12 +4,15 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "slices" "strings" "testing" - "slices" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/blueprint" "github.com/osbuild/images/pkg/container" "github.com/osbuild/images/pkg/distro" @@ -17,8 +20,6 @@ import ( "github.com/osbuild/images/pkg/distrofactory" "github.com/osbuild/images/pkg/ostree" "github.com/osbuild/images/pkg/rpmmd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // Ensure that all package sets defined in the package set chains are defined for the image type @@ -597,7 +598,7 @@ func TestDistro_ManifestFIPSWarning(t *testing.T) { for _, imgTypeName := range arch.ListImageTypes() { bp := blueprint.Blueprint{ Customizations: &blueprint.Customizations{ - FIPS: &fips_enabled, + FIPS: types.Some(fips_enabled), }, } imgType, _ := arch.GetImageType(imgTypeName) diff --git a/pkg/distro/fedora/distro.go b/pkg/distro/fedora/distro.go index 246814a34c..6076910ac3 100644 --- a/pkg/distro/fedora/distro.go +++ b/pkg/distro/fedora/distro.go @@ -8,6 +8,7 @@ import ( "github.com/osbuild/images/internal/common" "github.com/osbuild/images/internal/environment" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/fsnode" "github.com/osbuild/images/pkg/customizations/oscap" @@ -357,7 +358,7 @@ var ( NoSElinux: common.ToPtr(true), ExcludeDocs: common.ToPtr(true), Locale: common.ToPtr("C.UTF-8"), - Timezone: common.ToPtr("Etc/UTC"), + Timezone: types.Some("Etc/UTC"), }, image: containerImage, bootable: false, @@ -377,7 +378,7 @@ var ( NoSElinux: common.ToPtr(true), ExcludeDocs: common.ToPtr(true), Locale: common.ToPtr("C.UTF-8"), - Timezone: common.ToPtr("Etc/UTC"), + Timezone: types.Some("Etc/UTC"), WSLConfig: &osbuild.WSLConfStageOptions{ Boot: osbuild.WSLConfBootOptions{ Systemd: true, @@ -435,7 +436,7 @@ type distribution struct { // Fedora based OS image configuration defaults var defaultDistroImageConfig = &distro.ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), Locale: common.ToPtr("en_US"), DefaultOSCAPDatastream: common.ToPtr(oscap.DefaultFedoraDatastream()), } diff --git a/pkg/distro/fedora/images.go b/pkg/distro/fedora/images.go index c40c42e943..2eed9a0338 100644 --- a/pkg/distro/fedora/images.go +++ b/pkg/distro/fedora/images.go @@ -104,17 +104,13 @@ func osCustomizations( osc.Keyboard = &imageConfig.Keyboard.Keymap } - if hostname := c.GetHostname(); hostname != nil { - osc.Hostname = *hostname - } else { - osc.Hostname = "localhost.localdomain" - } + osc.Hostname = c.GetHostname().TakeOr("localhost.localdomain") timezone, ntpServers := c.GetTimezoneSettings() - if timezone != nil { - osc.Timezone = *timezone - } else if imageConfig.Timezone != nil { - osc.Timezone = *imageConfig.Timezone + if timezone.IsSome() { + osc.Timezone = timezone + } else { + osc.Timezone = imageConfig.Timezone } if len(ntpServers) > 0 { @@ -417,7 +413,7 @@ func imageInstallerImage(workload workload.Workload, } img.Kickstart.Language = &img.OSCustomizations.Language img.Kickstart.Keyboard = img.OSCustomizations.Keyboard - img.Kickstart.Timezone = &img.OSCustomizations.Timezone + img.Kickstart.Timezone = img.OSCustomizations.Timezone if img.Kickstart.Unattended { // NOTE: this is not supported right now because the diff --git a/pkg/distro/image_config.go b/pkg/distro/image_config.go index 8cee15e3ef..8c1d7bbccd 100644 --- a/pkg/distro/image_config.go +++ b/pkg/distro/image_config.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/fsnode" "github.com/osbuild/images/pkg/customizations/shell" "github.com/osbuild/images/pkg/customizations/subscription" @@ -12,7 +13,7 @@ import ( // ImageConfig represents a (default) configuration applied to the image payload. type ImageConfig struct { - Timezone *string + Timezone types.Option[string] TimeSynchronization *osbuild.ChronyStageOptions Locale *string Keyboard *osbuild.KeymapStageOptions diff --git a/pkg/distro/image_config_test.go b/pkg/distro/image_config_test.go index 07f8e36e27..5a9ca474a8 100644 --- a/pkg/distro/image_config_test.go +++ b/pkg/distro/image_config_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/osbuild" ) @@ -19,7 +20,7 @@ func TestImageConfigInheritFrom(t *testing.T) { { name: "inheritance with overridden values", distroConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, @@ -57,7 +58,7 @@ func TestImageConfigInheritFrom(t *testing.T) { }, }, imageConfig: &ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { @@ -72,7 +73,7 @@ func TestImageConfigInheritFrom(t *testing.T) { }, }, expectedConfig: &ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { @@ -122,7 +123,7 @@ func TestImageConfigInheritFrom(t *testing.T) { { name: "empty image type configuration", distroConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, @@ -136,7 +137,7 @@ func TestImageConfigInheritFrom(t *testing.T) { }, imageConfig: &ImageConfig{}, expectedConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, @@ -153,7 +154,7 @@ func TestImageConfigInheritFrom(t *testing.T) { name: "empty distro configuration", distroConfig: &ImageConfig{}, imageConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, @@ -166,7 +167,7 @@ func TestImageConfigInheritFrom(t *testing.T) { DefaultTarget: common.ToPtr("multi-user.target"), }, expectedConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, @@ -183,7 +184,7 @@ func TestImageConfigInheritFrom(t *testing.T) { name: "empty distro configuration", distroConfig: nil, imageConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, @@ -196,7 +197,7 @@ func TestImageConfigInheritFrom(t *testing.T) { DefaultTarget: common.ToPtr("multi-user.target"), }, expectedConfig: &ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}}, }, diff --git a/pkg/distro/rhel/images.go b/pkg/distro/rhel/images.go index 7a20febcc3..9762447e57 100644 --- a/pkg/distro/rhel/images.go +++ b/pkg/distro/rhel/images.go @@ -118,15 +118,13 @@ func osCustomizations( } } - if hostname := c.GetHostname(); hostname != nil { - osc.Hostname = *hostname - } + osc.Hostname = c.GetHostname().Unwrap() timezone, ntpServers := c.GetTimezoneSettings() - if timezone != nil { - osc.Timezone = *timezone - } else if imageConfig.Timezone != nil { - osc.Timezone = *imageConfig.Timezone + if timezone.IsSome() { + osc.Timezone = timezone + } else { + osc.Timezone = imageConfig.Timezone } if len(ntpServers) > 0 { @@ -683,7 +681,7 @@ func ImageInstallerImage(workload workload.Workload, } img.Kickstart.Language = &img.OSCustomizations.Language img.Kickstart.Keyboard = img.OSCustomizations.Keyboard - img.Kickstart.Timezone = &img.OSCustomizations.Timezone + img.Kickstart.Timezone = img.OSCustomizations.Timezone installerConfig, err := t.getDefaultInstallerConfig() if err != nil { diff --git a/pkg/distro/rhel/rhel10/ami.go b/pkg/distro/rhel/rhel10/ami.go index 22026d27e3..44443be9c1 100644 --- a/pkg/distro/rhel/rhel10/ami.go +++ b/pkg/distro/rhel/rhel10/ami.go @@ -2,6 +2,7 @@ package rhel10 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" "github.com/osbuild/images/pkg/osbuild" @@ -15,7 +16,7 @@ const amiKernelOptions = "console=tty0 console=ttyS0,115200n8 nvme_core.io_timeo func baseEc2ImageConfig() *distro.ImageConfig { return &distro.ImageConfig{ Locale: common.ToPtr("en_US.UTF-8"), - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { diff --git a/pkg/distro/rhel/rhel10/azure.go b/pkg/distro/rhel/rhel10/azure.go index 6f565ed81f..ddacbc8141 100644 --- a/pkg/distro/rhel/rhel10/azure.go +++ b/pkg/distro/rhel/rhel10/azure.go @@ -2,6 +2,7 @@ package rhel10 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" "github.com/osbuild/images/pkg/osbuild" @@ -149,7 +150,7 @@ const defaultAzureKernelOptions = "ro loglevel=3 console=tty1 console=ttyS0 earl // based on https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/deploying_rhel_9_on_microsoft_azure/assembly_deploying-a-rhel-image-as-a-virtual-machine-on-microsoft-azure_cloud-content-azure#making-configuration-changes_configure-the-image-azure var defaultAzureImageConfig = &distro.ImageConfig{ - Timezone: common.ToPtr("Etc/UTC"), + Timezone: types.Some("Etc/UTC"), Locale: common.ToPtr("en_US.UTF-8"), Keyboard: &osbuild.KeymapStageOptions{ Keymap: "us", diff --git a/pkg/distro/rhel/rhel10/distro.go b/pkg/distro/rhel/rhel10/distro.go index 1b21cafbe4..714f3dd419 100644 --- a/pkg/distro/rhel/rhel10/distro.go +++ b/pkg/distro/rhel/rhel10/distro.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/oscap" "github.com/osbuild/images/pkg/distro" @@ -51,7 +52,7 @@ func distroISOLabelFunc(t *rhel.ImageType) string { func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig { return &distro.ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), Locale: common.ToPtr("C.UTF-8"), Sysconfig: []*osbuild.SysconfigStageOptions{ { diff --git a/pkg/distro/rhel/rhel7/ami.go b/pkg/distro/rhel/rhel7/ami.go index a88a310340..f13588d58a 100644 --- a/pkg/distro/rhel/rhel7/ami.go +++ b/pkg/distro/rhel/rhel7/ami.go @@ -4,6 +4,7 @@ import ( "os" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/fsnode" "github.com/osbuild/images/pkg/disk" @@ -74,7 +75,7 @@ func ec2ImageConfig() *distro.ImageConfig { } return &distro.ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { diff --git a/pkg/distro/rhel/rhel7/azure.go b/pkg/distro/rhel/rhel7/azure.go index d6c603f096..7b2bbd88f4 100644 --- a/pkg/distro/rhel/rhel7/azure.go +++ b/pkg/distro/rhel/rhel7/azure.go @@ -2,6 +2,7 @@ package rhel7 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/disk" @@ -41,7 +42,7 @@ func mkAzureRhuiImgType() *rhel.ImageType { } var azureDefaultImgConfig = &distro.ImageConfig{ - Timezone: common.ToPtr("Etc/UTC"), + Timezone: types.Some("Etc/UTC"), Locale: common.ToPtr("en_US.UTF-8"), GPGKeyFiles: []string{ "/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release", diff --git a/pkg/distro/rhel/rhel7/distro.go b/pkg/distro/rhel/rhel7/distro.go index 6a113f3f54..da2e0b1587 100644 --- a/pkg/distro/rhel/rhel7/distro.go +++ b/pkg/distro/rhel/rhel7/distro.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" @@ -14,7 +15,7 @@ import ( // RHEL-based OS image configuration defaults func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig { return &distro.ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), Locale: common.ToPtr("en_US.UTF-8"), GPGKeyFiles: []string{ "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release", diff --git a/pkg/distro/rhel/rhel8/ami.go b/pkg/distro/rhel/rhel8/ami.go index 12f8043933..4e1f507af4 100644 --- a/pkg/distro/rhel/rhel8/ami.go +++ b/pkg/distro/rhel/rhel8/ami.go @@ -2,6 +2,7 @@ package rhel8 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" @@ -160,7 +161,7 @@ func mkEc2SapImgTypeX86_64(rd *rhel.Distribution) *rhel.ImageType { // default EC2 images config (common for all architectures) func baseEc2ImageConfig() *distro.ImageConfig { return &distro.ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { diff --git a/pkg/distro/rhel/rhel8/azure.go b/pkg/distro/rhel/rhel8/azure.go index 5441ac65cd..680c5fe478 100644 --- a/pkg/distro/rhel/rhel8/azure.go +++ b/pkg/distro/rhel/rhel8/azure.go @@ -2,6 +2,7 @@ package rhel8 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/shell" "github.com/osbuild/images/pkg/customizations/subscription" @@ -504,7 +505,7 @@ func azureRhuiBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) // based on https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/deploying_rhel_8_on_microsoft_azure/assembly_deploying-a-rhel-image-as-a-virtual-machine-on-microsoft-azure_cloud-content-azure#making-configuration-changes_configure-the-image-azure var defaultAzureImageConfig = &distro.ImageConfig{ - Timezone: common.ToPtr("Etc/UTC"), + Timezone: types.Some("Etc/UTC"), Locale: common.ToPtr("en_US.UTF-8"), Keyboard: &osbuild.KeymapStageOptions{ Keymap: "us", diff --git a/pkg/distro/rhel/rhel8/distro.go b/pkg/distro/rhel/rhel8/distro.go index f35f4500e3..129086bafe 100644 --- a/pkg/distro/rhel/rhel8/distro.go +++ b/pkg/distro/rhel/rhel8/distro.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/oscap" "github.com/osbuild/images/pkg/distro" @@ -38,7 +39,7 @@ var ( // RHEL-based OS image configuration defaults func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig { return &distro.ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), Locale: common.ToPtr("en_US.UTF-8"), Sysconfig: []*osbuild.SysconfigStageOptions{ { diff --git a/pkg/distro/rhel/rhel8/gce.go b/pkg/distro/rhel/rhel8/gce.go index bd0ce8a5ef..4d388881d4 100644 --- a/pkg/distro/rhel/rhel8/gce.go +++ b/pkg/distro/rhel/rhel8/gce.go @@ -2,6 +2,7 @@ package rhel8 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" @@ -63,7 +64,7 @@ func mkGceRhuiImgType(rd distro.Distro) *rhel.ImageType { // https://issues.redhat.com/browse/COMPOSER-2157 func defaultGceByosImageConfig(rd distro.Distro) *distro.ImageConfig { ic := &distro.ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "metadata.google.internal"}}, }, diff --git a/pkg/distro/rhel/rhel9/ami.go b/pkg/distro/rhel/rhel9/ami.go index 5f5cf3fdbd..f76ca76a4f 100644 --- a/pkg/distro/rhel/rhel9/ami.go +++ b/pkg/distro/rhel/rhel9/ami.go @@ -2,6 +2,7 @@ package rhel9 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" @@ -16,7 +17,7 @@ const amiKernelOptions = "console=tty0 console=ttyS0,115200n8 net.ifnames=0 nvme func baseEc2ImageConfig() *distro.ImageConfig { return &distro.ImageConfig{ Locale: common.ToPtr("en_US.UTF-8"), - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { diff --git a/pkg/distro/rhel/rhel9/azure.go b/pkg/distro/rhel/rhel9/azure.go index 238207dbb2..3137a82d0e 100644 --- a/pkg/distro/rhel/rhel9/azure.go +++ b/pkg/distro/rhel/rhel9/azure.go @@ -2,6 +2,7 @@ package rhel9 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/disk" @@ -455,7 +456,7 @@ const defaultAzureKernelOptions = "ro loglevel=3 console=tty1 console=ttyS0 earl // based on https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/deploying_rhel_9_on_microsoft_azure/assembly_deploying-a-rhel-image-as-a-virtual-machine-on-microsoft-azure_cloud-content-azure#making-configuration-changes_configure-the-image-azure var defaultAzureImageConfig = &distro.ImageConfig{ - Timezone: common.ToPtr("Etc/UTC"), + Timezone: types.Some("Etc/UTC"), Locale: common.ToPtr("en_US.UTF-8"), Keyboard: &osbuild.KeymapStageOptions{ Keymap: "us", diff --git a/pkg/distro/rhel/rhel9/distro.go b/pkg/distro/rhel/rhel9/distro.go index 2e77b6be6a..23d630d4e5 100644 --- a/pkg/distro/rhel/rhel9/distro.go +++ b/pkg/distro/rhel/rhel9/distro.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/customizations/oscap" "github.com/osbuild/images/pkg/distro" @@ -54,7 +55,7 @@ func distroISOLabelFunc(t *rhel.ImageType) string { func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig { return &distro.ImageConfig{ - Timezone: common.ToPtr("America/New_York"), + Timezone: types.Some("America/New_York"), Locale: common.ToPtr("C.UTF-8"), Sysconfig: []*osbuild.SysconfigStageOptions{ { diff --git a/pkg/distro/rhel/rhel9/gce.go b/pkg/distro/rhel/rhel9/gce.go index be9cad1b3d..deba210794 100644 --- a/pkg/distro/rhel/rhel9/gce.go +++ b/pkg/distro/rhel/rhel9/gce.go @@ -2,6 +2,7 @@ package rhel9 import ( "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/distro/rhel" @@ -63,7 +64,7 @@ func mkGCERHUIImageType() *rhel.ImageType { func baseGCEImageConfig() *distro.ImageConfig { ic := &distro.ImageConfig{ - Timezone: common.ToPtr("UTC"), + Timezone: types.Some("UTC"), TimeSynchronization: &osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{{Hostname: "metadata.google.internal"}}, }, diff --git a/pkg/manifest/anaconda_installer.go b/pkg/manifest/anaconda_installer.go index bb82f5556d..80deb68775 100644 --- a/pkg/manifest/anaconda_installer.go +++ b/pkg/manifest/anaconda_installer.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/container" "github.com/osbuild/images/pkg/customizations/fsnode" @@ -217,7 +217,9 @@ func (p *AnacondaInstaller) serializeEnd() { func installerRootUser() osbuild.UsersStageOptionsUser { return osbuild.UsersStageOptionsUser{ - Password: common.ToPtr(""), + // lock root accont by default + // XXX: this could just be "Password: nil" + Password: types.Some(""), } } @@ -267,11 +269,11 @@ func (p *AnacondaInstaller) payloadStages() []*osbuild.Stage { installShell := "/usr/libexec/anaconda/run-anaconda" installPassword := "" installUser := osbuild.UsersStageOptionsUser{ - UID: &installUID, - GID: &installGID, - Home: &installHome, - Shell: &installShell, - Password: &installPassword, + UID: types.Some(installUID), + GID: types.Some(installGID), + Home: types.Some(installHome), + Shell: types.Some(installShell), + Password: types.Some(installPassword), } usersStageOptions := &osbuild.UsersStageOptions{ diff --git a/pkg/manifest/anaconda_installer_iso_tree.go b/pkg/manifest/anaconda_installer_iso_tree.go index 6978f78361..bcafe7ac52 100644 --- a/pkg/manifest/anaconda_installer_iso_tree.go +++ b/pkg/manifest/anaconda_installer_iso_tree.go @@ -598,10 +598,7 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic stageOptions.Keyboard = *kickstartOptions.Keyboard } - stageOptions.Timezone = "UTC" - if kickstartOptions.Timezone != nil { - stageOptions.Timezone = *kickstartOptions.Timezone - } + stageOptions.Timezone = kickstartOptions.Timezone.TakeOr("UTC") stageOptions.Reboot = &osbuild.RebootOptions{Eject: true} stageOptions.RootPassword = &osbuild.RootPasswordOptions{Lock: true} diff --git a/pkg/manifest/os.go b/pkg/manifest/os.go index 91911f84c1..c1de699c8d 100644 --- a/pkg/manifest/os.go +++ b/pkg/manifest/os.go @@ -9,6 +9,7 @@ import ( "github.com/osbuild/images/internal/common" "github.com/osbuild/images/internal/environment" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/internal/workload" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/container" @@ -78,7 +79,7 @@ type OSCustomizations struct { Keyboard *string X11KeymapLayouts []string Hostname string - Timezone string + Timezone types.Option[string] EnabledServices []string DisabledServices []string MaskedServices []string @@ -469,7 +470,7 @@ func (p *OS) serialize() osbuild.Pipeline { if p.Hostname != "" { pipeline.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: p.Hostname})) } - pipeline.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: p.Timezone})) + pipeline.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: p.Timezone.Unwrap()})) if len(p.NTPServers) > 0 { chronyOptions := &osbuild.ChronyStageOptions{Servers: p.NTPServers} @@ -850,7 +851,7 @@ func usersFirstBootOptions(users []users.User) *osbuild.FirstBootStageOptions { sshdir := filepath.Join(home, ".ssh") cmds = append(cmds, fmt.Sprintf("mkdir -p %s", sshdir)) - cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", *user.Key, filepath.Join(sshdir, "authorized_keys"))) + cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", user.Key.Unwrap(), filepath.Join(sshdir, "authorized_keys"))) cmds = append(cmds, fmt.Sprintf("chown %s:%s -Rc %s", user.Name, user.Name, sshdir)) } } diff --git a/pkg/manifest/ostree_deployment.go b/pkg/manifest/ostree_deployment.go index a6886013eb..73879aa95e 100644 --- a/pkg/manifest/ostree_deployment.go +++ b/pkg/manifest/ostree_deployment.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/container" "github.com/osbuild/images/pkg/customizations/fsnode" "github.com/osbuild/images/pkg/customizations/users" @@ -405,7 +406,8 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline { userOptions := &osbuild.UsersStageOptions{ Users: map[string]osbuild.UsersStageOptionsUser{ "root": { - Password: common.ToPtr("!locked"), // this is treated as crypted and locks/disables the password + // XXX: using "" or nil here is used in other places + Password: types.Some("!locked"), // this is treated as crypted and locks/disables the password }, }, } diff --git a/pkg/manifest/raw_bootc_test.go b/pkg/manifest/raw_bootc_test.go index c2a1aca134..faa3c76907 100644 --- a/pkg/manifest/raw_bootc_test.go +++ b/pkg/manifest/raw_bootc_test.go @@ -9,6 +9,7 @@ import ( "github.com/osbuild/images/internal/common" "github.com/osbuild/images/internal/testdisk" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/container" "github.com/osbuild/images/pkg/customizations/users" "github.com/osbuild/images/pkg/manifest" @@ -58,7 +59,7 @@ func TestRawBootcImageSerialize(t *testing.T) { rawBootcPipeline := manifest.NewRawBootcImage(build, containers, pf) rawBootcPipeline.PartitionTable = testdisk.MakeFakePartitionTable("/", "/boot", "/boot/efi") - rawBootcPipeline.Users = []users.User{{Name: "root", Key: common.ToPtr("some-ssh-key")}} + rawBootcPipeline.Users = []users.User{{Name: "root", Key: types.Some("some-ssh-key")}} rawBootcPipeline.KernelOptionsAppend = []string{"karg1", "karg2"} rawBootcPipeline.SerializeStart(nil, []container.Spec{{Source: "foo"}}, nil, nil) diff --git a/pkg/osbuild/skopeo_index_source.go b/pkg/osbuild/skopeo_index_source.go index d273fb1381..547fde40f4 100644 --- a/pkg/osbuild/skopeo_index_source.go +++ b/pkg/osbuild/skopeo_index_source.go @@ -2,6 +2,8 @@ package osbuild import ( "fmt" + + "github.com/osbuild/images/internal/types" ) type SkopeoIndexSource struct { @@ -11,8 +13,8 @@ type SkopeoIndexSource struct { func (SkopeoIndexSource) isSource() {} type SkopeoIndexSourceImage struct { - Name string `json:"name"` - TLSVerify *bool `json:"tls-verify,omitempty"` + Name string `json:"name"` + TLSVerify types.Option[bool] `json:"tls-verify,omitempty"` } type SkopeoIndexSourceItem struct { @@ -37,7 +39,7 @@ func NewSkopeoIndexSource() *SkopeoIndexSource { // AddItem adds a source item to the source; will panic // if any of the supplied options are invalid or missing -func (source *SkopeoIndexSource) AddItem(name, image string, tlsVerify *bool) { +func (source *SkopeoIndexSource) AddItem(name, image string, tlsVerify types.Option[bool]) { item := SkopeoIndexSourceItem{ Image: SkopeoIndexSourceImage{ Name: name, diff --git a/pkg/osbuild/skopeo_source.go b/pkg/osbuild/skopeo_source.go index 1145e59c50..d7fd69d69e 100644 --- a/pkg/osbuild/skopeo_source.go +++ b/pkg/osbuild/skopeo_source.go @@ -3,6 +3,8 @@ package osbuild import ( "fmt" "regexp" + + "github.com/osbuild/images/internal/types" ) var skopeoDigestPattern = regexp.MustCompile(`sha256:[0-9a-f]{64}`) @@ -17,9 +19,9 @@ type SkopeoSource struct { func (SkopeoSource) isSource() {} type SkopeopSourceImage struct { - Name string `json:"name,omitempty"` - Digest string `json:"digest,omitempty"` - TLSVerify *bool `json:"tls-verify,omitempty"` + Name string `json:"name,omitempty"` + Digest string `json:"digest,omitempty"` + TLSVerify types.Option[bool] `json:"tls-verify,omitempty"` } type SkopeoSourceItem struct { @@ -27,7 +29,7 @@ type SkopeoSourceItem struct { } // NewSkopeoSourceItem creates a new source item for name and digest -func NewSkopeoSourceItem(name, digest string, tlsVerify *bool) SkopeoSourceItem { +func NewSkopeoSourceItem(name, digest string, tlsVerify types.Option[bool]) SkopeoSourceItem { item := SkopeoSourceItem{ Image: SkopeopSourceImage{ Name: name, @@ -62,7 +64,7 @@ func NewSkopeoSource() *SkopeoSource { // AddItem adds a source item to the source; will panic // if any of the supplied options are invalid or missing -func (source *SkopeoSource) AddItem(name, digest, image string, tlsVerify *bool) { +func (source *SkopeoSource) AddItem(name, digest, image string, tlsVerify types.Option[bool]) { item := NewSkopeoSourceItem(name, digest, tlsVerify) if !skopeoDigestPattern.MatchString(image) { panic(fmt.Errorf("item %#v has invalid image id", image)) diff --git a/pkg/osbuild/skopeo_source_test.go b/pkg/osbuild/skopeo_source_test.go index 0e4fb14dcc..5c1dd078c8 100644 --- a/pkg/osbuild/skopeo_source_test.go +++ b/pkg/osbuild/skopeo_source_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/osbuild/images/internal/assertx" - "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" ) func TestNewSkopeoSource(t *testing.T) { @@ -16,14 +16,14 @@ func TestNewSkopeoSource(t *testing.T) { source := NewSkopeoSource() - source.AddItem("name", testDigest, imageID, common.ToPtr(false)) + source.AddItem("name", testDigest, imageID, types.Some(false)) assert.Len(t, source.Items, 1) item, ok := source.Items[imageID] assert.True(t, ok) assert.Equal(t, item.Image.Name, "name") assert.Equal(t, item.Image.Digest, testDigest) - assert.Equal(t, item.Image.TLSVerify, common.ToPtr(false), false) + assert.Equal(t, item.Image.TLSVerify, types.Some(false), false) testDigest = "sha256:d49eebefb6c7ce5505594bef652bd4adc36f413861bd44209d9b9486310b1264" imageID = "sha256:d2ab8fea7f08a22f03b30c13c6ea443121f25e87202a7496e93736efa6fe345a" diff --git a/pkg/osbuild/users_stage.go b/pkg/osbuild/users_stage.go index db9e451b4b..01bed57f40 100644 --- a/pkg/osbuild/users_stage.go +++ b/pkg/osbuild/users_stage.go @@ -1,6 +1,7 @@ package osbuild import ( + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/crypt" "github.com/osbuild/images/pkg/customizations/users" ) @@ -12,16 +13,16 @@ type UsersStageOptions struct { func (UsersStageOptions) isStageOptions() {} type UsersStageOptionsUser struct { - UID *int `json:"uid,omitempty"` - GID *int `json:"gid,omitempty"` - Groups []string `json:"groups,omitempty"` - Description *string `json:"description,omitempty"` - Home *string `json:"home,omitempty"` - Shell *string `json:"shell,omitempty"` - Password *string `json:"password,omitempty"` - Key *string `json:"key,omitempty"` - ExpireDate *int `json:"expiredate,omitempty"` - ForcePasswordReset *bool `json:"force_password_reset,omitempty"` + UID types.Option[int] `json:"uid,omitempty"` + GID types.Option[int] `json:"gid,omitempty"` + Groups []string `json:"groups,omitempty"` + Description types.Option[string] `json:"description,omitempty"` + Home types.Option[string] `json:"home,omitempty"` + Shell types.Option[string] `json:"shell,omitempty"` + Password types.Option[string] `json:"password,omitempty"` + Key types.Option[string] `json:"key,omitempty"` + ExpireDate types.Option[int] `json:"expiredate,omitempty"` + ForcePasswordReset types.Option[bool] `json:"force_password_reset,omitempty"` } func NewUsersStage(options *UsersStageOptions) *Stage { @@ -39,18 +40,18 @@ func NewUsersStageOptions(userCustomizations []users.User, omitKey bool) (*Users users := make(map[string]UsersStageOptionsUser, len(userCustomizations)) for _, uc := range userCustomizations { // Don't hash empty passwords, set to nil to lock account - if uc.Password != nil && len(*uc.Password) == 0 { + if len(uc.Password.Unwrap()) == 0 { uc.Password = nil } // Hash non-empty un-hashed passwords - if uc.Password != nil && !crypt.PasswordIsCrypted(*uc.Password) { - cryptedPassword, err := crypt.CryptSHA512(*uc.Password) + if uc.Password != nil && !crypt.PasswordIsCrypted(uc.Password.Unwrap()) { + cryptedPassword, err := crypt.CryptSHA512(uc.Password.Unwrap()) if err != nil { return nil, err } - uc.Password = &cryptedPassword + uc.Password = types.Some(cryptedPassword) } user := UsersStageOptionsUser{ diff --git a/pkg/osbuild/users_stage_test.go b/pkg/osbuild/users_stage_test.go index 866364d206..5d04ba69fe 100644 --- a/pkg/osbuild/users_stage_test.go +++ b/pkg/osbuild/users_stage_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/types" "github.com/osbuild/images/pkg/customizations/users" ) @@ -28,15 +28,15 @@ func TestNewUsersStageOptionsPassword(t *testing.T) { users := []users.User{ { Name: "bart", - Password: &Pass, + Password: types.Some(Pass), }, { Name: "lisa", - Password: &CryptPass, + Password: types.Some(CryptPass), }, { Name: "maggie", - Password: &EmptyPass, + Password: types.Some(EmptyPass), }, { Name: "homer", @@ -48,10 +48,10 @@ func TestNewUsersStageOptionsPassword(t *testing.T) { require.NotNil(t, options) // bart's password should now be a hash - assert.True(t, strings.HasPrefix(*options.Users["bart"].Password, "$6$")) + assert.True(t, strings.HasPrefix(options.Users["bart"].Password.Unwrap(), "$6$")) // lisa's password should be left alone (already hashed) - assert.Equal(t, CryptPass, *options.Users["lisa"].Password) + assert.Equal(t, CryptPass, options.Users["lisa"].Password.Unwrap()) // maggie's password should now be nil (locked account) assert.Nil(t, options.Users["maggie"].Password) @@ -63,24 +63,24 @@ func TestNewUsersStageOptionsPassword(t *testing.T) { func TestGenUsersStageSameAsNewUsersStageOptions(t *testing.T) { users := []users.User{ { - Name: "user1", UID: common.ToPtr(1000), GID: common.ToPtr(1000), + Name: "user1", UID: types.Some(1000), GID: types.Some(1000), Groups: []string{"grp1"}, - Description: common.ToPtr("some-descr"), - Home: common.ToPtr("/home/user1"), - Shell: common.ToPtr("/bin/zsh"), - Key: common.ToPtr("some-key"), + Description: types.Some("some-descr"), + Home: types.Some("/home/user1"), + Shell: types.Some("/bin/zsh"), + Key: types.Some("some-key"), }, } expected := &UsersStageOptions{ Users: map[string]UsersStageOptionsUser{ "user1": { - UID: common.ToPtr(1000), - GID: common.ToPtr(1000), + UID: types.Some(1000), + GID: types.Some(1000), Groups: []string{"grp1"}, - Description: common.ToPtr("some-descr"), - Home: common.ToPtr("/home/user1"), - Shell: common.ToPtr("/bin/zsh"), - Key: common.ToPtr("some-key")}, + Description: types.Some("some-descr"), + Home: types.Some("/home/user1"), + Shell: types.Some("/bin/zsh"), + Key: types.Some("some-key")}, }, }