From 0632e3cc2f83f1e7f98b956160e7ceb1fde6a250 Mon Sep 17 00:00:00 2001 From: Andrea Fasano Date: Tue, 9 Dec 2025 09:47:56 -0500 Subject: [PATCH 1/3] add tls certificate to InternalReleaseImage registry This patch injects the specific tls certificate generated by the installer into the IRI registry. It also adds the root CA certificate to all the master nodes, to allow pulling the images from other nodes registry --- pkg/controller/bootstrap/bootstrap.go | 13 +- pkg/controller/common/constants.go | 3 + .../internalreleaseimage_bootstrap.go | 8 +- .../internalreleaseimage_bootstrap_test.go | 17 +- .../internalreleaseimage_controller.go | 210 ++++++++++++++---- .../internalreleaseimage_controller_test.go | 144 +++--------- .../internalreleaseimage_helpers_test.go | 175 +++++++++++++++ .../templates/master/files/root-ca.yaml | 5 + .../templates/master/files/tls-crt.yaml | 5 + .../templates/master/files/tls-key.yaml | 5 + .../units}/iri-registry.service.yaml | 3 +- 11 files changed, 409 insertions(+), 179 deletions(-) create mode 100644 pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go create mode 100644 pkg/controller/internalreleaseimage/templates/master/files/root-ca.yaml create mode 100644 pkg/controller/internalreleaseimage/templates/master/files/tls-crt.yaml create mode 100644 pkg/controller/internalreleaseimage/templates/master/files/tls-key.yaml rename pkg/controller/internalreleaseimage/templates/{ => master/units}/iri-registry.service.yaml (67%) diff --git a/pkg/controller/bootstrap/bootstrap.go b/pkg/controller/bootstrap/bootstrap.go index b99ad49859..396888c9b1 100644 --- a/pkg/controller/bootstrap/bootstrap.go +++ b/pkg/controller/bootstrap/bootstrap.go @@ -102,6 +102,7 @@ func (b *Bootstrap) Run(destDir string) error { imgCfg *apicfgv1.Image apiServer *apicfgv1.APIServer iri *mcfgv1alpha1.InternalReleaseImage + iriTLSCert *corev1.Secret ) for _, info := range infos { if info.IsDir() { @@ -114,6 +115,10 @@ func (b *Bootstrap) Run(destDir string) error { } defer file.Close() + if info.Name() == "internal-release-image-tls-secret.yaml" { + _ = "" + } + manifests, err := parseManifests(file.Name(), file) if err != nil { return fmt.Errorf("error parsing manifests from %s: %w", file.Name(), err) @@ -171,6 +176,10 @@ func (b *Bootstrap) Run(destDir string) error { if obj.GetName() == ctrlcommon.InternalReleaseImageInstanceName { iri = obj } + case *corev1.Secret: + if obj.GetName() == ctrlcommon.InternalReleaseImageTLSSecretName { + iriTLSCert = obj + } default: klog.Infof("skipping %q [%d] manifest because of unhandled %T", file.Name(), idx+1, obji) } @@ -253,11 +262,11 @@ func (b *Bootstrap) Run(destDir string) error { if fgHandler != nil && fgHandler.Enabled(features.FeatureGateNoRegistryClusterInstall) { if iri != nil { - iriConfig, err := internalreleaseimage.RunInternalReleaseImageBootstrap(iri, cconfig) + configs, err := internalreleaseimage.RunInternalReleaseImageBootstrap(iri, iriTLSCert, cconfig) if err != nil { return err } - configs = append(configs, iriConfig) + configs = append(configs, configs...) klog.Infof("Successfully generated MachineConfig from InternalReleaseImage.") } } diff --git a/pkg/controller/common/constants.go b/pkg/controller/common/constants.go index d3e4c7578c..6ed6fb8bb9 100644 --- a/pkg/controller/common/constants.go +++ b/pkg/controller/common/constants.go @@ -66,6 +66,9 @@ const ( // InternalReleaseImageInstanceName is a singleton name for InternalReleaseImage InternalReleaseImageInstanceName = "cluster" + // InternalReleaseImageTLSSecretName is the name of the secret manifest containing the IntennalReleaseImage TLS certificate. + InternalReleaseImageTLSSecretName = "internal-release-image-tls" + // APIServerInstanceName is a singleton name for APIServer configuration APIServerBootstrapFileLocation = "/etc/mcs/bootstrap/api-server/api-server.yaml" diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go index 700e02ced2..07a9d6cbdf 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go @@ -1,11 +1,13 @@ package internalreleaseimage import ( + corev1 "k8s.io/api/core/v1" + mcfgv1 "github.com/openshift/api/machineconfiguration/v1" mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" ) -// RunInternalReleaseImageBootstrap generates a MachineConfig object for InternalReleaseImage that would have been generated by syncInternalReleaseImage -func RunInternalReleaseImageBootstrap(iri *mcfgv1alpha1.InternalReleaseImage, controllerConfig *mcfgv1.ControllerConfig) (*mcfgv1.MachineConfig, error) { - return generateInternalReleaseImageMachineConfig(iri, controllerConfig) +// RunInternalReleaseImageBootstrap generates the MachineConfig objects for InternalReleaseImage that would have been generated by syncInternalReleaseImage +func RunInternalReleaseImageBootstrap(iri *mcfgv1alpha1.InternalReleaseImage, iriTLSCert *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) ([]*mcfgv1.MachineConfig, error) { + return generateInternalReleaseImageMachineConfigs(iri, iriTLSCert, controllerConfig) } diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go index 0aaf2a1180..a3227a7288 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go @@ -3,25 +3,12 @@ package internalreleaseimage import ( "testing" - mcfgv1 "github.com/openshift/api/machineconfiguration/v1" mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" - templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" "github.com/stretchr/testify/assert" ) func TestRunInternalReleaseImageBootstrap(t *testing.T) { - cc := &mcfgv1.ControllerConfig{ - Spec: mcfgv1.ControllerConfigSpec{ - Images: map[string]string{ - templatectrl.DockerRegistryKey: "docker-registry-image-pullspec", - }, - }, - } - - mc, err := RunInternalReleaseImageBootstrap(&mcfgv1alpha1.InternalReleaseImage{}, cc) + configs, err := RunInternalReleaseImageBootstrap(&mcfgv1alpha1.InternalReleaseImage{}, iriCertSecret().obj, cconfig().obj) assert.NoError(t, err) - assert.Equal(t, mc.Name, iriMachineConfigName) - assert.Equal(t, mc.Labels[mcfgv1.MachineConfigRoleLabelKey], "master") - assert.Equal(t, mc.OwnerReferences[0].Kind, "InternalReleaseImage") - assert.Contains(t, string(mc.Spec.Config.Raw), "docker-registry-image-pullspec") + verifyInternalReleaseMasterMachineConfig(t, configs[0]) } diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go b/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go index 4c64455855..7a6436f2f6 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go @@ -3,8 +3,9 @@ package internalreleaseimage import ( "bytes" "context" - _ "embed" + "embed" "fmt" + "path/filepath" "reflect" "text/template" "time" @@ -41,7 +42,8 @@ import ( const ( maxRetries = 15 - iriMachineConfigName = "02-master-internalreleaseimage" + iriMachineConfigName = "02-master-internalreleaseimage" + iriMachineConfigNameFmt = "02-%s-internalreleaseimage" ) var ( @@ -54,13 +56,14 @@ var ( Jitter: 1.0, } - //go:embed templates/iri-registry.service.yaml - iriRegistryServiceTemplate string + //go:embed templates/* + templatesFS embed.FS ) // Controller defines the InternalReleaseImage controller. type Controller struct { client mcfgclientset.Interface + kubeClient clientset.Interface eventRecorder record.EventRecorder syncHandler func(mcp string) error @@ -91,8 +94,8 @@ func New( eventBroadcaster.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) ctrl := &Controller{ - client: mcfgClient, - + client: mcfgClient, + kubeClient: kubeClient, eventRecorder: ctrlcommon.NamespacedEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "machineconfigcontroller-internalreleaseimagecontroller"})), queue: workqueue.NewTypedRateLimitingQueueWithConfig( workqueue.DefaultTypedControllerRateLimiter[string](), @@ -320,10 +323,16 @@ func (ctrl *Controller) syncInternalReleaseImage(key string) error { return fmt.Errorf("could not get ControllerConfig %w", err) } + iriSecret, err := ctrl.kubeClient.CoreV1().Secrets(common.MCONamespace).Get(context.TODO(), ctrlcommon.InternalReleaseImageTLSSecretName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("could not get Secret %s: %w", ctrlcommon.InternalReleaseImageTLSSecretName, err) + } + if isNotFound { - mc, err = generateInternalReleaseImageMachineConfig(iri, cconfig) + configs, _ := generateInternalReleaseImageMachineConfigs(iri, iriSecret, cconfig) //!!! + mc = configs[0] } else { - err = updateInternalReleaseImageMachineConfig(mc, cconfig) + err = updateInternalReleaseImageMachineConfig(mc, iriSecret, cconfig) } if err != nil { return err // syncStatus, could not create/update MachineConfig @@ -349,21 +358,6 @@ func (ctrl *Controller) syncInternalReleaseImage(key string) error { return nil } -func updateInternalReleaseImageMachineConfig(mc *mcfgv1.MachineConfig, controllerConfig *mcfgv1.ControllerConfig) error { - ignCfg, err := generateIgnitionFromTemplate(controllerConfig) - if err != nil { - return err - } - - rawIgn, err := json.Marshal(ignCfg) - if err != nil { - return err - } - - mc.Spec.Config.Raw = rawIgn - return nil -} - func (ctrl *Controller) addFinalizerToInternalReleaseImage(iri *mcfgv1alpha1.InternalReleaseImage, mc *mcfgv1.MachineConfig) error { if len(iri.GetFinalizers()) > 0 { return nil @@ -389,15 +383,66 @@ func (ctrl *Controller) updateInternalReleaseImageFinalizers(iri *mcfgv1alpha1.I return err } -func generateInternalReleaseImageMachineConfig(iri *mcfgv1alpha1.InternalReleaseImage, controllerConfig *mcfgv1.ControllerConfig) (*mcfgv1.MachineConfig, error) { - ignCfg, err := generateIgnitionFromTemplate(controllerConfig) +func updateInternalReleaseImageMachineConfig(mc *mcfgv1.MachineConfig, iriTLSCert *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) error { + rc, err := newRenderConfig(iriTLSCert, controllerConfig) + if err != nil { + return err + } + + ignCfg, err := generateIgnitionFromTemplates("master", rc) + if err != nil { + return err + } + + // update existing machine config + rawIgn, err := json.Marshal(ignCfg) + if err != nil { + return err + } + + mc.Spec.Config.Raw = rawIgn + return nil +} + +func generateInternalReleaseImageMachineConfigs(iri *mcfgv1alpha1.InternalReleaseImage, iriTLSCert *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) ([]*mcfgv1.MachineConfig, error) { + rc, err := newRenderConfig(iriTLSCert, controllerConfig) if err != nil { return nil, err } - mcfg, err := ctrlcommon.MachineConfigFromIgnConfig(common.MachineConfigPoolMaster, iriMachineConfigName, ignCfg) + entries, err := templatesFS.ReadDir("templates") if err != nil { - return nil, fmt.Errorf("error creating MachineConfig from Ignition config: %w", err) + return nil, err + } + + configs := []*mcfgv1.MachineConfig{} + for _, e := range entries { + if !e.IsDir() { + continue + } + role := e.Name() + + ignCfg, err := generateIgnitionFromTemplates(role, rc) + if err != nil { + return nil, err + } + + name := fmt.Sprintf("02-%s-internalreleaseimage", role) + mc, err := createMachineConfigWithIgnition(name, role, ignCfg, iri) + if err != nil { + return nil, err + } + + configs = append(configs, mc) + } + + return configs, nil +} + +func createMachineConfigWithIgnition(name string, role string, ignCfg *ign3types.Config, iri *mcfgv1alpha1.InternalReleaseImage) (*mcfgv1.MachineConfig, error) { + mcfg, err := ctrlcommon.MachineConfigFromIgnConfig(role, name, ignCfg) + if err != nil { + return nil, fmt.Errorf("error creating MachineConfig '%s' from Ignition config: %w", name, err) } cref := metav1.NewControllerRef(iri, controllerKind) @@ -409,28 +454,113 @@ func generateInternalReleaseImageMachineConfig(iri *mcfgv1alpha1.InternalRelease return mcfg, nil } -func generateIgnitionFromTemplate(controllerConfig *mcfgv1.ControllerConfig) (*ign3types.Config, error) { - // Parse the iri template - tmpl, err := template.New("iri-template").Parse(iriRegistryServiceTemplate) +type renderConfig struct { + DockerRegistryImage string + IriTLSKey string + IriTLSCert string + RootCA string +} + +func newRenderConfig(iriSecret *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) (*renderConfig, error) { + iriTLSKey, err := extractTLSCertFieldFromSecret(iriSecret, "tls.key") if err != nil { - return nil, fmt.Errorf("failed to parse iri-template : %w", err) + return nil, err } - - type iriRenderConfig struct { - DockerRegistryImage string + iriTLSCert, err := extractTLSCertFieldFromSecret(iriSecret, "tls.crt") + if err != nil { + return nil, err } - - buf := new(bytes.Buffer) - if err := tmpl.Execute(buf, iriRenderConfig{ + return &renderConfig{ DockerRegistryImage: controllerConfig.Spec.Images[templatectrl.DockerRegistryKey], - }); err != nil { - return nil, fmt.Errorf("failed to execute template: %w", err) + IriTLSKey: iriTLSKey, + IriTLSCert: iriTLSCert, + RootCA: string(controllerConfig.Spec.RootCAData), + }, nil +} + +func generateIgnitionFromTemplates(role string, rc *renderConfig) (*ign3types.Config, error) { + // Render templates + units, err := renderTemplateFolder(rc, filepath.Join(role, "units")) + if err != nil { + return nil, err + } + files, err := renderTemplateFolder(rc, filepath.Join(role, "files")) + if err != nil { + return nil, err + } + dirs := []string{ + "/etc/iri-registry", + "/etc/iri-registry/certs", + "/var/lib/iri-registry", } // Generate the iri ignition - ignCfg, err := ctrlcommon.TranspileCoreOSConfigToIgn(nil, []string{buf.String()}) + ignCfg, err := ctrlcommon.TranspileCoreOSConfigToIgn(files, units) if err != nil { return nil, fmt.Errorf("error transpiling CoreOS config to Ignition config: %w", err) } + transpileIgitionDirs(ignCfg, dirs) + return ignCfg, nil } + +func extractTLSCertFieldFromSecret(secret *corev1.Secret, fieldName string) (string, error) { + raw, found := secret.Data[fieldName] + if !found { + return "", fmt.Errorf("cannot find %s in secret %s", fieldName, secret.Name) + } + return string(raw), nil +} + +func transpileIgitionDirs(ignCfg *ign3types.Config, directories []string) { + for _, d := range directories { + mode := 0644 + ignCfg.Storage.Directories = append(ignCfg.Storage.Directories, ign3types.Directory{ + Node: ign3types.Node{ + Path: d, + }, + DirectoryEmbedded1: ign3types.DirectoryEmbedded1{ + Mode: &mode, + }, + }) + } +} + +func renderTemplateFolder(rc any, folder string) ([]string, error) { + tmplFolder := filepath.Join("templates", folder) + + files := []string{} + entries, err := templatesFS.ReadDir(tmplFolder) + if err != nil { + return nil, err + } + for _, e := range entries { + data, err := templatesFS.ReadFile(filepath.Join(tmplFolder, e.Name())) + if err != nil { + return nil, err + } + + rendered, err := applyTemplate(rc, data) + if err != nil { + return nil, err + } + files = append(files, rendered) + } + + return files, nil +} + +func applyTemplate(rc any, iriTemplate []byte) (string, error) { + funcs := ctrlcommon.GetTemplateFuncMap() + tmpl, err := template.New("internalreleaseimage").Funcs(funcs).Parse(string(iriTemplate)) + if err != nil { + return "", fmt.Errorf("failed to parse iri-template : %w", err) + } + + buf := new(bytes.Buffer) + if err := tmpl.Execute(buf, rc); err != nil { + return "", fmt.Errorf("failed to execute template: %w", err) + } + + return buf.String(), nil +} diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go b/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go index 5e59f6461c..79a8a387a7 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go @@ -11,8 +11,9 @@ import ( informers "github.com/openshift/client-go/machineconfiguration/informers/externalversions" "github.com/openshift/machine-config-operator/pkg/controller/common" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" - templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" "github.com/stretchr/testify/assert" + + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -36,41 +37,40 @@ func TestInternalReleaseImageCreate(t *testing.T) { }, { name: "add finalizer if not present", - initialObjects: objs(iri(), cconfig()), + initialObjects: objs(iri(), cconfig(), iriCertSecret()), verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { assert.Equal(t, iri().finalizer(iriMachineConfigName).build(), actualIRI) }, }, { name: "generate iri machine-config if not present", - initialObjects: objs(iri(), cconfig()), + initialObjects: objs(iri(), cconfig(), iriCertSecret()), verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { assert.Equal(t, iriMachineConfigName, actualMC.Name) assert.Equal(t, actualMC.Labels[mcfgv1.MachineConfigRoleLabelKey], common.MachineConfigPoolMaster) assert.Equal(t, actualMC.OwnerReferences[0].Kind, "InternalReleaseImage") - // Check that the templating code replaced the docker-registry image - assert.Contains(t, string(actualMC.Spec.Config.Raw), "docker-registry-image-pullspec") + verifyInternalReleaseMasterMachineConfig(t, actualMC) }, }, { name: "avoid machine-config drifting", - initialObjects: objs(iri().finalizer(iriMachineConfigName), cconfig(), machineconfig().ignition("some garbage")), + initialObjects: objs(iri().finalizer(iriMachineConfigName), cconfig(), iriCertSecret(), mastermachineconfig().ignition("some garbage")), verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { - assert.Contains(t, string(actualMC.Spec.Config.Raw), "docker-registry-image-pullspec") + verifyInternalReleaseMasterMachineConfig(t, actualMC) }, }, { name: "refresh machine-config on controllerConfig update", - initialObjects: objs(iri().finalizer(iriMachineConfigName), cconfig().dockerRegistryImage("a-new-docker-registry-image-pullspec"), machineconfig()), + initialObjects: objs(iri().finalizer(iriMachineConfigName), cconfig().dockerRegistryImage("a-new-docker-registry-image-pullspec"), iriCertSecret(), mastermachineconfig()), verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { - assert.Contains(t, string(actualMC.Spec.Config.Raw), "a-new-docker-registry-image-pullspec") + verifyInternalReleaseMasterMachineConfig(t, actualMC) }, }, { name: "machine-config cascade delete on iri removal", initialObjects: objs( iri().finalizer(iriMachineConfigName).setDeletionTimestamp(), - cconfig(), machineconfig()), + cconfig(), iriCertSecret(), mastermachineconfig()), verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { assert.Empty(t, actualIRI.Finalizers) assert.Nil(t, actualMC) @@ -118,138 +118,49 @@ func TestInternalReleaseImageCreate(t *testing.T) { } } -// objs is an helper func to improve the test readability. -func objs(builders ...objBuilder) func() []runtime.Object { - return func() []runtime.Object { - objects := []runtime.Object{} - for _, b := range builders { - objects = append(objects, b.build()) - } - return objects - } -} - -type objBuilder interface { - build() runtime.Object -} - -// iriBuilder simplifies the creation of an InternalReleaseImage resource in the test. -type iriBuilder struct { - obj *mcfgv1alpha1.InternalReleaseImage -} - -func iri() *iriBuilder { - return &iriBuilder{ - obj: &mcfgv1alpha1.InternalReleaseImage{ - ObjectMeta: v1.ObjectMeta{ - Name: ctrlcommon.InternalReleaseImageInstanceName, - }, - }, - } -} - -func (ib *iriBuilder) finalizer(f ...string) *iriBuilder { - ib.obj.SetFinalizers(f) - return ib -} - -func (ib *iriBuilder) setDeletionTimestamp() *iriBuilder { - now := v1.Now() - ib.obj.SetDeletionTimestamp(&now) - return ib -} - -func (ib *iriBuilder) build() runtime.Object { - return ib.obj -} - -// controllerConfigBuilder simplifies the creation of a ControllerConfig resource in the test. -type controllerConfigBuilder struct { - obj *mcfgv1.ControllerConfig -} - -func cconfig() *controllerConfigBuilder { - return &controllerConfigBuilder{ - obj: &mcfgv1.ControllerConfig{ - ObjectMeta: v1.ObjectMeta{ - Name: ctrlcommon.ControllerConfigName, - }, - Spec: mcfgv1.ControllerConfigSpec{ - Images: map[string]string{ - templatectrl.DockerRegistryKey: "docker-registry-image-pullspec", - }, - }, - }, - } -} - -func (ccb *controllerConfigBuilder) dockerRegistryImage(image string) *controllerConfigBuilder { - ccb.obj.Spec.Images[templatectrl.DockerRegistryKey] = image - return ccb -} - -func (ccb *controllerConfigBuilder) build() runtime.Object { - return ccb.obj -} - -// machineConfigBuilder simplifies the creation of a MachineConfig resource in the test. -type machineConfigBuilder struct { - obj *mcfgv1.MachineConfig -} - -func machineconfig() *machineConfigBuilder { - return &machineConfigBuilder{ - obj: &mcfgv1.MachineConfig{ - ObjectMeta: v1.ObjectMeta{ - Name: iriMachineConfigName, - }, - Spec: mcfgv1.MachineConfigSpec{ - Config: runtime.RawExtension{}, - }, - }, - } -} - -func (mcb *machineConfigBuilder) ignition(ign string) *machineConfigBuilder { - mcb.obj.Spec.Config.Raw = []byte(ign) - return mcb -} - -func (mcb *machineConfigBuilder) build() runtime.Object { - return mcb.obj -} - // The fixture used to setup and run the controller. type fixture struct { t *testing.T client *fake.Clientset + k8sClient *k8sfake.Clientset iriLister []*mcfgv1alpha1.InternalReleaseImage ccLister []*mcfgv1.ControllerConfig mcLister []*mcfgv1.MachineConfig controller *Controller objects []runtime.Object + k8sObjects []runtime.Object } func newFixture(t *testing.T, objects []runtime.Object) *fixture { - f := &fixture{ - t: t, - objects: objects, - } + f := &fixture{t: t} + f.setupObjects(objects) f.controller = f.newController() return f } +func (f *fixture) setupObjects(objs []runtime.Object) { + for _, o := range objs { + switch o.(type) { + case *corev1.Secret, *corev1.ConfigMap, *corev1.Pod: + f.k8sObjects = append(f.k8sObjects, o) + default: + f.objects = append(f.objects, o) + } + } +} + func (f *fixture) newController() *Controller { f.client = fake.NewSimpleClientset(f.objects...) + f.k8sClient = k8sfake.NewSimpleClientset(f.k8sObjects...) i := informers.NewSharedInformerFactory(f.client, func() time.Duration { return 0 }()) c := New( i.Machineconfiguration().V1alpha1().InternalReleaseImages(), i.Machineconfiguration().V1().ControllerConfigs(), i.Machineconfiguration().V1().MachineConfigs(), - k8sfake.NewSimpleClientset(), + f.k8sClient, f.client, ) @@ -282,7 +193,6 @@ func (f *fixture) run(key string) { } func (f *fixture) runController(key string, expectError bool) { - err := f.controller.syncHandler(key) if !expectError && err != nil { f.t.Errorf("error syncing internalreleaseimage: %v", err) diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go b/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go new file mode 100644 index 0000000000..b85394c27b --- /dev/null +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go @@ -0,0 +1,175 @@ +package internalreleaseimage + +// Test builders and helper methods. + +import ( + "testing" + + ign3types "github.com/coreos/ignition/v2/config/v3_5/types" + mcfgv1 "github.com/openshift/api/machineconfiguration/v1" + mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" + "github.com/openshift/machine-config-operator/pkg/controller/common" + ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" + templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func verifyInternalReleaseMasterMachineConfig(t *testing.T, mc *mcfgv1.MachineConfig) { + assert.Equal(t, iriMachineConfigName, mc.Name) + assert.Equal(t, common.MachineConfigPoolMaster, mc.Labels[mcfgv1.MachineConfigRoleLabelKey]) + assert.Equal(t, "InternalReleaseImage", mc.OwnerReferences[0].Kind) + + ignCfg, err := ctrlcommon.ParseAndConvertConfig(mc.Spec.Config.Raw) + assert.NoError(t, err) + + assert.Len(t, ignCfg.Systemd.Units, 1) + assert.Contains(t, *ignCfg.Systemd.Units[0].Contents, "docker-registry-image-pullspec") + + assert.Len(t, ignCfg.Storage.Files, 3) + verifyIgnitionFile(t, &ignCfg, "/etc/pki/ca-trust/source/anchors/root-ca.crt", "root-ca-data") + verifyIgnitionFile(t, &ignCfg, "/etc/iri-registry/certs/tls.key", "iri-tls-key") + verifyIgnitionFile(t, &ignCfg, "/etc/iri-registry/certs/tls.crt", "iri-tls-crt") +} + +func verifyIgnitionFile(t *testing.T, ignCfg *ign3types.Config, path string, expectedContent string) { + data, err := ctrlcommon.GetIgnitionFileDataByPath(ignCfg, path) + assert.NoError(t, err) + assert.Equal(t, expectedContent, string(data), path) +} + +// objs is an helper func to improve the test readability. +func objs(builders ...objBuilder) func() []runtime.Object { + return func() []runtime.Object { + objects := []runtime.Object{} + for _, b := range builders { + objects = append(objects, b.build()) + } + return objects + } +} + +type objBuilder interface { + build() runtime.Object +} + +// iriBuilder simplifies the creation of an InternalReleaseImage resource in the test. +type iriBuilder struct { + obj *mcfgv1alpha1.InternalReleaseImage +} + +func iri() *iriBuilder { + return &iriBuilder{ + obj: &mcfgv1alpha1.InternalReleaseImage{ + ObjectMeta: v1.ObjectMeta{ + Name: ctrlcommon.InternalReleaseImageInstanceName, + }, + }, + } +} + +func (ib *iriBuilder) finalizer(f ...string) *iriBuilder { + ib.obj.SetFinalizers(f) + return ib +} + +func (ib *iriBuilder) setDeletionTimestamp() *iriBuilder { + now := v1.Now() + ib.obj.SetDeletionTimestamp(&now) + return ib +} + +func (ib *iriBuilder) build() runtime.Object { + return ib.obj +} + +// controllerConfigBuilder simplifies the creation of a ControllerConfig resource in the test. +type controllerConfigBuilder struct { + obj *mcfgv1.ControllerConfig +} + +func cconfig() *controllerConfigBuilder { + return &controllerConfigBuilder{ + obj: &mcfgv1.ControllerConfig{ + ObjectMeta: v1.ObjectMeta{ + Name: ctrlcommon.ControllerConfigName, + }, + Spec: mcfgv1.ControllerConfigSpec{ + Images: map[string]string{ + templatectrl.DockerRegistryKey: "docker-registry-image-pullspec", + }, + RootCAData: []byte("root-ca-data"), + }, + }, + } +} + +func (ccb *controllerConfigBuilder) dockerRegistryImage(image string) *controllerConfigBuilder { + ccb.obj.Spec.Images[templatectrl.DockerRegistryKey] = image + return ccb +} + +func (ccb *controllerConfigBuilder) build() runtime.Object { + return ccb.obj +} + +// machineConfigBuilder simplifies the creation of a MachineConfig resource in the test. +type machineConfigBuilder struct { + obj *mcfgv1.MachineConfig +} + +func mastermachineconfig() *machineConfigBuilder { + return &machineConfigBuilder{ + obj: &mcfgv1.MachineConfig{ + ObjectMeta: v1.ObjectMeta{ + Name: iriMachineConfigName, + Labels: map[string]string{ + mcfgv1.MachineConfigRoleLabelKey: common.MachineConfigPoolMaster, + }, + OwnerReferences: []v1.OwnerReference{ + { + Kind: "InternalReleaseImage", + }, + }, + }, + Spec: mcfgv1.MachineConfigSpec{ + Config: runtime.RawExtension{}, + }, + }, + } +} + +func (mcb *machineConfigBuilder) ignition(ign string) *machineConfigBuilder { + mcb.obj.Spec.Config.Raw = []byte(ign) + return mcb +} + +func (mcb *machineConfigBuilder) build() runtime.Object { + return mcb.obj +} + +// secretBuilder simplifies the creation of a Secret resource in the test. +type secretBuilder struct { + obj *corev1.Secret +} + +func iriCertSecret() *secretBuilder { + return &secretBuilder{ + obj: &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Namespace: common.MCONamespace, + Name: ctrlcommon.InternalReleaseImageTLSSecretName, + }, + Data: map[string][]byte{ + "tls.key": []byte("iri-tls-key"), + "tls.crt": []byte("iri-tls-crt"), + }, + }, + } +} + +func (sb *secretBuilder) build() runtime.Object { + return sb.obj +} diff --git a/pkg/controller/internalreleaseimage/templates/master/files/root-ca.yaml b/pkg/controller/internalreleaseimage/templates/master/files/root-ca.yaml new file mode 100644 index 0000000000..3ca105ab4d --- /dev/null +++ b/pkg/controller/internalreleaseimage/templates/master/files/root-ca.yaml @@ -0,0 +1,5 @@ +mode: 0644 +path: "/etc/pki/ca-trust/source/anchors/root-ca.crt" +contents: + inline: |- +{{indent 4 .RootCA}} diff --git a/pkg/controller/internalreleaseimage/templates/master/files/tls-crt.yaml b/pkg/controller/internalreleaseimage/templates/master/files/tls-crt.yaml new file mode 100644 index 0000000000..bbf4963758 --- /dev/null +++ b/pkg/controller/internalreleaseimage/templates/master/files/tls-crt.yaml @@ -0,0 +1,5 @@ +mode: 0644 +path: "/etc/iri-registry/certs/tls.crt" +contents: + inline: |- +{{indent 4 .IriTLSCert}} diff --git a/pkg/controller/internalreleaseimage/templates/master/files/tls-key.yaml b/pkg/controller/internalreleaseimage/templates/master/files/tls-key.yaml new file mode 100644 index 0000000000..1c3020762d --- /dev/null +++ b/pkg/controller/internalreleaseimage/templates/master/files/tls-key.yaml @@ -0,0 +1,5 @@ +mode: 0600 +path: "/etc/iri-registry/certs/tls.key" +contents: + inline: |- +{{indent 4 .IriTLSKey}} diff --git a/pkg/controller/internalreleaseimage/templates/iri-registry.service.yaml b/pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml similarity index 67% rename from pkg/controller/internalreleaseimage/templates/iri-registry.service.yaml rename to pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml index c109b32a75..ee9087fc08 100644 --- a/pkg/controller/internalreleaseimage/templates/iri-registry.service.yaml +++ b/pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml @@ -8,8 +8,7 @@ contents: | [Service] Environment=PODMAN_SYSTEMD_UNIT=%n ExecStartPre=/bin/rm -f %t/%n.ctr-id - ExecStartPre=/usr/bin/mkdir -p /var/lib/iri-registry - ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --log-driver=journald --replace --name=iri-registry -v /var/lib/iri-registry:/var/lib/registry:ro -e REGISTRY_HTTP_ADDR=0.0.0.0:22625 -u 0 --entrypoint=/usr/bin/distribution {{ .DockerRegistryImage }} serve /etc/registry/config.yaml + ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --log-driver=journald --replace --name=iri-registry -v /var/lib/iri-registry:/var/lib/registry:ro -v /etc/iri-registry/certs:/certs:ro -e REGISTRY_HTTP_ADDR=0.0.0.0:22625 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/tls.crt -e REGISTRY_HTTP_TLS_KEY=/certs/tls.key -u 0 --entrypoint=/usr/bin/distribution {{ .DockerRegistryImage }} serve /etc/registry/config.yaml ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id From 9f747564c852456d95656f5fc6f5095e52023b39 Mon Sep 17 00:00:00 2001 From: Andrea Fasano Date: Wed, 10 Dec 2025 10:10:58 -0500 Subject: [PATCH 2/3] added tls cert to workers trust store reviewed bootstrap/controller logic to allow generation and management of multiple machine configs --- pkg/controller/bootstrap/bootstrap.go | 8 +- pkg/controller/common/constants.go | 2 +- .../internalreleaseimage_bootstrap.go | 21 +- .../internalreleaseimage_bootstrap_test.go | 2 +- .../internalreleaseimage_controller.go | 280 +++--------------- .../internalreleaseimage_controller_test.go | 85 ++++-- .../internalreleaseimage_helpers_test.go | 53 +++- .../internalreleaseimage_renderer.go | 195 ++++++++++++ .../master/units/iri-registry.service.yaml | 1 + .../templates/worker/files/root-ca.yaml | 5 + 10 files changed, 369 insertions(+), 283 deletions(-) create mode 100644 pkg/controller/internalreleaseimage/internalreleaseimage_renderer.go create mode 100644 pkg/controller/internalreleaseimage/templates/worker/files/root-ca.yaml diff --git a/pkg/controller/bootstrap/bootstrap.go b/pkg/controller/bootstrap/bootstrap.go index 396888c9b1..cd256b5e9c 100644 --- a/pkg/controller/bootstrap/bootstrap.go +++ b/pkg/controller/bootstrap/bootstrap.go @@ -115,10 +115,6 @@ func (b *Bootstrap) Run(destDir string) error { } defer file.Close() - if info.Name() == "internal-release-image-tls-secret.yaml" { - _ = "" - } - manifests, err := parseManifests(file.Name(), file) if err != nil { return fmt.Errorf("error parsing manifests from %s: %w", file.Name(), err) @@ -262,11 +258,11 @@ func (b *Bootstrap) Run(destDir string) error { if fgHandler != nil && fgHandler.Enabled(features.FeatureGateNoRegistryClusterInstall) { if iri != nil { - configs, err := internalreleaseimage.RunInternalReleaseImageBootstrap(iri, iriTLSCert, cconfig) + iriConfigs, err := internalreleaseimage.RunInternalReleaseImageBootstrap(iri, iriTLSCert, cconfig) if err != nil { return err } - configs = append(configs, configs...) + configs = append(configs, iriConfigs...) klog.Infof("Successfully generated MachineConfig from InternalReleaseImage.") } } diff --git a/pkg/controller/common/constants.go b/pkg/controller/common/constants.go index 6ed6fb8bb9..15d773dc0a 100644 --- a/pkg/controller/common/constants.go +++ b/pkg/controller/common/constants.go @@ -66,7 +66,7 @@ const ( // InternalReleaseImageInstanceName is a singleton name for InternalReleaseImage InternalReleaseImageInstanceName = "cluster" - // InternalReleaseImageTLSSecretName is the name of the secret manifest containing the IntennalReleaseImage TLS certificate. + // InternalReleaseImageTLSSecretName is the name of the secret manifest containing the InternalReleaseImage TLS certificate. InternalReleaseImageTLSSecretName = "internal-release-image-tls" // APIServerInstanceName is a singleton name for APIServer configuration diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go index 07a9d6cbdf..6d2a243eab 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap.go @@ -7,7 +7,22 @@ import ( mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" ) -// RunInternalReleaseImageBootstrap generates the MachineConfig objects for InternalReleaseImage that would have been generated by syncInternalReleaseImage -func RunInternalReleaseImageBootstrap(iri *mcfgv1alpha1.InternalReleaseImage, iriTLSCert *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) ([]*mcfgv1.MachineConfig, error) { - return generateInternalReleaseImageMachineConfigs(iri, iriTLSCert, controllerConfig) +// RunInternalReleaseImageBootstrap generates the MachineConfig objects for InternalReleaseImage that would have been generated by syncInternalReleaseImage. +func RunInternalReleaseImageBootstrap(iri *mcfgv1alpha1.InternalReleaseImage, iriSecret *corev1.Secret, cconfig *mcfgv1.ControllerConfig) ([]*mcfgv1.MachineConfig, error) { + configs := []*mcfgv1.MachineConfig{} + + for _, role := range SupportedRoles { + r := NewRendererByRole(role, iri, iriSecret, cconfig) + mc, err := r.CreateEmptyMachineConfig() + if err != nil { + return nil, err + } + err = r.RenderAndSetIgnition(mc) + if err != nil { + return nil, err + } + configs = append(configs, mc) + } + + return configs, nil } diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go index a3227a7288..be62296f9f 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_bootstrap_test.go @@ -10,5 +10,5 @@ import ( func TestRunInternalReleaseImageBootstrap(t *testing.T) { configs, err := RunInternalReleaseImageBootstrap(&mcfgv1alpha1.InternalReleaseImage{}, iriCertSecret().obj, cconfig().obj) assert.NoError(t, err) - verifyInternalReleaseMasterMachineConfig(t, configs[0]) + verifyAllInternalReleaseImageMachineConfigs(t, configs) } diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go b/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go index 7a6436f2f6..c148dfa2f9 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_controller.go @@ -1,17 +1,11 @@ package internalreleaseimage import ( - "bytes" "context" - "embed" "fmt" - "path/filepath" "reflect" - "text/template" "time" - "github.com/clarketm/json" - ign3types "github.com/coreos/ignition/v2/config/v3_5/types" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -32,18 +26,13 @@ import ( mcfginformersv1alpha1 "github.com/openshift/client-go/machineconfiguration/informers/externalversions/machineconfiguration/v1alpha1" mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1" mcfglistersv1alpha1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1alpha1" - "github.com/openshift/machine-config-operator/pkg/controller/common" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" - "github.com/openshift/machine-config-operator/pkg/version" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( maxRetries = 15 - - iriMachineConfigName = "02-master-internalreleaseimage" - iriMachineConfigNameFmt = "02-%s-internalreleaseimage" ) var ( @@ -55,9 +44,6 @@ var ( Duration: 100 * time.Millisecond, Jitter: 1.0, } - - //go:embed templates/* - templatesFS embed.FS ) // Controller defines the InternalReleaseImage controller. @@ -258,7 +244,8 @@ func (ctrl *Controller) deleteMachineConfig(obj interface{}) { func (ctrl *Controller) processMachineConfigEvent(obj interface{}, logMsg string) { mc := obj.(*mcfgv1.MachineConfig) - if mc.Name != iriMachineConfigName { + // Skip any event not related to the InternalReleaseImage machine configs + if len(mc.OwnerReferences) == 0 || mc.OwnerReferences[0].Kind != controllerKind.Kind { return } @@ -303,7 +290,7 @@ func (ctrl *Controller) syncInternalReleaseImage(key string) error { // Deep-copy otherwise we are mutating our cache. iri = iri.DeepCopy() - // Check for Deleted InternalReleaseImage and optionally delete finalizers + // Check for Deleted InternalReleaseImage and optionally delete finalizers. if !iri.DeletionTimestamp.IsZero() { if len(iri.GetFinalizers()) > 0 { return ctrl.cascadeDelete(iri) @@ -311,34 +298,49 @@ func (ctrl *Controller) syncInternalReleaseImage(key string) error { return nil } - // Create or update InternalReleaseImage MachineConfig - mc, err := ctrl.client.MachineconfigurationV1().MachineConfigs().Get(context.TODO(), iriMachineConfigName, metav1.GetOptions{}) - isNotFound := errors.IsNotFound(err) - if err != nil && !isNotFound { - return err // syncStatus, could not find MachineConfig - } - cconfig, err := ctrl.client.MachineconfigurationV1().ControllerConfigs().Get(context.TODO(), ctrlcommon.ControllerConfigName, metav1.GetOptions{}) if err != nil { return fmt.Errorf("could not get ControllerConfig %w", err) } - iriSecret, err := ctrl.kubeClient.CoreV1().Secrets(common.MCONamespace).Get(context.TODO(), ctrlcommon.InternalReleaseImageTLSSecretName, metav1.GetOptions{}) + iriSecret, err := ctrl.kubeClient.CoreV1().Secrets(ctrlcommon.MCONamespace).Get(context.TODO(), ctrlcommon.InternalReleaseImageTLSSecretName, metav1.GetOptions{}) if err != nil { return fmt.Errorf("could not get Secret %s: %w", ctrlcommon.InternalReleaseImageTLSSecretName, err) } - if isNotFound { - configs, _ := generateInternalReleaseImageMachineConfigs(iri, iriSecret, cconfig) //!!! - mc = configs[0] - } else { - err = updateInternalReleaseImageMachineConfig(mc, iriSecret, cconfig) - } - if err != nil { - return err // syncStatus, could not create/update MachineConfig + for _, role := range SupportedRoles { + r := NewRendererByRole(role, iri, iriSecret, cconfig) + + mc, err := ctrl.client.MachineconfigurationV1().MachineConfigs().Get(context.TODO(), r.GetMachineConfigName(), metav1.GetOptions{}) + isNotFound := errors.IsNotFound(err) + if err != nil && !isNotFound { + return err // syncStatus, could not find MachineConfig + } + if isNotFound { + mc, err = r.CreateEmptyMachineConfig() + if err != nil { + return err // syncStatusOnly, could not create MachineConfig + } + } + + err = r.RenderAndSetIgnition(mc) + if err != nil { + return err // syncStatus, could not generate IRI configs + } + err = ctrl.createOrUpdateMachineConfig(isNotFound, mc) + if err != nil { + return err // syncStatus, could not Create/Update MachineConfig + } + if err := ctrl.addFinalizerToInternalReleaseImage(iri, mc); err != nil { + return err // syncStatus , could not add finalizers + } } - if err := retry.RetryOnConflict(updateBackoff, func() error { + return nil +} + +func (ctrl *Controller) createOrUpdateMachineConfig(isNotFound bool, mc *mcfgv1.MachineConfig) error { + return retry.RetryOnConflict(updateBackoff, func() error { var err error if isNotFound { _, err = ctrl.client.MachineconfigurationV1().MachineConfigs().Create(context.TODO(), mc, metav1.CreateOptions{}) @@ -346,221 +348,27 @@ func (ctrl *Controller) syncInternalReleaseImage(key string) error { _, err = ctrl.client.MachineconfigurationV1().MachineConfigs().Update(context.TODO(), mc, metav1.UpdateOptions{}) } return err - }); err != nil { - return err // syncStatus, could not Create/Update MachineConfig - } - - // Add finalizer to the InternalReleaseImage - if err := ctrl.addFinalizerToInternalReleaseImage(iri, mc); err != nil { - return err // syncStatus , could not add finalizers - } - - return nil + }) } func (ctrl *Controller) addFinalizerToInternalReleaseImage(iri *mcfgv1alpha1.InternalReleaseImage, mc *mcfgv1.MachineConfig) error { - if len(iri.GetFinalizers()) > 0 { + if ctrlcommon.InSlice(mc.Name, iri.Finalizers) { return nil } - return ctrl.updateInternalReleaseImageFinalizers(iri, []string{mc.Name}) + iri.Finalizers = append(iri.Finalizers, mc.Name) + _, err := ctrl.client.MachineconfigurationV1alpha1().InternalReleaseImages().Update(context.TODO(), iri, metav1.UpdateOptions{}) + return err } func (ctrl *Controller) cascadeDelete(iri *mcfgv1alpha1.InternalReleaseImage) error { - // Delete the InternalReleaseImage machine config - err := ctrl.client.MachineconfigurationV1().MachineConfigs().Delete(context.TODO(), iriMachineConfigName, metav1.DeleteOptions{}) + mcName := iri.GetFinalizers()[0] + err := ctrl.client.MachineconfigurationV1().MachineConfigs().Delete(context.TODO(), mcName, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { return err } - // Remove the InternalRelaseImage finalizer - return ctrl.updateInternalReleaseImageFinalizers(iri, []string{}) -} - -func (ctrl *Controller) updateInternalReleaseImageFinalizers(iri *mcfgv1alpha1.InternalReleaseImage, finalizers []string) error { - iri.SetFinalizers(finalizers) - _, err := ctrl.client.MachineconfigurationV1alpha1().InternalReleaseImages().Update(context.TODO(), iri, metav1.UpdateOptions{}) + iri.Finalizers = append([]string{}, iri.Finalizers[1:]...) + _, err = ctrl.client.MachineconfigurationV1alpha1().InternalReleaseImages().Update(context.TODO(), iri, metav1.UpdateOptions{}) return err } - -func updateInternalReleaseImageMachineConfig(mc *mcfgv1.MachineConfig, iriTLSCert *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) error { - rc, err := newRenderConfig(iriTLSCert, controllerConfig) - if err != nil { - return err - } - - ignCfg, err := generateIgnitionFromTemplates("master", rc) - if err != nil { - return err - } - - // update existing machine config - rawIgn, err := json.Marshal(ignCfg) - if err != nil { - return err - } - - mc.Spec.Config.Raw = rawIgn - return nil -} - -func generateInternalReleaseImageMachineConfigs(iri *mcfgv1alpha1.InternalReleaseImage, iriTLSCert *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) ([]*mcfgv1.MachineConfig, error) { - rc, err := newRenderConfig(iriTLSCert, controllerConfig) - if err != nil { - return nil, err - } - - entries, err := templatesFS.ReadDir("templates") - if err != nil { - return nil, err - } - - configs := []*mcfgv1.MachineConfig{} - for _, e := range entries { - if !e.IsDir() { - continue - } - role := e.Name() - - ignCfg, err := generateIgnitionFromTemplates(role, rc) - if err != nil { - return nil, err - } - - name := fmt.Sprintf("02-%s-internalreleaseimage", role) - mc, err := createMachineConfigWithIgnition(name, role, ignCfg, iri) - if err != nil { - return nil, err - } - - configs = append(configs, mc) - } - - return configs, nil -} - -func createMachineConfigWithIgnition(name string, role string, ignCfg *ign3types.Config, iri *mcfgv1alpha1.InternalReleaseImage) (*mcfgv1.MachineConfig, error) { - mcfg, err := ctrlcommon.MachineConfigFromIgnConfig(role, name, ignCfg) - if err != nil { - return nil, fmt.Errorf("error creating MachineConfig '%s' from Ignition config: %w", name, err) - } - - cref := metav1.NewControllerRef(iri, controllerKind) - mcfg.SetOwnerReferences([]metav1.OwnerReference{*cref}) - mcfg.SetAnnotations(map[string]string{ - ctrlcommon.GeneratedByControllerVersionAnnotationKey: version.Hash, - }) - - return mcfg, nil -} - -type renderConfig struct { - DockerRegistryImage string - IriTLSKey string - IriTLSCert string - RootCA string -} - -func newRenderConfig(iriSecret *corev1.Secret, controllerConfig *mcfgv1.ControllerConfig) (*renderConfig, error) { - iriTLSKey, err := extractTLSCertFieldFromSecret(iriSecret, "tls.key") - if err != nil { - return nil, err - } - iriTLSCert, err := extractTLSCertFieldFromSecret(iriSecret, "tls.crt") - if err != nil { - return nil, err - } - return &renderConfig{ - DockerRegistryImage: controllerConfig.Spec.Images[templatectrl.DockerRegistryKey], - IriTLSKey: iriTLSKey, - IriTLSCert: iriTLSCert, - RootCA: string(controllerConfig.Spec.RootCAData), - }, nil -} - -func generateIgnitionFromTemplates(role string, rc *renderConfig) (*ign3types.Config, error) { - // Render templates - units, err := renderTemplateFolder(rc, filepath.Join(role, "units")) - if err != nil { - return nil, err - } - files, err := renderTemplateFolder(rc, filepath.Join(role, "files")) - if err != nil { - return nil, err - } - dirs := []string{ - "/etc/iri-registry", - "/etc/iri-registry/certs", - "/var/lib/iri-registry", - } - - // Generate the iri ignition - ignCfg, err := ctrlcommon.TranspileCoreOSConfigToIgn(files, units) - if err != nil { - return nil, fmt.Errorf("error transpiling CoreOS config to Ignition config: %w", err) - } - transpileIgitionDirs(ignCfg, dirs) - - return ignCfg, nil -} - -func extractTLSCertFieldFromSecret(secret *corev1.Secret, fieldName string) (string, error) { - raw, found := secret.Data[fieldName] - if !found { - return "", fmt.Errorf("cannot find %s in secret %s", fieldName, secret.Name) - } - return string(raw), nil -} - -func transpileIgitionDirs(ignCfg *ign3types.Config, directories []string) { - for _, d := range directories { - mode := 0644 - ignCfg.Storage.Directories = append(ignCfg.Storage.Directories, ign3types.Directory{ - Node: ign3types.Node{ - Path: d, - }, - DirectoryEmbedded1: ign3types.DirectoryEmbedded1{ - Mode: &mode, - }, - }) - } -} - -func renderTemplateFolder(rc any, folder string) ([]string, error) { - tmplFolder := filepath.Join("templates", folder) - - files := []string{} - entries, err := templatesFS.ReadDir(tmplFolder) - if err != nil { - return nil, err - } - for _, e := range entries { - data, err := templatesFS.ReadFile(filepath.Join(tmplFolder, e.Name())) - if err != nil { - return nil, err - } - - rendered, err := applyTemplate(rc, data) - if err != nil { - return nil, err - } - files = append(files, rendered) - } - - return files, nil -} - -func applyTemplate(rc any, iriTemplate []byte) (string, error) { - funcs := ctrlcommon.GetTemplateFuncMap() - tmpl, err := template.New("internalreleaseimage").Funcs(funcs).Parse(string(iriTemplate)) - if err != nil { - return "", fmt.Errorf("failed to parse iri-template : %w", err) - } - - buf := new(bytes.Buffer) - if err := tmpl.Execute(buf, rc); err != nil { - return "", fmt.Errorf("failed to execute template: %w", err) - } - - return buf.String(), nil -} diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go b/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go index 79a8a387a7..3fd94603a0 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_controller_test.go @@ -9,7 +9,6 @@ import ( mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" "github.com/openshift/client-go/machineconfiguration/clientset/versioned/fake" informers "github.com/openshift/client-go/machineconfiguration/informers/externalversions" - "github.com/openshift/machine-config-operator/pkg/controller/common" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" "github.com/stretchr/testify/assert" @@ -25,55 +24,79 @@ func TestInternalReleaseImageCreate(t *testing.T) { cases := []struct { name string initialObjects func() []runtime.Object - verify func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) + verify func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) }{ { name: "feature inactive", initialObjects: objs(), - verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { assert.Nil(t, actualIRI) - assert.Nil(t, actualMC) + assert.Nil(t, actualMasterMC) + assert.Nil(t, actualWorkerMC) }, }, { name: "add finalizer if not present", initialObjects: objs(iri(), cconfig(), iriCertSecret()), - verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { - assert.Equal(t, iri().finalizer(iriMachineConfigName).build(), actualIRI) + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { + assert.Equal(t, iri().finalizer(masterName(), workerName()).build(), actualIRI) }, }, { name: "generate iri machine-config if not present", initialObjects: objs(iri(), cconfig(), iriCertSecret()), - verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { - assert.Equal(t, iriMachineConfigName, actualMC.Name) - assert.Equal(t, actualMC.Labels[mcfgv1.MachineConfigRoleLabelKey], common.MachineConfigPoolMaster) - assert.Equal(t, actualMC.OwnerReferences[0].Kind, "InternalReleaseImage") - verifyInternalReleaseMasterMachineConfig(t, actualMC) + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { + verifyInternalReleaseMasterMachineConfig(t, actualMasterMC) + verifyInternalReleaseWorkerMachineConfig(t, actualWorkerMC) }, }, { - name: "avoid machine-config drifting", - initialObjects: objs(iri().finalizer(iriMachineConfigName), cconfig(), iriCertSecret(), mastermachineconfig().ignition("some garbage")), - verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { - verifyInternalReleaseMasterMachineConfig(t, actualMC) + name: "avoid machine-config drifting", + initialObjects: objs( + iri().finalizer(masterName(), workerName()), + cconfig(), iriCertSecret(), + machineconfigmaster().ignition("some garbage"), + machineconfigworker().ignition("other garbage")), + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { + verifyInternalReleaseMasterMachineConfig(t, actualMasterMC) + verifyInternalReleaseWorkerMachineConfig(t, actualWorkerMC) + }, + }, + { + name: "refresh machine-config on controllerConfig update", + initialObjects: objs( + iri().finalizer(masterName(), workerName()), + cconfig().dockerRegistryImage("a-new-docker-registry-image-pullspec"), iriCertSecret(), + machineconfigmaster(), machineconfigworker()), + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { + verifyInternalReleaseMasterMachineConfig(t, actualMasterMC) + verifyInternalReleaseWorkerMachineConfig(t, actualWorkerMC) }, }, { - name: "refresh machine-config on controllerConfig update", - initialObjects: objs(iri().finalizer(iriMachineConfigName), cconfig().dockerRegistryImage("a-new-docker-registry-image-pullspec"), iriCertSecret(), mastermachineconfig()), - verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { - verifyInternalReleaseMasterMachineConfig(t, actualMC) + name: "machine-config cascade delete on iri removal - removes the first machineconfig", + initialObjects: objs( + iri().finalizer(masterName(), workerName()).setDeletionTimestamp(), + cconfig(), iriCertSecret(), + machineconfigmaster(), machineconfigworker()), + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { + assert.NotNil(t, iri) + assert.Equal(t, []string{workerName()}, actualIRI.Finalizers) + assert.Nil(t, actualMasterMC) + assert.NotNil(t, actualWorkerMC) }, }, { - name: "machine-config cascade delete on iri removal", + name: "machine-config cascade delete on iri removal - then removes the remaining machineconfig", initialObjects: objs( - iri().finalizer(iriMachineConfigName).setDeletionTimestamp(), - cconfig(), iriCertSecret(), mastermachineconfig()), - verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMC *mcfgv1.MachineConfig) { + iri().finalizer(workerName()).setDeletionTimestamp(), + cconfig(), iriCertSecret(), + machineconfigworker()), + verify: func(t *testing.T, actualIRI *mcfgv1alpha1.InternalReleaseImage, actualMasterMC *mcfgv1.MachineConfig, actualWorkerMC *mcfgv1.MachineConfig) { + assert.NotNil(t, iri) assert.Empty(t, actualIRI.Finalizers) - assert.Nil(t, actualMC) + assert.Nil(t, actualMasterMC) + assert.Nil(t, actualWorkerMC) }, }, } @@ -103,15 +126,23 @@ func TestInternalReleaseImageCreate(t *testing.T) { actualIRI = nil } } - actualMC, err := f.client.MachineconfigurationV1().MachineConfigs().Get(context.TODO(), iriMachineConfigName, v1.GetOptions{}) + actualMasterMC, err := f.client.MachineconfigurationV1().MachineConfigs().Get(context.TODO(), masterName(), v1.GetOptions{}) + if err != nil { + if !errors.IsNotFound(err) { + t.Errorf("Error while running sync step: %v", err) + } else { + actualMasterMC = nil + } + } + actualWorkerMC, err := f.client.MachineconfigurationV1().MachineConfigs().Get(context.TODO(), workerName(), v1.GetOptions{}) if err != nil { if !errors.IsNotFound(err) { t.Errorf("Error while running sync step: %v", err) } else { - actualMC = nil + actualWorkerMC = nil } } - tc.verify(t, actualIRI, actualMC) + tc.verify(t, actualIRI, actualMasterMC, actualWorkerMC) } }) diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go b/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go index b85394c27b..271c38f209 100644 --- a/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_helpers_test.go @@ -3,12 +3,12 @@ package internalreleaseimage // Test builders and helper methods. import ( + "fmt" "testing" ign3types "github.com/coreos/ignition/v2/config/v3_5/types" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" - "github.com/openshift/machine-config-operator/pkg/controller/common" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" "github.com/stretchr/testify/assert" @@ -17,13 +17,19 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +func verifyAllInternalReleaseImageMachineConfigs(t *testing.T, configs []*mcfgv1.MachineConfig) { + assert.Len(t, configs, 2) + verifyInternalReleaseMasterMachineConfig(t, configs[0]) + verifyInternalReleaseWorkerMachineConfig(t, configs[1]) +} + func verifyInternalReleaseMasterMachineConfig(t *testing.T, mc *mcfgv1.MachineConfig) { - assert.Equal(t, iriMachineConfigName, mc.Name) - assert.Equal(t, common.MachineConfigPoolMaster, mc.Labels[mcfgv1.MachineConfigRoleLabelKey]) - assert.Equal(t, "InternalReleaseImage", mc.OwnerReferences[0].Kind) + assert.Equal(t, masterName(), mc.Name) + assert.Equal(t, ctrlcommon.MachineConfigPoolMaster, mc.Labels[mcfgv1.MachineConfigRoleLabelKey]) + assert.Equal(t, controllerKind.Kind, mc.OwnerReferences[0].Kind) ignCfg, err := ctrlcommon.ParseAndConvertConfig(mc.Spec.Config.Raw) - assert.NoError(t, err) + assert.NoError(t, err, mc.Name) assert.Len(t, ignCfg.Systemd.Units, 1) assert.Contains(t, *ignCfg.Systemd.Units[0].Contents, "docker-registry-image-pullspec") @@ -34,6 +40,19 @@ func verifyInternalReleaseMasterMachineConfig(t *testing.T, mc *mcfgv1.MachineCo verifyIgnitionFile(t, &ignCfg, "/etc/iri-registry/certs/tls.crt", "iri-tls-crt") } +func verifyInternalReleaseWorkerMachineConfig(t *testing.T, mc *mcfgv1.MachineConfig) { + assert.Equal(t, workerName(), mc.Name) + assert.Equal(t, ctrlcommon.MachineConfigPoolWorker, mc.Labels[mcfgv1.MachineConfigRoleLabelKey]) + assert.Equal(t, controllerKind.Kind, mc.OwnerReferences[0].Kind) + + ignCfg, err := ctrlcommon.ParseAndConvertConfig(mc.Spec.Config.Raw) + assert.NoError(t, err) + + assert.Len(t, ignCfg.Systemd.Units, 0) + assert.Len(t, ignCfg.Storage.Files, 1) + verifyIgnitionFile(t, &ignCfg, "/etc/pki/ca-trust/source/anchors/root-ca.crt", "root-ca-data") +} + func verifyIgnitionFile(t *testing.T, ignCfg *ign3types.Config, path string, expectedContent string) { data, err := ctrlcommon.GetIgnitionFileDataByPath(ignCfg, path) assert.NoError(t, err) @@ -120,13 +139,29 @@ type machineConfigBuilder struct { obj *mcfgv1.MachineConfig } -func mastermachineconfig() *machineConfigBuilder { +func machineconfigmaster() *machineConfigBuilder { + return machineconfig("master") +} + +func machineconfigworker() *machineConfigBuilder { + return machineconfig("worker") +} + +func masterName() string { + return fmt.Sprintf(machineConfigNameFmt, "master") +} + +func workerName() string { + return fmt.Sprintf(machineConfigNameFmt, "worker") +} + +func machineconfig(role string) *machineConfigBuilder { return &machineConfigBuilder{ obj: &mcfgv1.MachineConfig{ ObjectMeta: v1.ObjectMeta{ - Name: iriMachineConfigName, + Name: fmt.Sprintf(machineConfigNameFmt, role), Labels: map[string]string{ - mcfgv1.MachineConfigRoleLabelKey: common.MachineConfigPoolMaster, + mcfgv1.MachineConfigRoleLabelKey: role, }, OwnerReferences: []v1.OwnerReference{ { @@ -159,7 +194,7 @@ func iriCertSecret() *secretBuilder { return &secretBuilder{ obj: &corev1.Secret{ ObjectMeta: v1.ObjectMeta{ - Namespace: common.MCONamespace, + Namespace: ctrlcommon.MCONamespace, Name: ctrlcommon.InternalReleaseImageTLSSecretName, }, Data: map[string][]byte{ diff --git a/pkg/controller/internalreleaseimage/internalreleaseimage_renderer.go b/pkg/controller/internalreleaseimage/internalreleaseimage_renderer.go new file mode 100644 index 0000000000..36806638fc --- /dev/null +++ b/pkg/controller/internalreleaseimage/internalreleaseimage_renderer.go @@ -0,0 +1,195 @@ +package internalreleaseimage + +import ( + "bytes" + "embed" + "errors" + "fmt" + "io/fs" + "path/filepath" + "text/template" + + "github.com/clarketm/json" + ign3types "github.com/coreos/ignition/v2/config/v3_5/types" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + mcfgv1 "github.com/openshift/api/machineconfiguration/v1" + mcfgv1alpha1 "github.com/openshift/api/machineconfiguration/v1alpha1" + ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" + templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" + "github.com/openshift/machine-config-operator/pkg/version" +) + +var ( + //go:embed templates/* + templatesFS embed.FS + + // List of supported roles for generating the machine configs. + // Templates folders are organized by those roles. + SupportedRoles = []string{"master", "worker"} + + // Format of the name for the InternalReleaseImage machine configs. + machineConfigNameFmt = "02-%s-internalreleaseimage" +) + +// Renderer takes care of generating the required ignition (by role) for +// the InternalReleaseImage machine config resources. It can also create +// a MachineConfig instance when required. +type Renderer struct { + role string + iri *mcfgv1alpha1.InternalReleaseImage + iriSecret *corev1.Secret + cconfig *mcfgv1.ControllerConfig +} + +// NewRendererByRole creates a new Renderer instance for generating +// the machine config for the given role. +func NewRendererByRole(role string, iri *mcfgv1alpha1.InternalReleaseImage, iriSecret *corev1.Secret, cconfig *mcfgv1.ControllerConfig) *Renderer { + return &Renderer{ + role: role, + iri: iri, + iriSecret: iriSecret, + cconfig: cconfig, + } +} + +// GetMachineConfigName returns the name of the MachineConfig instance. +func (r *Renderer) GetMachineConfigName() string { + return fmt.Sprintf(machineConfigNameFmt, r.role) +} + +// CreateEmptyMachineConfig creates an empty MachineConfig (without any ignition configured) owned by InternalReleaseImage. +func (r *Renderer) CreateEmptyMachineConfig() (*mcfgv1.MachineConfig, error) { + mc, err := ctrlcommon.MachineConfigFromIgnConfig(r.role, r.GetMachineConfigName(), ctrlcommon.NewIgnConfig()) + if err != nil { + return nil, err + } + + cref := metav1.NewControllerRef(r.iri, controllerKind) + mc.SetOwnerReferences([]metav1.OwnerReference{*cref}) + mc.SetAnnotations(map[string]string{ + ctrlcommon.GeneratedByControllerVersionAnnotationKey: version.Hash, + }) + return mc, nil +} + +// RenderAndSetIgnition generates the required ignition for the given role, +// and sets it on the specified MachineConfig. +func (r *Renderer) RenderAndSetIgnition(mc *mcfgv1.MachineConfig) error { + rc, err := r.newRenderContext() + if err != nil { + return err + } + + ignCfg, err := r.generateIgnitionFromTemplates(rc) + if err != nil { + return err + } + + rawIgn, err := json.Marshal(ignCfg) + if err != nil { + return err + } + + mc.Spec.Config.Raw = rawIgn + return nil +} + +// renderContext is a type used to hold the configuration required +// for current the template rendering. +type renderContext struct { + DockerRegistryImage string + IriTLSKey string + IriTLSCert string + RootCA string +} + +// newRenderContext creates a new renderContext instance. +func (r *Renderer) newRenderContext() (*renderContext, error) { + iriTLSKey, err := r.extractTLSCertFieldFromSecret(r.iriSecret, "tls.key") + if err != nil { + return nil, err + } + iriTLSCert, err := r.extractTLSCertFieldFromSecret(r.iriSecret, "tls.crt") + if err != nil { + return nil, err + } + return &renderContext{ + DockerRegistryImage: r.cconfig.Spec.Images[templatectrl.DockerRegistryKey], + IriTLSKey: iriTLSKey, + IriTLSCert: iriTLSCert, + RootCA: string(r.cconfig.Spec.RootCAData), + }, nil +} + +// extractTLSCertFieldFromSecret is an helper func to get the specified secret field data. +func (r *Renderer) extractTLSCertFieldFromSecret(secret *corev1.Secret, fieldName string) (string, error) { + raw, found := secret.Data[fieldName] + if !found { + return "", fmt.Errorf("cannot find %s in secret %s", fieldName, secret.Name) + } + return string(raw), nil +} + +// generateIgnitionFromTemplates creates the required ignition for the given roles +// using the InternalReleaseImage templates. +func (r *Renderer) generateIgnitionFromTemplates(rc *renderContext) (*ign3types.Config, error) { + // Render template subfolders, if defined. + units, err := r.renderTemplateFolder(rc, filepath.Join(r.role, "units")) + if err != nil { + return nil, err + } + files, err := r.renderTemplateFolder(rc, filepath.Join(r.role, "files")) + if err != nil { + return nil, err + } + + ignCfg, err := ctrlcommon.TranspileCoreOSConfigToIgn(files, units) + if err != nil { + return nil, fmt.Errorf("error transpiling CoreOS config to Ignition config: %w", err) + } + return ignCfg, nil +} + +// renderTemplateFolder renders all the templates found in the specified folder. +func (r *Renderer) renderTemplateFolder(rc any, folder string) ([]string, error) { + tmplFolder := filepath.Join("templates", folder) + + files := []string{} + entries, err := templatesFS.ReadDir(tmplFolder) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + + for _, e := range entries { + data, err := templatesFS.ReadFile(filepath.Join(tmplFolder, e.Name())) + if err != nil { + return nil, err + } + + rendered, err := r.applyTemplate(rc, data) + if err != nil { + return nil, err + } + files = append(files, rendered) + } + + return files, nil +} + +// applyTemplate applies the current template to the specified render context. +func (r *Renderer) applyTemplate(rc any, iriTemplate []byte) (string, error) { + funcs := ctrlcommon.GetTemplateFuncMap() + tmpl, err := template.New("internalreleaseimage").Funcs(funcs).Parse(string(iriTemplate)) + if err != nil { + return "", fmt.Errorf("failed to parse template : %w", err) + } + + buf := new(bytes.Buffer) + if err := tmpl.Execute(buf, rc); err != nil { + return "", fmt.Errorf("failed to execute template: %w", err) + } + + return buf.String(), nil +} diff --git a/pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml b/pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml index ee9087fc08..dac56bb40d 100644 --- a/pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml +++ b/pkg/controller/internalreleaseimage/templates/master/units/iri-registry.service.yaml @@ -7,6 +7,7 @@ contents: | [Service] Environment=PODMAN_SYSTEMD_UNIT=%n + ExecStartPre=mkdir -p /var/lib/iri-registry ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --log-driver=journald --replace --name=iri-registry -v /var/lib/iri-registry:/var/lib/registry:ro -v /etc/iri-registry/certs:/certs:ro -e REGISTRY_HTTP_ADDR=0.0.0.0:22625 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/tls.crt -e REGISTRY_HTTP_TLS_KEY=/certs/tls.key -u 0 --entrypoint=/usr/bin/distribution {{ .DockerRegistryImage }} serve /etc/registry/config.yaml ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id diff --git a/pkg/controller/internalreleaseimage/templates/worker/files/root-ca.yaml b/pkg/controller/internalreleaseimage/templates/worker/files/root-ca.yaml new file mode 100644 index 0000000000..3ca105ab4d --- /dev/null +++ b/pkg/controller/internalreleaseimage/templates/worker/files/root-ca.yaml @@ -0,0 +1,5 @@ +mode: 0644 +path: "/etc/pki/ca-trust/source/anchors/root-ca.crt" +contents: + inline: |- +{{indent 4 .RootCA}} From c2b317474deb4a0b4cc749a596bd2ed4d705a778 Mon Sep 17 00:00:00 2001 From: Andrea Fasano Date: Thu, 11 Dec 2025 07:23:37 -0500 Subject: [PATCH 3/3] added OWNERS file members of the agent team --- pkg/controller/internalreleaseimage/OWNERS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pkg/controller/internalreleaseimage/OWNERS diff --git a/pkg/controller/internalreleaseimage/OWNERS b/pkg/controller/internalreleaseimage/OWNERS new file mode 100644 index 0000000000..74e8b7eee0 --- /dev/null +++ b/pkg/controller/internalreleaseimage/OWNERS @@ -0,0 +1,14 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md + +approvers: + - andfasano + - bfournie + - pawanpinjarkar + - rwsu + - zaneb +reviewers: + - andfasano + - bfournie + - pawanpinjarkar + - rwsu + - zaneb \ No newline at end of file