Repository: openshift/machine-config-operator
Pull request #4971: MCO-1509: Enable OCL for disconnected workflow
Author: login: umohnani8
Labels: name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: jira/valid-reference
description: Indicates that this PR references a valid Jira ticket of any type.
Created: 2025-04-04T03:18:38Z | Merged: 2025-04-18T01:03:54Z

Closes https://issues.redhat.com/browse/MCO-1509

<!--
If this is a bug fix, make sure your description includes "Fixes: #xxxx", or
"Closes: #xxxx"

Please provide the following information:
-->

**- What I did**
Grab the /etc/containers/registries.conf and
/etc/containers/policy.json files from the node
and mount it into the builder pod so that buildah
can use it during the build process.
Note: mirrors only work for image pulls and not for image push. So the mirror here will be used when doing the FROM BaseOSImage part in the Containerfile during the build.

**- How to verify it**
Replace the value of `BaseOSImage` with a registry/image that doesn't exist. Set up an IDMS, something like this with the mirror and source configured:
```
apiVersion: config.openshift.io/v1 
kind: ImageDigestMirrorSet 
metadata:
  name: builder-test
spec:
  imageDigestMirrors: 
  - mirrors:
    - quay.io/umohnani8/mirror-base
    source: quay.io/umohnani8/og-base
    mirrorSourcePolicy: AllowContactingSource
```
Create a MachineOSConfig. The mirrors should be resolved and the build process should pull the base image from your mirrored location.
I verified this by looking at the build logs and by ensure my "source" registry/image didn't actually exist. So the only way it could pull the image is from the mirror.

Note mirrors only work on image pulls and not for image push. So the registry to push to should be the actual registry and not something that is mirrored.

I don't think there is a way for me to add an e2e test for this, so will have to rely on QE testing for this feature.

**- Description for the changelog**
Mount the registries.conf and policy.json with mirror configuration the build pod so it can used during the image build process.
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->



Diff:
diff --git a/pkg/controller/build/buildrequest/buildrequest.go b/pkg/controller/build/buildrequest/buildrequest.go
index a7b38683d4..aacfabea87 100644
--- a/pkg/controller/build/buildrequest/buildrequest.go
+++ b/pkg/controller/build/buildrequest/buildrequest.go
@@ -12,12 +12,14 @@ import (
 	mcfgclientset "github.com/openshift/client-go/machineconfiguration/clientset/versioned"
 	"github.com/openshift/machine-config-operator/pkg/controller/build/constants"
 	"github.com/openshift/machine-config-operator/pkg/controller/build/utils"
+	chelpers "github.com/openshift/machine-config-operator/pkg/controller/common"
 	ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	clientset "k8s.io/client-go/kubernetes"
+	"k8s.io/klog/v2"
 )
 
 //go:embed assets/Containerfile.on-cluster-build-template
