Skip to content

adds machine-config-osimagestream bootstrap helper#5770

Draft
cheesesashimi wants to merge 2 commits intoopenshift:mainfrom
cheesesashimi:zzlotnik/add-osimagestream-generation-cmd
Draft

adds machine-config-osimagestream bootstrap helper#5770
cheesesashimi wants to merge 2 commits intoopenshift:mainfrom
cheesesashimi:zzlotnik/add-osimagestream-generation-cmd

Conversation

@cheesesashimi
Copy link
Member

@cheesesashimi cheesesashimi commented Mar 16, 2026

- What I did

This introduces a new helper binary called machine-config-osimagestream that will examine either the given release image pullspec or the provided ImageStream file to create an OSImageStream object in either JSON or YAML format. This is mostly intended to be used by the bootstrap process, but could also be used to help debug the OSImageStream produced by a given release image or ImageStream file.

There is also a default-node-image mode which will get the OSImageStream and then look up the name of the default OS image by matching the pullspec for the default OS image to the pullspec in the ImageStream.

- How to verify it

  1. Clone this repo locally.
  2. go build -o machine-config-osimagestream ./cmd/machine-config-osimagestream
  3. Run: ./machine-config-osimagestream get osimagestream --release-image registry.ci.openshift.org/ocp/release-5:5.0.0-0.nightly-2026-03-16-164911 --pull-secret /path/to/pull/secret
  4. Run: ./machine-config-osimagestream get default-node-image --release-image registry.ci.openshift.org/ocp/release-5:5.0.0-0.nightly-2026-03-16-164911 --pull-secret /path/to/pull/secret

- Description for the changelog
Adds machine-config-osimagestream

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Excluded labels (none allowed) (1)
  • do-not-merge/work-in-progress

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b221b3c-17e9-4ee0-b318-380deae1da82

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 16, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 16, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 16, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cheesesashimi

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 16, 2026
@cheesesashimi cheesesashimi force-pushed the zzlotnik/add-osimagestream-generation-cmd branch from 7904d0a to a479290 Compare March 17, 2026 00:10
getCmd.PersistentFlags().StringVar(&o.outputFormat, "output-format", "json", "The output format to use: 'json' or 'yaml'.")
getCmd.PersistentFlags().StringVar(&o.outputFile, "output-file", "", "Path to write output to a file instead of stdout. Format is auto-detected from extension (.json, .yaml, .yml).")

