main: add new --registrations options

This new flag allows to add a file with registration data. This
is meant to eventually hold all sort of registrations like
ansible or satelite but initially only contains the redhat
subscription. Currently only JSON is supported.

It looks like:
```json:
{
  "redhat": {
    "subscription": {
      "activation_key": "ak_123",
      "organization": "org_123",
      "server_url": "server_url_123",
      "base_url": "base_url_123",
      "insights": true,
      "rhc": true,
      "proxy": "proxy_123"
    }
  }
}
```

This is not part of the blueprint (today) because its more
ephemeral than the things we usually put into the blueprint.

This allows us to build images that are immediately registered. It
also keeps our options open in the future. If we move to a new
blueprint format where we support multiple blueprints and also
ephemeral data like this the "registrations" flag just becomes an
alias for "--blueprint".
This commit is contained in:
Michael Vogt 2025-04-10 10:59:15 +02:00 committed by Simon de Vlieger
parent 020dbd6f41
commit aecbe5928a
4 changed files with 140 additions and 3 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/osbuild/images/pkg/cloud"
"github.com/osbuild/images/pkg/cloud/awscloud"
"github.com/osbuild/images/pkg/manifestgen"
"github.com/osbuild/images/pkg/reporegistry"
)
@ -71,3 +72,11 @@ func MockAwscloudNewUploader(f func(string, string, string, *awscloud.UploaderOp
awscloudNewUploader = saved
}
}
func MockManifestgenDepsolver(new manifestgen.DepsolveFunc) (restore func()) {
saved := manifestgenDepsolver
manifestgenDepsolver = new
return func() {
manifestgenDepsolver = saved
}
}

View file

@ -2,6 +2,7 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@ -17,6 +18,7 @@ import (
"github.com/osbuild/image-builder-cli/pkg/progress"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/customizations/subscription"
"github.com/osbuild/images/pkg/imagefilter"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
@ -97,6 +99,37 @@ func ostreeImageOptions(cmd *cobra.Command) (*ostree.ImageOptions, error) {
}, nil
}
type registrations struct {
Redhat struct {
Subscription *subscription.ImageOptions `json:"subscription,omitempty"`
} `json:"redhat,omitempty"`
}
func subscriptionImageOptions(cmd *cobra.Command) (*subscription.ImageOptions, error) {
regFilePath, err := cmd.Flags().GetString("registrations")
if err != nil {
return nil, err
}
if regFilePath == "" {
return nil, nil
}
f, err := os.Open(regFilePath)
if err != nil {
return nil, fmt.Errorf("cannot open registrations file: %w", err)
}
defer f.Close()
// XXX: support yaml eventually
var regs registrations
dec := json.NewDecoder(f)
dec.DisallowUnknownFields()
if err := dec.Decode(&regs); err != nil {
return nil, fmt.Errorf("cannot parse registrations file: %w", err)
}
return regs.Redhat.Subscription, nil
}
type cmdManifestWrapperOptions struct {
useBootstrapIfNeeded bool
}
@ -160,6 +193,10 @@ func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []st
}
customSeed = &seedFlagVal
}
subscription, err := subscriptionImageOptions(cmd)
if err != nil {
return nil, err
}
// no error check here as this is (deliberately) not defined on
// "manifest" (if "images" learn to set the output filename in
// manifests we would change this
@ -199,6 +236,7 @@ func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []st
RpmDownloader: rpmDownloader,
WithSBOM: withSBOM,
CustomSeed: customSeed,
Subscription: subscription,
ForceRepos: forceRepos,
}
@ -407,7 +445,7 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support.
RunE: cmdListImages,
SilenceUsage: true,
Args: cobra.NoArgs,
Aliases: []string{"list-images"},
Aliases: []string{"list-images"},
}
listCmd.Flags().StringArray("filter", nil, `Filter distributions by a specific criteria (e.g. "type:iot*")`)
listCmd.Flags().String("format", "", "Output in a specific format (text, json)")
@ -430,6 +468,7 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support.
manifestCmd.Flags().String("ostree-url", "", `OSTREE url`)
manifestCmd.Flags().Bool("use-librepo", true, `use librepo to download packages (disable if you use old versions of osbuild)`)
manifestCmd.Flags().Bool("with-sbom", false, `export SPDX SBOM document`)
manifestCmd.Flags().String("registrations", "", `filename of a registrations file with e.g. subscription details`)
rootCmd.AddCommand(manifestCmd)
uploadCmd := &cobra.Command{

View file

@ -2,6 +2,7 @@ package main_test
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
@ -18,6 +19,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/dnfjson"
"github.com/osbuild/images/pkg/rpmmd"
testrepos "github.com/osbuild/images/test/data/repositories"
main "github.com/osbuild/image-builder-cli/cmd/image-builder"
@ -948,3 +952,81 @@ func TestBasenameFor(t *testing.T) {
assert.Equal(t, tc.expected, main.BasenameFor(res, tc.basename))
}
}
// XXX: move into as manifestgen.FakeDepsolve
func fakeDepsolve(cacheDir string, depsolveWarningsOutput io.Writer, packageSets map[string][]rpmmd.PackageSet, d distro.Distro, arch string) (map[string]dnfjson.DepsolveResult, error) {
depsolvedSets := make(map[string]dnfjson.DepsolveResult)
for name, pkgSetChain := range packageSets {
specSet := make([]rpmmd.PackageSpec, 0)
for _, pkgSet := range pkgSetChain {
include := pkgSet.Include
slices.Sort(include)
for _, pkgName := range include {
checksum := fmt.Sprintf("%x", sha256.Sum256([]byte(pkgName)))
spec := rpmmd.PackageSpec{
Name: pkgName,
Checksum: "sha256:" + checksum,
}
specSet = append(specSet, spec)
}
depsolvedSets[name] = dnfjson.DepsolveResult{
Packages: specSet,
Repos: pkgSet.Repositories,
}
}
}
return depsolvedSets, nil
}
func TestManifestIntegrationWithRegistrations(t *testing.T) {
restore := main.MockManifestgenDepsolver(fakeDepsolve)
defer restore()
restore = main.MockNewRepoRegistry(testrepos.New)
defer restore()
// XXX: only "proxy_123", "server_url_123" are actually observable
// in the manifest(?)
fakeRegContent := `{
"redhat": {
"subscription": {
"activation_key": "ak_123",
"organization": "org_123",
"server_url": "server_url_123",
"base_url": "base_url_123",
"insights": true,
"rhc": true,
"proxy": "proxy_123"
}
}
}`
fakeRegistrationsPath := filepath.Join(t.TempDir(), "registrations.json")
err := os.WriteFile(fakeRegistrationsPath, []byte(fakeRegContent), 0644)
assert.NoError(t, err)
// XXX: fake the depsolving or we will need full support for
// subscripbed hosts in the tests
restore = main.MockOsArgs([]string{
"manifest",
"qcow2",
"--arch=x86_64",
"--distro=rhel-9.6",
"--registrations", fakeRegistrationsPath,
})
defer restore()
var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()
err = main.Run()
assert.NoError(t, err)
// XXX: manifesttest really needs to grow more helpers
assert.Contains(t, fakeStdout.String(), `{"type":"org.osbuild.insights-client.config","options":{"proxy":"proxy_123"}}`)
assert.Contains(t, fakeStdout.String(), `"type":"org.osbuild.systemd.unit.create","options":{"filename":"osbuild-subscription-register.service"`)
assert.Contains(t, fakeStdout.String(), `server_url_123`)
}