@@ -117,7 +119,28 @@ func (br buildRequestImpl) ConfigMaps() ([]*corev1.ConfigMap, error) {
 
 	additionaltrustbundle := br.additionaltrustbundleToConfigMap()
 
-	return []*corev1.ConfigMap{containerfile, machineconfig, additionaltrustbundle}, nil
+	etcPolicy, err := br.etcPolicyToConfigMap(br.opts.MachineConfig)
+	if err != nil {
+		return nil, fmt.Errorf("could not convert etc/containers registries files into ConfigMap %q: %w", br.getEtcPolicyConfigMapName(), err)
+	}
+	etcRegistries, err := br.etcRegistriesToConfigMap(br.opts.MachineConfig)
+	if err != nil {
+		return nil, fmt.Errorf("could not convert registries.conf files into ConfigMap %q: %w", br.getEtcRegistriesConfigMapName(), err)
+	}
+
+	configMaps := []*corev1.ConfigMap{containerfile, machineconfig, additionaltrustbundle}
+	if etcPolicy != nil {
+		configMaps = append(configMaps, etcPolicy)
+	} else {
+		klog.Warningf("/etc/containers/policy.json file not found in MachineConfig %q, could not create ConfigMap %q", br.opts.MachineConfig.Name, br.getEtcPolicyConfigMapName())
+	}
+	if etcRegistries != nil {
+		configMaps = append(configMaps, etcRegistries)
+	} else {
+		klog.Warningf("/etc/containers/registries.conf file not found in MachineConfig %q, could not create ConfigMap %q", br.opts.MachineConfig.Name, br.getEtcRegistriesConfigMapName())
+	}
+
+	return configMaps, nil
 }
 
 func (br buildRequestImpl) canonicalizeSecret(name string, secret *corev1.Secret) (*corev1.Secret, error) {
@@ -198,6 +221,76 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.ignitionFileToConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.ignitionFileToConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) ignitionFileToConfigMapData(mc *mcfgv1.MachineConfig, filePath, prefixToTrim string) (map[string]string, error) {
+	if len(mc.Spec.Config.Raw) == 0 {
+		return nil, nil
+	}
+	// Build the ConfigMap data
+	ignCfg, err := ctrlcommon.ParseAndConvertConfig(mc.Spec.Config.Raw)
+	if err != nil {
+		return nil, fmt.Errorf("parsing rendered MC Ignition config failed with error: %w", err)
+	}
+
+	for _, file := range ignCfg.Storage.Files {
+		if file.Path != filePath {
+			continue
+		}
+		if file.Contents.Source == nil {
+			return nil, fmt.Errorf("nil source for %s", file.Path)
+		}
+
+		// Extract and decode the encoded data
+		decodedData, err := chelpers.DecodeIgnitionFileContents(file.Contents.Source, file.Contents.Compression)
+		if err != nil {
+			return nil, fmt.Errorf("error decoding %s: %v", file.Path, err)
+		}
+
+		// Key in the configmap is the path without the prefix
+		fileKey := strings.TrimPrefix(file.Path, prefixToTrim)
+		return map[string]string{fileKey: string(decodedData)}, nil
+	}
+	klog.Infof("Could not find %s in MachineConfig %s, skipping configmap creation....", filePath, mc.Name)
+	return nil, nil
+}
+
 // Renders our Containerfile template.
 //
 // TODO: Figure out how to parse the Containerfile using
@@ -364,6 +457,16 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 			Name:      "containerfile",
 			MountPath: "/tmp/containerfile",
 		},
+		{
+			Name:      "etc-policy",
+			MountPath: "/etc/containers/policy.json",
+			SubPath:   "policy.json",
+		},
+		{
+			Name:      "etc-registries",
+			MountPath: "/etc/containers/registries.conf",
+			SubPath:   "registries.conf",
+		},
 		{
 			Name:      "additional-trust-bundle",
 			MountPath: "/etc/pki/ca-trust/source/anchors",
@@ -382,6 +485,7 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 		},
 	}
 
+	boolTrue := true
 	volumes := []corev1.Volume{
 		{
 			// Provides the rendered Containerfile.
@@ -417,6 +521,30 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 				},
 			},
 		},