getCmd.MarkPersistentFlagRequired("pull-secret")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this seems good to me, I think most of the bootstrap code in the installer is thought to use default paths. The installer generated ignition for the bootstrap node takes care of placing the pull-secret, mirrot and proxy configuration in the correct file so calls to podman (or skopeo) don't need to provide those args everywhere. The only exception I've observed is rpm-ostree, that requiers the pull-secret to be passed with --authfile.
Following to the previous point, we need to make really sure mirror, proxy and custom CAs are properly loaded, no matter if it's because the defaults of container-libs do the job or because we use the builder imageutils.NewSysContextBuilder returns to craft a proper auth/tls/mirror/proxy context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done a quick search in the installer and these are my findings:

  1. I think the proxy env vars are automatically loaded by ignition into systemd.conf.d/10-default-env.conf and into /etc/profile.d/proxy.sh. I'd expect that the proxy env vars are present when the unit runs.
  2. registries.conf (mirror rules): I've seen a few places (specially in the agent, that is the workflow that relies on them, but not exclusively) where the installer generates it for the final ignition. Not too sure about this one, but my vet is that the file should be already placed in /etc/containers/registries.conf when the installer units run. If not, I cannot explain why rpm-ostree works.
  3. pull-secret. Exists cause it's rendered by this template file https://github.com/openshift/installer/blob/791a41c20c5d3c72dee4aca31df817710a7d39b0/data/data/bootstrap/files/root/.docker/config.json.template (everything inside the files directory of the assets it pushed to the bootstrap by ignition)
  4. Custom CAs: Not 100% sure, but seems to be like the previous point: https://github.com/openshift/installer/blob/59e504062d51674fc20394ac043bf0e3e022c385/data/data/bootstrap/files/etc/pki/ca-trust/source/anchors/ca.crt.template

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I began integrating this into the installer a bit (see: openshift/installer#10406), I began to realize that another problem we'll have to deal with is mounting things into the container this will run in. I contemplated an operational mode where we extract the binary from the image and run it natively on the bootstrap host so that it can access what it needs given the original execution context.

@cheesesashimi cheesesashimi force-pushed the zzlotnik/add-osimagestream-generation-cmd branch 4 times, most recently from 99a50d4 to 0de6a09 Compare March 18, 2026 17:38
@cheesesashimi cheesesashimi force-pushed the zzlotnik/add-osimagestream-generation-cmd branch 3 times, most recently from cddae24 to 50150a3 Compare March 19, 2026 14:57

// Wraps imageRegistrySecretImpl and implements UnmarshalJSON so that
// progressive JSON decoding may be used.
type dockerConfigJSONDecoder struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation would need some UT testing. I know the type it's not exported so we may not want to directly test it, but test the caller with some inputs that give us good coverage of this piede of code.

CertDir: opts.certsPath,
PerHostCertDir: opts.perHostCertsPath,
PullSecret: opts.pullSecretPath,
RegistryConfig: opts.registryConfigPath,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Should we provide safe defaults and always pass them to container-libs or are we advocating to the container-libs defaults?
In other words, do we want to pick a default and never trust/rely on their defaults?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could go either way with this, honestly.

Comment on lines +123 to +169
# Get the full image pullspec from release image:
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--release-image quay.io/openshift-release-dev/ocp-release:latest \
--image-pullspec

# Get the image tag name from ImageStream file:
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--imagestream /path/to/imagestream.json \
--image-name

# Get the full image pullspec from ImageStream file:
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--imagestream /path/to/imagestream.yaml \
--image-pullspec

# With per-host certificates (output tag name):
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--per-host-certs /etc/containers/certs.d \
--release-image quay.io/openshift-release-dev/ocp-release:latest \
--image-name

# With custom certificates and registry configuration (output pullspec):
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--certs /path/to/certs \
--registry-config /etc/containers/registries.conf \
--release-image quay.io/openshift-release-dev/ocp-release:latest \
--image-pullspec

# With additional trust bundle (single file, output tag name):
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--trust-bundle /etc/pki/ca-trust/source/anchors/ca-bundle.crt \
--release-image quay.io/openshift-release-dev/ocp-release:latest \
--image-name

# With multiple trust bundles (files and directories, output pullspec):
$ machine-config-osimagestream get default-node-image \
--pull-secret /path/to/pull/secret \
--trust-bundle /etc/pki/ca-trust/source/anchors/ \
--trust-bundle /opt/custom/cert.pem \
--release-image quay.io/openshift-release-dev/ocp-release:latest \
--image-pullspec`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've the feeling this is way too much for the help option of the binary. I'd say the examples are nice, but I see them fitting better in a doc md.
The same applies to 55c004f#diff-ad14b191f88ac54c22961ae04f78ba66ecadad7b167abeae58a377ef3d0d85c4R38

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. I'll rework the examples and produce a README file instead.

getCmd.PersistentFlags().StringVar(&o.releaseImage, "release-image", "", "The OCP / OKD release payload image to run against.")
getCmd.PersistentFlags().StringVar(&o.imageStreamPath, "imagestream", "", "Path to the ImageStream file to run against.")

getCmd.MarkPersistentFlagRequired("pull-secret")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most tools tools use --authfile to provide a pull-secret, and if not given they fallback to ${XDG_RUNTIME_DIR}/containers/auth.json (among others, including the one the installer uses for everything, including podman /root/.docker./config.json https://github.com/containers/skopeo/blob/f9d4a40261d9f6dd924d22274805cd075e1fe939/vendor/go.podman.io/common/pkg/auth/auth.go#L83.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I will change that.

"strings"
"time"

"github.com/ghodss/yaml"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"sigs.k8s.io/yaml", that uses github.com/ghodss/yaml underneath, is a bit better fit I think:

  • It's maintained
  • It handles multline strings better. (not required for this use case)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I keep forgetting about that library. Will change my code to use that.


// TODO: Not sure if this value is used by types.SystemContext or not. But
// we'll retrieve it anyway for right now.
if noProxy := os.Getenv("NO_PROXY"); noProxy != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately no, I introduced a change in container-libs to be able to properly consume the 3 fields https://github.com/containers/container-libs/pull/583/changes (it will allow us to provide a callback that consumes them), but given that they don't backport and we cannot upgrade we cannot use it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it for now since it's going unused. But I'll leave a comment in its place containing this context.

}

return getImageStreamFromReleaseImage(ctx, sysCtx, releaseImage)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprissed about the content of this function, as I'd expected something more like this:

func getImageStreamProvider(ctx context.Context, imagesInspector ImagesInspector, imageStreamPath, releaseImage string) (osimagestream.ImageStreamProvider,  error)
  if imageStreamPath != "" {
    is, err := getImageStreamFromFile(imageStreamPath)
    if err != nil {
       return nil, err
    }
    return osimagestreams.NewImageStreamProviderResource(is)
  }
  return osimagestreams.NewImageStreamProviderNetwork(imagesInspector, releaseImage)
}

Later on you can directly pass the ImageStreamProvider to the NewImageStreamStreamSource you already have in place. The ImagesInspector you create later would be now created a bit before to provide it to this function.

Copy link
Member Author

@cheesesashimi cheesesashimi Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I originally implemented it like that. Will reconsider though.

EDIT: Here's more context:

I originally thought that we just needed the OS image tag name (e.g., rhel-coreos) instead of the OS image pullspec. To resolve that from the OSImageStream, I also needed the ImageStream itself so I could look it up. As it turns out, the image tag name is unimportant. So with that in mind, I changed it to something similar to what you suggested.

@cheesesashimi
Copy link
Member Author

@pablintino Putting this here for posterity: On its own, this code doesn't adequately handle OKD. I noticed that #5714 does handle it much better. So I created a separate branch and cherry-picked it locally. Once I did that, this code handled OKD with no issues.

@cheesesashimi cheesesashimi force-pushed the zzlotnik/add-osimagestream-generation-cmd branch from 50150a3 to 6a43924 Compare March 20, 2026 16:34
Assisted-By: Claude Sonnet 4.5
This adds additional test cases for K8s secret decoding since I ran into
what I thought was a bug when trying to write additional test cases for
the new SysContext helper.

Assisted-By: Claude Sonnet 4.5
@cheesesashimi cheesesashimi force-pushed the zzlotnik/add-osimagestream-generation-cmd branch from 6a43924 to 479346d Compare March 20, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants