Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: implement manifest command #6

Merged
merged 9 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: sudo apt update

# This is needed for the container resolver dependencies
- name: Install libgpgme devel package
- name: Install test dependencies
run: sudo apt install -y libgpgme-dev libbtrfs-dev libdevmapper-dev podman

- name: Build
Expand Down
42 changes: 42 additions & 0 deletions HACKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Hacking on image-builder-cli

Hacking on `image-builder` should be fun and is easy.

We have unit tests and some integration testing.

## Setup

To work on bootc-image-builder one needs a working Go environment. See
[go.mod](go.mod).

To run the testsuite install the test dependencies as outlined in the
[github action](./.github/workflows/go.yml) under
"Install test dependencies".

## Code layout

The go source code of image-builder-cli is under
`./cmd/image-builder`. It uses the
[images](https://github.com/osbuild/images) library internally to
generate the images. Unit tests (and integration tests where it makes
sense) are expected to be part of every PR but we are happy to help if
those are missing from a PR.

## Build

Build by running:
```console
$ go build ./cmd/image-builder/
```

## Unit tests

Run the unit tests via:
```console
$ go test -short ./...
```

There are some integration tests that can be run via:
```console
$ go test ./...
```
30 changes: 30 additions & 0 deletions cmd/image-builder/distro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"fmt"

"github.com/osbuild/images/pkg/distro"
)

var distroGetHostDistroName = distro.GetHostDistroName

// findDistro will ensure that the given distro argument do not
// diverge. If no distro is set via the blueprint or the argument
// the host is used to derive the distro.
func findDistro(argDistroName, bpDistroName string) (string, error) {
switch {
case argDistroName != "" && bpDistroName != "" && argDistroName != bpDistroName:
return "", fmt.Errorf("error selecting distro name, cmdline argument %q is different from blueprint %q", argDistroName, bpDistroName)
case argDistroName != "":
return argDistroName, nil
case bpDistroName != "":
return bpDistroName, nil
}
// nothing selected by the user, derive from host
distroStr, err := distroGetHostDistroName()
if err != nil {
return "", fmt.Errorf("error deriving host distro %w", err)
}
fmt.Fprintf(osStderr, "No distro name specified, selecting %q based on host, use --distro to override", distroStr)
return distroStr, nil
}
48 changes: 48 additions & 0 deletions cmd/image-builder/distro_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main_test

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"

"github.com/osbuild/image-builder-cli/cmd/image-builder"
)

func TestFindDistro(t *testing.T) {
for _, tc := range []struct {
argDistro string
bpDistro string
expectedDistro string
expectedErr string
}{
{"arg", "", "arg", ""},
{"", "bp", "bp", ""},
{"arg", "bp", "", `error selecting distro name, cmdline argument "arg" is different from blueprint "bp"`},
// the argDistro,bpDistro == "" case is tested below
} {
distro, err := main.FindDistro(tc.argDistro, tc.bpDistro)
if tc.expectedErr != "" {
assert.Equal(t, tc.expectedErr, err.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tc.expectedDistro, distro)
}
}
}

func TestFindDistroAutoDetect(t *testing.T) {
var buf bytes.Buffer
restore := main.MockOsStderr(&buf)
defer restore()

restore = main.MockDistroGetHostDistroName(func() (string, error) {
return "mocked-host-distro", nil
})
defer restore()

distro, err := main.FindDistro("", "")
assert.NoError(t, err)
assert.Equal(t, "mocked-host-distro", distro)
assert.Equal(t, `No distro name specified, selecting "mocked-host-distro" based on host, use --distro to override`, buf.String())
}
16 changes: 13 additions & 3 deletions cmd/image-builder/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import (
"github.com/osbuild/images/pkg/reporegistry"
)

var (
GetOneImage = getOneImage
Run = run
FindDistro = findDistro
)

func MockOsArgs(new []string) (restore func()) {
saved := os.Args
os.Args = append([]string{"argv0"}, new...)
Expand Down Expand Up @@ -45,6 +51,10 @@ func MockNewRepoRegistry(f func() (*reporegistry.RepoRegistry, error)) (restore
}
}

var (
Run = run
)
func MockDistroGetHostDistroName(f func() (string, error)) (restore func()) {
saved := distroGetHostDistroName
distroGetHostDistroName = f
return func() {
distroGetHostDistroName = saved
}
}
48 changes: 48 additions & 0 deletions cmd/image-builder/filters.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package main