+		{
+			// Provides the /etc/containers/policy.json content from the node
+			Name: "etc-policy",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcPolicyConfigMapName(),
+					},
+					Optional: &boolTrue,
+				},
+			},
+		},
+		{
+			// Provides the /etc/containers/registries.conf content from the node
+			Name: "etc-registries",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcRegistriesConfigMapName(),
+					},
+					Optional: &boolTrue,
+				},
+			},
+		},
 		{
 			// Provides the credentials needed to pull the base OS image.
 			Name: "base-image-pull-creds",
@@ -602,6 +730,14 @@ func (br buildRequestImpl) getMCConfigMapName() string {
 	return utils.GetMCConfigMapName(br.opts.MachineOSBuild)
 }
 
+func (br buildRequestImpl) getEtcPolicyConfigMapName() string {
+	return utils.GetEtcPolicyConfigMapName(br.opts.MachineOSBuild)
+}
+
+func (br buildRequestImpl) getEtcRegistriesConfigMapName() string {
+	return utils.GetEtcRegistriesConfigMapName(br.opts.MachineOSBuild)
+}
+
 // Computes the build name based upon the MachineConfigPool name.
 func (br buildRequestImpl) getBuildName() string {
 	return utils.GetBuildJobName(br.opts.MachineOSBuild)
diff --git a/pkg/controller/build/osbuildcontroller_test.go b/pkg/controller/build/osbuildcontroller_test.go
index ebee34e4d6..6d1968ec48 100644
--- a/pkg/controller/build/osbuildcontroller_test.go
+++ b/pkg/controller/build/osbuildcontroller_test.go
@@ -124,7 +124,7 @@ func TestOSBuildControllerDeletesRunningBuildBeforeStartingANewOne(t *testing.T)
 // the same MachineOSConfig.
 func TestOSBuildControllerLeavesSuccessfulBuildAlone(t *testing.T) {
 
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
 	t.Cleanup(cancel)
 
 	poolName := "worker"
diff --git a/pkg/controller/build/utils/helpers.go b/pkg/controller/build/utils/helpers.go
index 889781ab04..f86c8cca4c 100644
--- a/pkg/controller/build/utils/helpers.go
+++ b/pkg/controller/build/utils/helpers.go
@@ -69,6 +69,14 @@ func GetMCConfigMapName(mosb *mcfgv1.MachineOSBuild) string {
 	return fmt.Sprintf("mc-%s", getFieldFromMachineOSBuild(mosb))
 }
 
+func GetEtcPolicyConfigMapName(mosb *mcfgv1.MachineOSBuild) string {
+	return fmt.Sprintf("etc-policy-%s", getFieldFromMachineOSBuild(mosb))
+}
+
+func GetEtcRegistriesConfigMapName(mosb *mcfgv1.MachineOSBuild) string {
+	return fmt.Sprintf("etc-registries-%s", getFieldFromMachineOSBuild(mosb))
+}
+
 // Computes the build job name.
 func GetBuildJobName(mosb *mcfgv1.MachineOSBuild) string {
 	return fmt.Sprintf("build-%s", getFieldFromMachineOSBuild(mosb))
diff --git a/test/e2e-ocl/onclusterlayering_test.go b/test/e2e-ocl/onclusterlayering_test.go
index 776339bc3b..339c81f81d 100644
--- a/test/e2e-ocl/onclusterlayering_test.go
+++ b/test/e2e-ocl/onclusterlayering_test.go
@@ -284,6 +284,8 @@ func assertNodeRevertsToNonLayered(t *testing.T, cs *framework.ClientSet, node c
 // simulating a build where someone has added this content; usually a Red Hat
 // Satellite user.
 func TestYumReposBuilds(t *testing.T) {
+	// Skipping this test as it is having a package conflict issue unrelated to MCO
+	t.Skip()
 	runOnClusterLayeringTest(t, onClusterLayeringTestOpts{
 		poolName: layeredMCPName,
 		customDockerfiles: map[string]string{
@@ -660,12 +662,16 @@ func assertBuildObjectsAreCreated(t *testing.T, kubeassert *helpers.Assertions,
 	kubeassert.JobExists(utils.GetBuildJobName(mosb))
 	kubeassert.ConfigMapExists(utils.GetContainerfileConfigMapName(mosb))
 	kubeassert.ConfigMapExists(utils.GetMCConfigMapName(mosb))
+	kubeassert.ConfigMapExists(utils.GetEtcPolicyConfigMapName(mosb))
+	kubeassert.ConfigMapExists(utils.GetEtcRegistriesConfigMapName(mosb))
 	kubeassert.SecretExists(utils.GetBasePullSecretName(mosb))
 	kubeassert.SecretExists(utils.GetFinalPushSecretName(mosb))
 
 	// Check that ownerReferences are set as well
 	kubeassert.ConfigMapHasOwnerSet(utils.GetContainerfileConfigMapName(mosb))
 	kubeassert.ConfigMapHasOwnerSet(utils.GetMCConfigMapName(mosb))
+	kubeassert.ConfigMapHasOwnerSet(utils.GetEtcPolicyConfigMapName(mosb))
+	kubeassert.ConfigMapHasOwnerSet(utils.GetEtcRegistriesConfigMapName(mosb))
 	kubeassert.SecretHasOwnerSet(utils.GetBasePullSecretName(mosb))
 	kubeassert.SecretHasOwnerSet(utils.GetFinalPushSecretName(mosb))
 }
@@ -676,6 +682,8 @@ func assertBuildObjectsAreDeleted(t *testing.T, kubeassert *helpers.Assertions,
 	kubeassert.JobDoesNotExist(utils.GetBuildJobName(mosb))
 	kubeassert.ConfigMapDoesNotExist(utils.GetContainerfileConfigMapName(mosb))
 	kubeassert.ConfigMapDoesNotExist(utils.GetMCConfigMapName(mosb))
+	kubeassert.ConfigMapDoesNotExist(utils.GetEtcPolicyConfigMapName(mosb))
+	kubeassert.ConfigMapDoesNotExist(utils.GetEtcRegistriesConfigMapName(mosb))
 	kubeassert.SecretDoesNotExist(utils.GetBasePullSecretName(mosb))
 	kubeassert.SecretDoesNotExist(utils.GetFinalPushSecretName(mosb))
 }


Reviews:
user: login: dkhater-redhat
body: good work urvashi! just a couple of questions I had 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T15:38:21Z
user: login: umohnani8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T12:13:45Z
user: login: umohnani8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T12:15:01Z
user: login: umohnani8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T13:51:46Z
user: login: dkhater-redhat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T17:37:25Z
user: login: cheesesashimi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T19:29:22Z
user: login: umohnani8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:43:00Z
user: login: umohnani8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:43:11Z
user: login: umohnani8
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:43:45Z

Review comments:
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: dkhater-redhat
body: thought (non-blocking): refactor the config map creation logic into a helper function that can be cited
created_at: 2025-04-09T15:30:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -417,6 +533,28 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 				},
 			},
 		},
+		{
+			// Provides the /etc/containers/policy.json content from the node
+			Name: "etc-policy",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcPolicyConfigMapName(),
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: dkhater-redhat
body: Question: if the mc lacks these files (policy.json/registries.conf) or the configmap doesn't get created, this will fail - right? Do we need to add a check to skip the volumes/mounts when the configmap doesn't exist? 
created_at: 2025-04-09T15:38:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -417,6 +533,28 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 				},
 			},
 		},
+		{
+			// Provides the /etc/containers/policy.json content from the node
+			Name: "etc-policy",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcPolicyConfigMapName(),
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: umohnani8
body: From what I can see, the MC always has these files but I can add a check to skip if these are missing
created_at: 2025-04-14T12:13:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: umohnani8
body: What part are you referring to for the helper function? I have created a helper for getting the data from the MC, which we then use when creating the configmap.
created_at: 2025-04-14T12:15:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -417,6 +533,28 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 				},
 			},
 		},
+		{
+			// Provides the /etc/containers/policy.json content from the node
+			Name: "etc-policy",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcPolicyConfigMapName(),
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: umohnani8
body: Fixed, made these optional mounts - so if the configmap doesn't exist, the pod creation will not fail.
created_at: 2025-04-14T13:51:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: dkhater-redhat
body: ```
func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
    return br.createContainerConfigMap(mc, "policy.json", br.getEtcPolicyConfigMapName)
}

func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
    return br.createContainerConfigMap(mc, "registries.conf", br.getEtcRegistriesConfigMapName)
}
```



I was thinking something like this. it's cosmetic so nbd!
created_at: 2025-04-14T17:37:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) getConfigMapData(mc *mcfgv1.MachineConfig, filePath, prefixToTrim string) (map[string]string, error) {
+	if len(mc.Spec.Config.Raw) == 0 {
+		return nil, nil
+	}
+	// Build the ConfigMap data
+	ignCfg, err := ctrlcommon.ParseAndConvertConfig(mc.Spec.Config.Raw)
+	if err != nil {
+		return nil, fmt.Errorf("parsing rendered MC Ignition config failed with error: %w", err)
+	}
+
+	for _, file := range ignCfg.Storage.Files {
+		if file.Path != filePath {
+			continue
+		}
+		if file.Contents.Source == nil {
+			return nil, fmt.Errorf("nil source for %s", file.Path)
+		}
+
+		// Extract and decode the encoded data
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: cheesesashimi
body: thought (non-blocking): A simpler way to decode this content is to use this helper function: https://github.com/openshift/machine-config-operator/blob/main/pkg/controller/common/helpers.go#L620
created_at: 2025-04-14T19:25:03Z
author_association: MEMBER
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) getConfigMapData(mc *mcfgv1.MachineConfig, filePath, prefixToTrim string) (map[string]string, error) {
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: cheesesashimi
body: thought (non-blocking): Perhaps we should name this something like, `ignitionFileToConfigMapData()` since this name implies that it's used in a variety of different situations?
created_at: 2025-04-14T19:26:09Z
author_association: MEMBER
diff_hunk: @@ -417,6 +533,28 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 				},
 			},
 		},
+		{
+			// Provides the /etc/containers/policy.json content from the node
+			Name: "etc-policy",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcPolicyConfigMapName(),
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: cheesesashimi
body: thought (non-blocking): We might want to write a log message if we fail to create the ConfigMaps because those files can't be found.
created_at: 2025-04-14T19:29:12Z
author_association: MEMBER
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) getConfigMapData(mc *mcfgv1.MachineConfig, filePath, prefixToTrim string) (map[string]string, error) {
+	if len(mc.Spec.Config.Raw) == 0 {
+		return nil, nil
+	}
+	// Build the ConfigMap data
+	ignCfg, err := ctrlcommon.ParseAndConvertConfig(mc.Spec.Config.Raw)
+	if err != nil {
+		return nil, fmt.Errorf("parsing rendered MC Ignition config failed with error: %w", err)
+	}
+
+	for _, file := range ignCfg.Storage.Files {
+		if file.Path != filePath {
+			continue
+		}
+		if file.Contents.Source == nil {
+			return nil, fmt.Errorf("nil source for %s", file.Path)
+		}
+
+		// Extract and decode the encoded data
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: umohnani8
body: Fixed, thanks!
created_at: 2025-04-15T13:43:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -198,6 +218,92 @@ func (br buildRequestImpl) additionaltrustbundleToConfigMap() *corev1.ConfigMap
 	return configmap
 }
 
+func (br buildRequestImpl) etcPolicyToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/policy.json", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcPolicyConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) etcRegistriesToConfigMap(mc *mcfgv1.MachineConfig) (*corev1.ConfigMap, error) {
+	// Build the ConfigMap data
+	configMapData, err := br.getConfigMapData(mc, "/etc/containers/registries.conf", "/etc/containers/")
+	if err != nil {
+		return nil, err
+	}
+	if len(configMapData) == 0 {
+		return nil, nil
+	}
+
+	// Create the ConfigMap
+	configmap := &corev1.ConfigMap{
+		TypeMeta:   metav1.TypeMeta{},
+		ObjectMeta: br.getObjectMeta(br.getEtcRegistriesConfigMapName()),
+		Data:       configMapData,
+	}
+	return configmap, nil
+}
+
+func (br buildRequestImpl) getConfigMapData(mc *mcfgv1.MachineConfig, filePath, prefixToTrim string) (map[string]string, error) {
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: umohnani8
body: Fixed
created_at: 2025-04-15T13:43:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -417,6 +533,28 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
 				},
 			},
 		},
