diff --git a/examples/containerruntimeconfig.crd.yaml b/examples/containerruntimeconfig.crd.yaml index d73f2eedc3..7eb5443c20 100644 --- a/examples/containerruntimeconfig.crd.yaml +++ b/examples/containerruntimeconfig.crd.yaml @@ -9,3 +9,19 @@ spec: containerRuntimeConfig: pidsLimit: 2048 logLevel: debug +--- +apiVersion: machineconfiguration.openshift.io/v1 +kind: ContainerRuntimeConfig +metadata: + name: set-additional-stores +spec: + machineConfigPoolSelector: + matchLabels: + custom-crio: config-additional-stores + containerRuntimeConfig: + additionalLayerStores: + - path: /var/lib/stargz-store + additionalImageStores: + - path: /mnt/nfs-images + additionalArtifactStores: + - path: /mnt/ssd-artifacts diff --git a/pkg/controller/container-runtime-config/container_runtime_config_bootstrap.go b/pkg/controller/container-runtime-config/container_runtime_config_bootstrap.go index a00f47f4c8..598b659c8e 100644 --- a/pkg/controller/container-runtime-config/container_runtime_config_bootstrap.go +++ b/pkg/controller/container-runtime-config/container_runtime_config_bootstrap.go @@ -39,7 +39,9 @@ func RunContainerRuntimeBootstrap(templateDir string, crconfigs []*mcfgv1.Contai var configFileList []generatedConfigFile ctrcfg := cfg.Spec.ContainerRuntimeConfig - if ctrcfg.OverlaySize != nil && !ctrcfg.OverlaySize.IsZero() { + needsStorageConfig := (ctrcfg.OverlaySize != nil && !ctrcfg.OverlaySize.IsZero()) || + len(ctrcfg.AdditionalLayerStores) > 0 || len(ctrcfg.AdditionalImageStores) > 0 + if needsStorageConfig { storageTOML, err := mergeConfigChanges(originalStorageIgn, cfg, updateStorageConfig) if err != nil { klog.V(2).Infoln(cfg, err, "error merging user changes to storage.conf: %v", err) @@ -48,7 +50,9 @@ func RunContainerRuntimeBootstrap(templateDir string, crconfigs []*mcfgv1.Contai } } // Create the cri-o drop-in files - if ctrcfg.LogLevel != "" || ctrcfg.PidsLimit != nil || (ctrcfg.LogSizeMax != nil && !ctrcfg.LogSizeMax.IsZero()) || ctrcfg.DefaultRuntime != mcfgv1.ContainerRuntimeDefaultRuntimeEmpty { + needsCRIODropin := ctrcfg.LogLevel != "" || ctrcfg.PidsLimit != nil || (ctrcfg.LogSizeMax != nil && !ctrcfg.LogSizeMax.IsZero()) || ctrcfg.DefaultRuntime != mcfgv1.ContainerRuntimeDefaultRuntimeEmpty || + len(ctrcfg.AdditionalArtifactStores) > 0 + if needsCRIODropin { crioFileConfigs := createCRIODropinFiles(cfg) configFileList = append(configFileList, crioFileConfigs...) } diff --git a/pkg/controller/container-runtime-config/container_runtime_config_controller.go b/pkg/controller/container-runtime-config/container_runtime_config_controller.go index 390663c721..1d7df29743 100644 --- a/pkg/controller/container-runtime-config/container_runtime_config_controller.go +++ b/pkg/controller/container-runtime-config/container_runtime_config_controller.go @@ -363,6 +363,10 @@ func (ctrl *Controller) sigstoreAPIEnabled() bool { return ctrl.fgHandler.Enabled(features.FeatureGateSigstoreImageVerification) } +func (ctrl *Controller) additionalStorageConfigEnabled() bool { + return ctrl.fgHandler.Enabled(features.FeatureGateAdditionalStorageConfig) +} + func (ctrl *Controller) updateContainerRuntimeConfig(oldObj, newObj interface{}) { oldCtrCfg := oldObj.(*mcfgv1.ContainerRuntimeConfig) newCtrCfg := newObj.(*mcfgv1.ContainerRuntimeConfig) @@ -753,7 +757,10 @@ func (ctrl *Controller) syncContainerRuntimeConfig(key string) error { var configFileList []generatedConfigFile ctrcfg := cfg.Spec.ContainerRuntimeConfig - if ctrcfg.OverlaySize != nil && !ctrcfg.OverlaySize.IsZero() { + additionalStorageEnabled := ctrl.additionalStorageConfigEnabled() + needsStorageConfig := (ctrcfg.OverlaySize != nil && !ctrcfg.OverlaySize.IsZero()) || + (additionalStorageEnabled && (len(ctrcfg.AdditionalLayerStores) > 0 || len(ctrcfg.AdditionalImageStores) > 0)) + if needsStorageConfig { storageTOML, err := mergeConfigChanges(originalStorageIgn, cfg, updateStorageConfig) if err != nil { klog.V(2).Infoln(cfg, err, "error merging user changes to storage.conf: %v", err) @@ -765,7 +772,9 @@ func (ctrl *Controller) syncContainerRuntimeConfig(key string) error { } // Create the cri-o drop-in files - if ctrcfg.LogLevel != "" || ctrcfg.PidsLimit != nil || (ctrcfg.LogSizeMax != nil && !ctrcfg.LogSizeMax.IsZero()) || ctrcfg.DefaultRuntime != mcfgv1.ContainerRuntimeDefaultRuntimeEmpty { + needsCRIODropin := ctrcfg.LogLevel != "" || ctrcfg.PidsLimit != nil || (ctrcfg.LogSizeMax != nil && !ctrcfg.LogSizeMax.IsZero()) || ctrcfg.DefaultRuntime != mcfgv1.ContainerRuntimeDefaultRuntimeEmpty || + (additionalStorageEnabled && len(ctrcfg.AdditionalArtifactStores) > 0) + if needsCRIODropin { crioFileConfigs := createCRIODropinFiles(cfg) configFileList = append(configFileList, crioFileConfigs...) } diff --git a/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go b/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go index 0b25112b01..4737fbe284 100644 --- a/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go +++ b/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go @@ -2026,3 +2026,112 @@ func TestImagePolicyCreate(t *testing.T) { }) } } + +func TestContainerRuntimeConfigAdditionalStorageConfig(t *testing.T) { + for _, platform := range []apicfgv1.PlatformType{apicfgv1.AWSPlatformType, apicfgv1.NonePlatformType, "unrecognized"} { + t.Run(string(platform), func(t *testing.T) { + f := newFixture(t) + // Enable the AdditionalStorageConfig feature gate + f.fgHandler = ctrlcommon.NewFeatureGatesHardcodedHandler( + []apicfgv1.FeatureGateName{ + features.FeatureGateSigstoreImageVerification, + features.FeatureGateAdditionalStorageConfig, + }, + []apicfgv1.FeatureGateName{}, + ) + f.newController() + + cc := newControllerConfig(ctrlcommon.ControllerConfigName, platform) + mcp := helpers.NewMachineConfigPool("master", nil, helpers.MasterSelector, "v0") + mcp2 := helpers.NewMachineConfigPool("worker", nil, helpers.WorkerSelector, "v0") + ctrcfg := newContainerRuntimeConfig("set-additional-stores", &mcfgv1.ContainerRuntimeConfiguration{ + AdditionalLayerStores: []mcfgv1.AdditionalLayerStore{ + {Path: "/var/lib/stargz-store"}, + }, + AdditionalImageStores: []mcfgv1.AdditionalImageStore{ + {Path: "/mnt/nfs-images"}, + }, + AdditionalArtifactStores: []mcfgv1.AdditionalArtifactStore{ + {Path: "/mnt/ssd-artifacts"}, + }, + }, metav1.AddLabelToSelector(&metav1.LabelSelector{}, "pools.operator.machineconfiguration.openshift.io/master", "")) + ctrCfgKey, _ := getManagedKeyCtrCfg(mcp, f.client, ctrcfg) + mcs1 := helpers.NewMachineConfig(getManagedKeyCtrCfgDeprecated(mcp), map[string]string{"node-role": "master"}, "dummy://", []ign3types.File{{}}) + mcs2 := mcs1.DeepCopy() + mcs2.Name = ctrCfgKey + + f.ccLister = append(f.ccLister, cc) + f.mcpLister = append(f.mcpLister, mcp) + f.mcpLister = append(f.mcpLister, mcp2) + f.mccrLister = append(f.mccrLister, ctrcfg) + f.objects = append(f.objects, ctrcfg) + + f.expectGetMachineConfigAction(mcs2) + f.expectGetMachineConfigAction(mcs1) + f.expectGetMachineConfigAction(mcs1) + f.expectUpdateContainerRuntimeConfig(ctrcfg) + f.expectUpdateContainerRuntimeConfigRoot(ctrcfg) + f.expectCreateMachineConfigAction(mcs1) + f.expectPatchContainerRuntimeConfig(ctrcfg, ctrcfgPatchBytes) + f.expectGetMachineConfigAction(mcs2) + f.expectUpdateContainerRuntimeConfig(ctrcfg) + + f.run(getKey(ctrcfg, t)) + }) + } +} + +func TestContainerRuntimeConfigAdditionalStorageConfigFeatureGateDisabled(t *testing.T) { + for _, platform := range []apicfgv1.PlatformType{apicfgv1.AWSPlatformType, apicfgv1.NonePlatformType, "unrecognized"} { + t.Run(string(platform), func(t *testing.T) { + f := newFixture(t) + // Disable the AdditionalStorageConfig feature gate + f.fgHandler = ctrlcommon.NewFeatureGatesHardcodedHandler( + []apicfgv1.FeatureGateName{features.FeatureGateSigstoreImageVerification}, + []apicfgv1.FeatureGateName{features.FeatureGateAdditionalStorageConfig}, + ) + f.newController() + + cc := newControllerConfig(ctrlcommon.ControllerConfigName, platform) + mcp := helpers.NewMachineConfigPool("master", nil, helpers.MasterSelector, "v0") + mcp2 := helpers.NewMachineConfigPool("worker", nil, helpers.WorkerSelector, "v0") + // Even if these fields are set, the controller should not generate config + // because the feature gate is disabled + ctrcfg := newContainerRuntimeConfig("set-additional-stores-disabled", &mcfgv1.ContainerRuntimeConfiguration{ + AdditionalLayerStores: []mcfgv1.AdditionalLayerStore{ + {Path: "/var/lib/stargz-store"}, + }, + AdditionalImageStores: []mcfgv1.AdditionalImageStore{ + {Path: "/mnt/nfs-images"}, + }, + AdditionalArtifactStores: []mcfgv1.AdditionalArtifactStore{ + {Path: "/mnt/ssd-artifacts"}, + }, + }, metav1.AddLabelToSelector(&metav1.LabelSelector{}, "pools.operator.machineconfiguration.openshift.io/master", "")) + ctrCfgKey, _ := getManagedKeyCtrCfg(mcp, f.client, ctrcfg) + mcs1 := helpers.NewMachineConfig(getManagedKeyCtrCfgDeprecated(mcp), map[string]string{"node-role": "master"}, "dummy://", []ign3types.File{{}}) + mcs2 := mcs1.DeepCopy() + mcs2.Name = ctrCfgKey + + f.ccLister = append(f.ccLister, cc) + f.mcpLister = append(f.mcpLister, mcp) + f.mcpLister = append(f.mcpLister, mcp2) + f.mccrLister = append(f.mccrLister, ctrcfg) + f.objects = append(f.objects, ctrcfg) + + // When only additional stores are set and the feature gate is disabled, + // the controller creates an MC with empty ignition config (no storage/crio files). + // No intermediate syncStatusOnly because needsStorageConfig is false. + f.expectGetMachineConfigAction(mcs2) + f.expectGetMachineConfigAction(mcs1) + f.expectGetMachineConfigAction(mcs1) + f.expectUpdateContainerRuntimeConfigRoot(ctrcfg) + f.expectCreateMachineConfigAction(mcs1) + f.expectPatchContainerRuntimeConfig(ctrcfg, ctrcfgPatchBytes) + f.expectGetMachineConfigAction(mcs2) + f.expectUpdateContainerRuntimeConfig(ctrcfg) + + f.run(getKey(ctrcfg, t)) + }) + } +} diff --git a/pkg/controller/container-runtime-config/helpers.go b/pkg/controller/container-runtime-config/helpers.go index 85245534ca..7f81f6ae97 100644 --- a/pkg/controller/container-runtime-config/helpers.go +++ b/pkg/controller/container-runtime-config/helpers.go @@ -49,12 +49,15 @@ const ( policyConfigPath = "/etc/containers/policy.json" // CRIODropInFilePathLogLevel is the path at which changes to the crio config for log-level // will be dropped in this is exported so that we can use it in the e2e-tests - CRIODropInFilePathLogLevel = "/etc/crio/crio.conf.d/01-ctrcfg-logLevel" - crioDropInFilePathPidsLimit = "/etc/crio/crio.conf.d/01-ctrcfg-pidsLimit" - crioDropInFilePathLogSizeMax = "/etc/crio/crio.conf.d/01-ctrcfg-logSizeMax" - CRIODropInFilePathDefaultRuntime = "/etc/crio/crio.conf.d/01-ctrcfg-defaultRuntime" - imagepolicyType = "sigstoreSigned" - sigstoreRegistriesConfigFilePath = "/etc/containers/registries.d/sigstore-registries.yaml" + CRIODropInFilePathLogLevel = "/etc/crio/crio.conf.d/01-ctrcfg-logLevel" + crioDropInFilePathPidsLimit = "/etc/crio/crio.conf.d/01-ctrcfg-pidsLimit" + crioDropInFilePathLogSizeMax = "/etc/crio/crio.conf.d/01-ctrcfg-logSizeMax" + // CRIODropInFilePathDefaultRuntime is the path at which changes to the crio config for default-runtime + // will be dropped in this is exported so that we can use it in the e2e-tests + CRIODropInFilePathDefaultRuntime = "/etc/crio/crio.conf.d/01-ctrcfg-defaultRuntime" + crioDropInFilePathAdditionalArtifactStores = "/etc/crio/crio.conf.d/01-ctrcfg-additionalArtifactStores" + imagepolicyType = "sigstoreSigned" + sigstoreRegistriesConfigFilePath = "/etc/containers/registries.d/sigstore-registries.yaml" ) var ( @@ -125,6 +128,17 @@ type tomlConfigCRIODefaultRuntime struct { } `toml:"crio"` } +// tomlConfigCRIOAdditionalArtifactStores is used for conversions when additional-artifact-stores is changed +// TOML-friendly (it has all of the explicit tables). It's just used for +// conversions. +type tomlConfigCRIOAdditionalArtifactStores struct { + Crio struct { + Runtime struct { + AdditionalArtifactStores []string `toml:"additional_artifact_stores,omitempty"` + } `toml:"runtime"` + } `toml:"crio"` +} + type dockerConfig struct { UseSigstoreAttachments bool `json:"use-sigstore-attachments,omitempty"` } @@ -387,6 +401,22 @@ func updateStorageConfig(data []byte, internal *mcfgv1.ContainerRuntimeConfigura } } + if len(internal.AdditionalLayerStores) > 0 { + paths := make([]string, 0, len(internal.AdditionalLayerStores)) + for _, store := range internal.AdditionalLayerStores { + paths = append(paths, store.Path) + } + tomlConf.Storage.Options.AdditionalLayerStores = paths + } + + if len(internal.AdditionalImageStores) > 0 { + paths := make([]string, 0, len(internal.AdditionalImageStores)) + for _, store := range internal.AdditionalImageStores { + paths = append(paths, store.Path) + } + tomlConf.Storage.Options.AdditionalImageStores = paths + } + var newData bytes.Buffer encoder := toml.NewEncoder(&newData) if err := encoder.Encode(*tomlConf); err != nil { @@ -448,6 +478,18 @@ func createCRIODropinFiles(cfg *mcfgv1.ContainerRuntimeConfig) []generatedConfig klog.V(2).Infoln(cfg, err, "error updating user changes for default-runtime to crio.conf.d: %v", err) } } + if len(ctrcfg.AdditionalArtifactStores) > 0 { + tomlConf := tomlConfigCRIOAdditionalArtifactStores{} + paths := make([]string, 0, len(ctrcfg.AdditionalArtifactStores)) + for _, store := range ctrcfg.AdditionalArtifactStores { + paths = append(paths, store.Path) + } + tomlConf.Crio.Runtime.AdditionalArtifactStores = paths + generatedConfigFileList, err = addTOMLgeneratedConfigFile(generatedConfigFileList, crioDropInFilePathAdditionalArtifactStores, tomlConf) + if err != nil { + klog.V(2).Infoln(cfg, err, "error updating user changes for additional-artifact-stores to crio.conf.d: %v", err) + } + } return generatedConfigFileList } diff --git a/pkg/controller/container-runtime-config/helpers_test.go b/pkg/controller/container-runtime-config/helpers_test.go index 9d6c7b38e8..7780dd3a83 100644 --- a/pkg/controller/container-runtime-config/helpers_test.go +++ b/pkg/controller/container-runtime-config/helpers_test.go @@ -1348,6 +1348,138 @@ func TestUpdateStorageConfig(t *testing.T) { } } +func TestUpdateStorageConfigAdditionalStores(t *testing.T) { + templateStorageConfig := tomlConfigStorage{} + buf := bytes.Buffer{} + err := toml.NewEncoder(&buf).Encode(templateStorageConfig) + require.NoError(t, err) + templateBytes := buf.Bytes() + + tests := []struct { + name string + cfg *mcfgv1.ContainerRuntimeConfiguration + want tomlConfigStorage + }{ + { + name: "apply additional layer stores", + cfg: &mcfgv1.ContainerRuntimeConfiguration{ + AdditionalLayerStores: []mcfgv1.AdditionalLayerStore{ + {Path: "/var/lib/stargz-store"}, + {Path: "/mnt/nfs-layers"}, + }, + }, + want: tomlConfigStorage{ + Storage: struct { + Driver string "toml:\"driver\"" + RunRoot string "toml:\"runroot\"" + GraphRoot string "toml:\"graphroot\"" + Options struct{ storageconfig.OptionsConfig } "toml:\"options\"" + }{ + Options: struct{ storageconfig.OptionsConfig }{ + storageconfig.OptionsConfig{ + AdditionalLayerStores: []string{"/var/lib/stargz-store", "/mnt/nfs-layers"}, + }, + }, + }, + }, + }, + { + name: "apply additional image stores", + cfg: &mcfgv1.ContainerRuntimeConfiguration{ + AdditionalImageStores: []mcfgv1.AdditionalImageStore{ + {Path: "/mnt/nfs-images"}, + {Path: "/mnt/ssd-images"}, + }, + }, + want: tomlConfigStorage{ + Storage: struct { + Driver string "toml:\"driver\"" + RunRoot string "toml:\"runroot\"" + GraphRoot string "toml:\"graphroot\"" + Options struct{ storageconfig.OptionsConfig } "toml:\"options\"" + }{ + Options: struct{ storageconfig.OptionsConfig }{ + storageconfig.OptionsConfig{ + AdditionalImageStores: []string{"/mnt/nfs-images", "/mnt/ssd-images"}, + }, + }, + }, + }, + }, + { + name: "apply both additional layer and image stores with overlay size", + cfg: func() *mcfgv1.ContainerRuntimeConfiguration { + overlaySize := resource.MustParse("10G") + return &mcfgv1.ContainerRuntimeConfiguration{ + OverlaySize: &overlaySize, + AdditionalLayerStores: []mcfgv1.AdditionalLayerStore{ + {Path: "/var/lib/stargz-store"}, + }, + AdditionalImageStores: []mcfgv1.AdditionalImageStore{ + {Path: "/mnt/nfs-images"}, + }, + } + }(), + want: tomlConfigStorage{ + Storage: struct { + Driver string "toml:\"driver\"" + RunRoot string "toml:\"runroot\"" + GraphRoot string "toml:\"graphroot\"" + Options struct{ storageconfig.OptionsConfig } "toml:\"options\"" + }{ + Options: struct{ storageconfig.OptionsConfig }{ + storageconfig.OptionsConfig{ + Size: "10G", + AdditionalLayerStores: []string{"/var/lib/stargz-store"}, + AdditionalImageStores: []string{"/mnt/nfs-images"}, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := updateStorageConfig(templateBytes, test.cfg) + require.NoError(t, err) + gotConf := tomlConfigStorage{} + _, err = toml.Decode(string(got), &gotConf) + require.NoError(t, err) + if !reflect.DeepEqual(gotConf, test.want) { + t.Errorf("failed.\ngot: %+v\nwant: %+v", gotConf, test.want) + } + }) + } +} + +func TestCreateCRIODropinFilesAdditionalArtifactStores(t *testing.T) { + cfg := &mcfgv1.ContainerRuntimeConfig{ + Spec: mcfgv1.ContainerRuntimeConfigSpec{ + ContainerRuntimeConfig: &mcfgv1.ContainerRuntimeConfiguration{ + AdditionalArtifactStores: []mcfgv1.AdditionalArtifactStore{ + {Path: "/mnt/ssd-artifacts"}, + {Path: "/mnt/nfs-artifacts"}, + }, + }, + }, + } + + files := createCRIODropinFiles(cfg) + + var found bool + for _, f := range files { + if f.filePath == crioDropInFilePathAdditionalArtifactStores { + found = true + gotConf := tomlConfigCRIOAdditionalArtifactStores{} + _, err := toml.Decode(string(f.data), &gotConf) + require.NoError(t, err) + assert.Equal(t, []string{"/mnt/ssd-artifacts", "/mnt/nfs-artifacts"}, gotConf.Crio.Runtime.AdditionalArtifactStores) + } + } + assert.True(t, found, "expected to find additional artifact stores drop-in file at %s", crioDropInFilePathAdditionalArtifactStores) +} + func TestGetValidScopePolicies(t *testing.T) { type testcase struct { name string diff --git a/vendor/github.com/openshift/api/features/features.go b/vendor/github.com/openshift/api/features/features.go index 36a479071a..4202d7fc35 100644 --- a/vendor/github.com/openshift/api/features/features.go +++ b/vendor/github.com/openshift/api/features/features.go @@ -361,6 +361,14 @@ var ( enableIn(configv1.Default, configv1.OKD, configv1.DevPreviewNoUpgrade, configv1.TechPreviewNoUpgrade). mustRegister() + FeatureGateAdditionalStorageConfig = newFeatureGate("AdditionalStorageConfig"). + reportProblemsToJiraComponent("node"). + contactPerson("saschagrunert"). + productScope(ocpSpecific). + enhancementPR("https://github.com/openshift/enhancements/pull/1934"). + enableIn(configv1.DevPreviewNoUpgrade, configv1.TechPreviewNoUpgrade). + mustRegister() + FeatureGateUpgradeStatus = newFeatureGate("UpgradeStatus"). reportProblemsToJiraComponent("Cluster Version Operator"). contactPerson("pmuller"). diff --git a/vendor/github.com/openshift/api/machineconfiguration/v1/types.go b/vendor/github.com/openshift/api/machineconfiguration/v1/types.go index 6673adeb1b..0870585e79 100644 --- a/vendor/github.com/openshift/api/machineconfiguration/v1/types.go +++ b/vendor/github.com/openshift/api/machineconfiguration/v1/types.go @@ -892,6 +892,60 @@ type ContainerRuntimeConfiguration struct { // +kubebuilder:validation:Enum=crun;runc // +optional DefaultRuntime ContainerRuntimeDefaultRuntime `json:"defaultRuntime,omitempty"` + + // additionalLayerStores configures additional read-only container image layer store locations for Open Container Initiative (OCI) images. + // + // Layers are checked in order: additional stores first, then the default location. + // Stores are read-only. + // Maximum of 5 stores allowed. + // Each path must be unique. + // + // When omitted, only the default layer location is used. + // When specified, at least one store must be provided. + // + // +openshift:enable:FeatureGate=AdditionalStorageConfig + // +optional + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=5 + // +kubebuilder:validation:XValidation:rule="self.all(x, self.exists_one(y, x.path == y.path))",message="additionalLayerStores must not contain duplicate paths" + AdditionalLayerStores []AdditionalLayerStore `json:"additionalLayerStores,omitempty"` + + // additionalImageStores configures additional read-only container image store locations for Open Container Initiative (OCI) images. + // + // Images are checked in order: additional stores first, then the default location. + // Stores are read-only. + // Maximum of 10 stores allowed. + // Each path must be unique. + // + // When omitted, only the default image location is used. + // When specified, at least one store must be provided. + // + // +openshift:enable:FeatureGate=AdditionalStorageConfig + // +optional + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=10 + // +kubebuilder:validation:XValidation:rule="self.all(x, self.exists_one(y, x.path == y.path))",message="additionalImageStores must not contain duplicate paths" + AdditionalImageStores []AdditionalImageStore `json:"additionalImageStores,omitempty"` + + // additionalArtifactStores configures additional read-only artifact storage locations for Open Container Initiative (OCI) artifacts. + // + // Artifacts are checked in order: additional stores first, then the default location (/var/lib/containers/storage/artifacts). + // Stores are read-only. + // Maximum of 10 stores allowed. + // Each path must be unique. + // + // When omitted, only the default artifact location is used. + // When specified, at least one store must be provided. + // + // +openshift:enable:FeatureGate=AdditionalStorageConfig + // +optional + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=10 + // +kubebuilder:validation:XValidation:rule="self.all(x, self.exists_one(y, x.path == y.path))",message="additionalArtifactStores must not contain duplicate paths" + AdditionalArtifactStores []AdditionalArtifactStore `json:"additionalArtifactStores,omitempty"` } type ContainerRuntimeDefaultRuntime string @@ -904,6 +958,66 @@ const ( ContainerRuntimeDefaultRuntimeDefault = ContainerRuntimeDefaultRuntimeCrun ) +// AdditionalLayerStore defines a read-only storage location for Open Container Initiative (OCI) container image layers. +type AdditionalLayerStore struct { + // path specifies the absolute location of the additional layer store. + // + // The path must exist on the node before configuration is applied. + // When a container image is requested, layers found at this location will be used instead of + // retrieving from the registry. + // + // This field is required and must: + // - Have length between 1 and 256 characters + // - Start with '/' (absolute path) + // - Contain only: a-z, A-Z, 0-9, '/', '.', '_', '-' (no spaces or special characters) + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.matches('^/[a-zA-Z0-9/._-]+$')",message="path must be absolute and contain only alphanumeric characters, '/', '.', '_', and '-'" + Path string `json:"path,omitempty"` +} + +// AdditionalImageStore defines an additional read-only storage location for Open Container Initiative (OCI) images. +type AdditionalImageStore struct { + // path specifies the absolute location of the additional image store. + // + // The path must exist on the node before configuration is applied. + // When a container image is requested, images found at this location will be used instead of + // retrieving from the registry. + // + // This field is required and must: + // - Have length between 1 and 256 characters + // - Start with '/' (absolute path) + // - Contain only: a-z, A-Z, 0-9, '/', '.', '_', '-' (no spaces or special characters) + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.matches('^/[a-zA-Z0-9/._-]+$')",message="path must be absolute and contain only alphanumeric characters, '/', '.', '_', and '-'" + Path string `json:"path,omitempty"` +} + +// AdditionalArtifactStore defines an additional read-only storage location for Open Container Initiative (OCI) artifacts. +type AdditionalArtifactStore struct { + // path specifies the absolute location of the additional artifact store. + // + // The path must exist on the node before configuration is applied. + // When an artifact is requested, artifacts found at this location will be used instead of + // retrieving from the registry. + // + // This field is required and must: + // - Have length between 1 and 256 characters + // - Start with '/' (absolute path) + // - Contain only: a-z, A-Z, 0-9, '/', '.', '_', '-' (no spaces or special characters) + // + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.matches('^/[a-zA-Z0-9/._-]+$')",message="path must be absolute and contain only alphanumeric characters, '/', '.', '_', and '-'" + Path string `json:"path,omitempty"` +} + // ContainerRuntimeConfigStatus defines the observed state of a ContainerRuntimeConfig type ContainerRuntimeConfigStatus struct { // observedGeneration represents the generation observed by the controller. diff --git a/vendor/github.com/openshift/api/machineconfiguration/v1/zz_generated.deepcopy.go b/vendor/github.com/openshift/api/machineconfiguration/v1/zz_generated.deepcopy.go index 5061d8b822..a42a2f36ed 100644 --- a/vendor/github.com/openshift/api/machineconfiguration/v1/zz_generated.deepcopy.go +++ b/vendor/github.com/openshift/api/machineconfiguration/v1/zz_generated.deepcopy.go @@ -13,6 +13,54 @@ import ( intstr "k8s.io/apimachinery/pkg/util/intstr" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalArtifactStore) DeepCopyInto(out *AdditionalArtifactStore) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalArtifactStore. +func (in *AdditionalArtifactStore) DeepCopy() *AdditionalArtifactStore { + if in == nil { + return nil + } + out := new(AdditionalArtifactStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalImageStore) DeepCopyInto(out *AdditionalImageStore) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalImageStore. +func (in *AdditionalImageStore) DeepCopy() *AdditionalImageStore { + if in == nil { + return nil + } + out := new(AdditionalImageStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalLayerStore) DeepCopyInto(out *AdditionalLayerStore) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalLayerStore. +func (in *AdditionalLayerStore) DeepCopy() *AdditionalLayerStore { + if in == nil { + return nil + } + out := new(AdditionalLayerStore) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CertExpiry) DeepCopyInto(out *CertExpiry) { *out = *in @@ -178,6 +226,21 @@ func (in *ContainerRuntimeConfiguration) DeepCopyInto(out *ContainerRuntimeConfi x := (*in).DeepCopy() *out = &x } + if in.AdditionalLayerStores != nil { + in, out := &in.AdditionalLayerStores, &out.AdditionalLayerStores + *out = make([]AdditionalLayerStore, len(*in)) + copy(*out, *in) + } + if in.AdditionalImageStores != nil { + in, out := &in.AdditionalImageStores, &out.AdditionalImageStores + *out = make([]AdditionalImageStore, len(*in)) + copy(*out, *in) + } + if in.AdditionalArtifactStores != nil { + in, out := &in.AdditionalArtifactStores, &out.AdditionalArtifactStores + *out = make([]AdditionalArtifactStore, len(*in)) + copy(*out, *in) + } return }