import (
"fmt"
"strings"

"github.com/gobwas/glob"

"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/imagefilter"
)
Expand All @@ -11,5 +16,48 @@ func newImageFilterDefault(dataDir string) (*imagefilter.ImageFilter, error) {
if err != nil {
return nil, err
}

return imagefilter.New(fac, repos)
}

// should this be moved to images:imagefilter?
func getOneImage(dataDir, distroName, imgTypeStr, archStr string) (*imagefilter.Result, error) {
imageFilter, err := newImageFilterDefault(dataDir)
if err != nil {
return nil, err
}
// strip prefixes to make ib copy/paste friendly when pasting output
// from "list-images"
distroName = strings.TrimPrefix(distroName, "distro:")
imgTypeStr = strings.TrimPrefix(imgTypeStr, "type:")
archStr = strings.TrimPrefix(archStr, "arch:")

// error early when globs are used
for _, s := range []string{distroName, imgTypeStr, archStr} {
if glob.QuoteMeta(s) != s {
return nil, fmt.Errorf("cannot use globs in %q when getting a single image", s)
}
}

filterExprs := []string{
fmt.Sprintf("distro:%s", distroName),
fmt.Sprintf("arch:%s", archStr),
fmt.Sprintf("type:%s", imgTypeStr),
}
filteredResults, err := imageFilter.Filter(filterExprs...)
if err != nil {
return nil, err
}
switch len(filteredResults) {
case 0:
return nil, fmt.Errorf("cannot find image for: distro:%q type:%q arch:%q", distroName, imgTypeStr, archStr)
case 1:
return &filteredResults[0], nil
default:
// This condition should never be hit in practise as we
// disallow globs above.
// XXX: imagefilter.Result should have a String() method so
// that this output can actually show the results
return nil, fmt.Errorf("internal error: found %v results for %q %q %q", len(filteredResults), distroName, imgTypeStr, archStr)
}
}
55 changes: 55 additions & 0 deletions cmd/image-builder/filters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main_test

import (
"testing"

"github.com/stretchr/testify/assert"

testrepos "github.com/osbuild/images/test/data/repositories"

"github.com/osbuild/image-builder-cli/cmd/image-builder"
)

func TestGetOneImageHappy(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()

dataDir := ""
for _, tc := range []struct {
distro, imgType, arch string
}{
{"centos-9", "qcow2", "x86_64"},
{"distro:centos-9", "qcow2", "x86_64"},
{"distro:centos-9", "type:qcow2", "x86_64"},
{"distro:centos-9", "type:qcow2", "arch:x86_64"},
} {
res, err := main.GetOneImage(dataDir, tc.distro, tc.imgType, tc.arch)
assert.NoError(t, err)
assert.Equal(t, "centos-9", res.Distro.Name())
assert.Equal(t, "qcow2", res.ImgType.Name())
assert.Equal(t, "x86_64", res.Arch.Name())
}
}

func TestGetOneImageError(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()

dataDir := ""
for _, tc := range []struct {
distro, imgType, arch string
expectedErr string
}{
{
"unknown", "qcow2", "x86_64",
`cannot find image for: distro:"unknown" type:"qcow2" arch:"x86_64"`,
},
{
"centos*", "qcow2", "x86_64",
`cannot use globs in "centos*" when getting a single image`,
},
} {
_, err := main.GetOneImage(dataDir, tc.distro, tc.imgType, tc.arch)
assert.EqualError(t, err, tc.expectedErr)
}
}
6 changes: 3 additions & 3 deletions cmd/image-builder/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"github.com/osbuild/images/pkg/imagefilter"
)

func listImages(output string, filterExprs []string, opts *cmdlineOpts) error {
imageFilter, err := newImageFilterDefault(opts.dataDir)
func listImages(dataDir, output string, filterExprs []string) error {
imageFilter, err := newImageFilterDefault(dataDir)
if err != nil {
return err
}
Expand All @@ -19,7 +19,7 @@ func listImages(output string, filterExprs []string, opts *cmdlineOpts) error {
if err != nil {
return err
}
if err := fmter.Output(opts.out, filteredResult); err != nil {
if err := fmter.Output(osStdout, filteredResult); err != nil {
return err
}

Expand Down
Loading
Loading