+		{
+			// Provides the /etc/containers/policy.json content from the node
+			Name: "etc-policy",
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: br.getEtcPolicyConfigMapName(),
path: pkg/controller/build/buildrequest/buildrequest.go
user: login: umohnani8
body: Added warning logs if we fail to create the configmap
created_at: 2025-04-15T13:43:45Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci-robot
created_at: 2025-04-04T03:18:42Z
body: @umohnani8: This pull request references [MCO-1509](https://issues.redhat.com//browse/MCO-1509) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/machine-config-operator/pull/4971):

>Closes https://issues.redhat.com/browse/MCO-1509
>
><!--
>If this is a bug fix, make sure your description includes "Fixes: #xxxx", or
>"Closes: #xxxx"
>
>Please provide the following information:
>-->
>
>**- What I did**
>Grab the /etc/containers/registries.conf and
>/etc/containers/policy.json files from the node
>and mount it into the builder pod so that buildah
>can use it during the build process.
>Note: mirrors only work for image pulls and not for image push. So the mirror here will be used when doing the FROM BaseOSImage part in the Containerfile during the build.
>
>**- How to verify it**
>Replace the value of `BaseOSImage` with a registry/image that doesn't exist. Set up an IDMS, something like this with the mirror and source configured:
>```
>apiVersion: config.openshift.io/v1 
>kind: ImageDigestMirrorSet 
>metadata:
>  name: builder-test
>spec:
>  imageDigestMirrors: 
>  - mirrors:
>    - quay.io/umohnani8/mirror-base
>    source: quay.io/umohnani8/og-base
>    mirrorSourcePolicy: AllowContactingSource
>```
>Create a MachineOSConfig. The mirrors should be resolved and the build process should pull the base image from your mirrored location.
>I verified this by looking at the build logs and by ensure my "source" registry/image didn't actually exist. So the only way it could pull the image is from the mirror.
>
>Note mirrors only work on image pulls and not for image push. So the registry to push to should be the actual registry and not something that is mirrored.
>
>I don't think there is a way for me to add an e2e test for this, so will have to rely on QE testing for this feature.
>
>**- Description for the changelog**
>Mount the registries.conf and policy.json with mirror configuration the build pod so it can used during the image build process.
><!--
>Write a short (one line) summary that describes the changes in this
>pull request for inclusion in the changelog:
>-->
>


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fmachine-config-operator).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: CONTRIBUTOR
user: login: dkhater-redhat
created_at: 2025-04-14T17:38:38Z
body: this looks good to me as well. ping me once the tests look good. might just need a rerun

author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-15T17:09:47Z
body: /retest
author_association: CONTRIBUTOR
user: login: dkhater-redhat
created_at: 2025-04-15T17:11:00Z
body: /lgtm
author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-04-15T20:10:24Z
body: /retest-required

Remaining retests: 0 against base HEAD d5e84665d5cdce0fb0b03066ebe075655ec4a32e and 2 for PR HEAD 3f04e20ff85a4fe7c632feee23ebe0105b808da2 in total

author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-16T00:05:48Z
body: /retest
author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-04-16T02:54:34Z
body: /retest-required

Remaining retests: 0 against base HEAD d5e84665d5cdce0fb0b03066ebe075655ec4a32e and 2 for PR HEAD 3f04e20ff85a4fe7c632feee23ebe0105b808da2 in total

author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-04-16T08:50:09Z
body: /retest-required

Remaining retests: 0 against base HEAD d5e84665d5cdce0fb0b03066ebe075655ec4a32e and 2 for PR HEAD 3f04e20ff85a4fe7c632feee23ebe0105b808da2 in total

author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-16T17:00:14Z
body: /retest
author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-16T20:03:51Z
body: /retest
author_association: CONTRIBUTOR
user: login: dkhater-redhat
created_at: 2025-04-17T14:25:16Z
body: /lgtm
author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-17T14:39:19Z
body: /retest
author_association: CONTRIBUTOR
user: login: yuqi-zhang
created_at: 2025-04-17T15:57:14Z
body: /override ci/prow/e2e-aws-ovn-upgrade
/override ci/prow/e2e-aws-ovn
/override ci/prow/e2e-hypershift

We've determined that the failures are not related, overriding to take some pressure off CI
author_association: CONTRIBUTOR
user: login: yuqi-zhang
created_at: 2025-04-17T15:57:47Z
body: /hold

for merge order
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-04-17T15:58:41Z
body: @yuqi-zhang: Overrode contexts on behalf of yuqi-zhang: ci/prow/e2e-aws-ovn, ci/prow/e2e-aws-ovn-upgrade, ci/prow/e2e-hypershift

<details>

In response to [this](https://github.com/openshift/machine-config-operator/pull/4971#issuecomment-2813424540):

>/override ci/prow/e2e-aws-ovn-upgrade
>/override ci/prow/e2e-aws-ovn
>/override ci/prow/e2e-hypershift
>
>We've determined that the failures are not related, overriding to take some pressure off CI


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: CONTRIBUTOR
user: login: yuqi-zhang
created_at: 2025-04-17T20:40:39Z
body: /lgtm
/override ci/prow/e2e-aws-ovn-upgrade
/override ci/prow/e2e-aws-ovn
/override ci/prow/e2e-hypershift

for rebase
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-04-17T20:41:15Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openshift/machine-config-operator/pull/4971#issuecomment-2813120294" title="LGTM">dkhater-redhat</a>*, *<a href="https://github.com/openshift/machine-config-operator/pull/4971#" title="Author self-approved">umohnani8</a>*, *<a href="https://github.com/openshift/machine-config-operator/pull/4971#issuecomment-2813978569" title="LGTM">yuqi-zhang</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=openshift%2Fmachine-config-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/openshift/machine-config-operator/blob/main/OWNERS)~~ [dkhater-redhat,umohnani8,yuqi-zhang]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-04-17T20:41:20Z
body: @yuqi-zhang: Overrode contexts on behalf of yuqi-zhang: ci/prow/e2e-aws-ovn, ci/prow/e2e-aws-ovn-upgrade, ci/prow/e2e-hypershift

<details>

In response to [this](https://github.com/openshift/machine-config-operator/pull/4971#issuecomment-2813978569):

>/lgtm
>/override ci/prow/e2e-aws-ovn-upgrade
>/override ci/prow/e2e-aws-ovn
>/override ci/prow/e2e-hypershift
>
>for rebase


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-17T21:16:38Z
body: /hold cancel
author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-04-17T23:34:59Z
body: /retest-required

Remaining retests: 0 against base HEAD b55822c9796bec864b577795c22726aa580cd1a5 and 2 for PR HEAD e3e381ef29276a30df209cc1b041603ec082dce3 in total

author_association: CONTRIBUTOR
user: login: umohnani8
created_at: 2025-04-18T00:58:11Z
body: /refresh
/override ci/prow/e2e-aws-ovn-upgrade
/override ci/prow/e2e-aws-ovn
/skip
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-04-18T00:59:00Z
body: @umohnani8: Overrode contexts on behalf of umohnani8: ci/prow/e2e-aws-ovn, ci/prow/e2e-aws-ovn-upgrade

<details>

In response to [this](https://github.com/openshift/machine-config-operator/pull/4971#issuecomment-2814276611):

>/refresh
>/override ci/prow/e2e-aws-ovn-upgrade
>/override ci/prow/e2e-aws-ovn
>/skip


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-04-18T00:59:06Z
body: @umohnani8: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/e2e-azure-ovn-upgrade-out-of-change | e3e381ef29276a30df209cc1b041603ec082dce3 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/openshift_machine-config-operator/4971/pull-ci-openshift-machine-config-operator-main-e2e-azure-ovn-upgrade-out-of-change/1912966571328802816) | false | `/test e2e-azure-ovn-upgrade-out-of-change`
ci/prow/okd-scos-e2e-aws-ovn | e3e381ef29276a30df209cc1b041603ec082dce3 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/openshift_machine-config-operator/4971/pull-ci-openshift-machine-config-operator-main-okd-scos-e2e-aws-ovn/1912966588693221376) | false | `/test okd-scos-e2e-aws-ovn`
ci/prow/e2e-aws-ovn-upgrade-out-of-change | e3e381ef29276a30df209cc1b041603ec082dce3 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/openshift_machine-config-operator/4971/pull-ci-openshift-machine-config-operator-main-e2e-aws-ovn-upgrade-out-of-change/1912966571282665472) | false | `/test e2e-aws-ovn-upgrade-out-of-change`
ci/prow/bootstrap-unit | e3e381ef29276a30df209cc1b041603ec082dce3 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/openshift_machine-config-operator/4971/pull-ci-openshift-machine-config-operator-main-bootstrap-unit/1912966571051978752) | false | `/test bootstrap-unit`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=openshift&repo=machine-config-operator&pr=4971). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:umohnani8).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
author_association: CONTRIBUTOR
user: login: openshift-bot
created_at: 2025-04-18T05:32:35Z
body: **[ART PR BUILD NOTIFIER]**

Distgit: ose-machine-config-operator
This PR has been included in build [ose-machine-config-operator-container-v4.19.0-202504180443.p0.g320f3d4.assembly.stream.el9](https://brewweb.engineering.redhat.com/brew/buildinfo?buildID=3610511).
All builds following this will include this PR.
author_association: CONTRIBUTOR