View file

@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"github.com/osbuild/images/pkg/customizations/subscription"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/imagefilter"
"github.com/osbuild/images/pkg/manifestgen"
@ -22,6 +23,7 @@ type manifestOptions struct {
OutputFilename string
BlueprintPath string
Ostree *ostree.ImageOptions
Subscription *subscription.ImageOptions
RpmDownloader osbuild.RpmDownloader
WithSBOM bool
CustomSeed *int64
@ -46,6 +48,9 @@ func sbomWriter(outputDir, filename string, content io.Reader) error {
return nil
}
// used in tests
var manifestgenDepsolver manifestgen.DepsolveFunc
func generateManifest(dataDir string, extraRepos []string, img *imagefilter.Result, output io.Writer, depsolveWarningsOutput io.Writer, opts *manifestOptions) error {
repos, err := newRepoRegistry(dataDir, extraRepos)
if err != nil {
@ -58,6 +63,7 @@ func generateManifest(dataDir string, extraRepos []string, img *imagefilter.Resu
RpmDownloader: opts.RpmDownloader,
UseBootstrapContainer: opts.UseBootstrapContainer,
CustomSeed: opts.CustomSeed,
Depsolver: manifestgenDepsolver,
}
if opts.WithSBOM {
outputDir := basenameFor(img, opts.OutputDir)
@ -84,9 +90,10 @@ func generateManifest(dataDir string, extraRepos []string, img *imagefilter.Resu
return err
}
var imgOpts *distro.ImageOptions
if opts.Ostree != nil {
if opts.Ostree != nil || opts.Subscription != nil {
imgOpts = &distro.ImageOptions{
OSTree: opts.Ostree,
OSTree: opts.Ostree,
Subscription: opts.Subscription,
}
}