Skip to content

Commit

Permalink
Add new build-cimage command
Browse files Browse the repository at this point in the history
This is a big step towards coreos#2685

I know there's a lot going on with the pipeline, and I don't
want to conflict with all that work - but at the same time,
in my opinion we are just too dependent on complex Jenkins flows
and our bespoke "meta.json in S3".

The core of CoreOS *is a container image* now.  This new command
adds an opinionated flow where one can do:

```
$ cosa init
$ cosa build-cimage quay.io/cgwalters/ostest
```

And *that's it* - we do proper change detection, reading and
writing from the remote container image.  We don't do silly things
like storing an `.ociarchive` in S3 when we have native registries
available.

Later, we can build on this and rework our disk images to
derive from that container image, as coreos#2685 calls for.

Also in the near term future, I think we can rework `cmd-build`
such that it reuses this flow, but outputs to an `.ociarchive` instead.
However, this code is going to need a bit more work to run in
supermin.
  • Loading branch information
cgwalters committed Oct 20, 2022
1 parent 7a7190a commit 4e6df71
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 0 deletions.
188 changes: 188 additions & 0 deletions cmd/buildcimage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// See usage below
package main

// build-cimage is a wrapper for `rpm-ostree compose image`; for more
// on that, see https://coreos.github.io/rpm-ostree/container/
//
// A key motivation here is to sever the dependency on S3 (and meta.json) for
// our container image builds. As part of the ostree native container work,
// the core of CoreOS becomes a container image. Our disk images
// have always been derivatives of the container, and this is pushing things
// farther in that direction.
// See https://github.com/coreos/coreos-assembler/issues/2685
//
// This command is opinionated on reading and writing to a remote registry,
// whereas the underlying `rpm-ostree compose image` defaults to
// an ociarchive.

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/coreos/coreos-assembler/internal/pkg/cmdrun"
"github.com/coreos/coreos-assembler/internal/pkg/cosash"
"github.com/spf13/cobra"
)

const initConfigPath = "src/config.json"
const defaultManifest = "src/config/manifest.yaml"

type BuildCImageOptions struct {
authfile string
initialize bool
}

var (
BuildCImageOpts BuildCImageOptions

cmdBuildCImage = &cobra.Command{
Use: "build-cimage",
Short: "cosa build-cimage [repository]",
Args: cobra.ExactArgs(1),
Long: "Initialize directory for ostree container image build",
RunE: implRunBuildCImage,
}
)

func init() {
cmdBuildCImage.Flags().BoolVarP(
&BuildCImageOpts.initialize, "initialize", "i", false,
"Assume target image does not exist")
cmdBuildCImage.Flags().StringVar(
&BuildCImageOpts.authfile, "authfile", "",
"Path to container authentication file")
}

func runBuildCImage(argv []string) error {
cmdBuildCImage.SetArgs(argv)
return cmdBuildCImage.Execute()
}

// This is a Go reipmlementation of pick_yaml_or_else_json() from cmdlib.sh
func pickConfigFileYamlOrJson(name string, preferJson bool) (string, error) {
jsonPath := fmt.Sprintf("src/config/%s.json", name)
yamlPath := fmt.Sprintf("src/config/%s.yaml", name)
if _, err := os.Stat(jsonPath); err != nil {
if !os.IsNotExist(err) {
return "", err
}
jsonPath = ""
}
if _, err := os.Stat(yamlPath); err != nil {
if !os.IsNotExist(err) {
return "", err
}
yamlPath = ""
}
if jsonPath != "" && yamlPath != "" {
return "", fmt.Errorf("found both %s and %s", jsonPath, yamlPath)
}
if jsonPath != "" {
return jsonPath, nil
}
return yamlPath, nil
}

type configVariant struct {
Variant string `json:"coreos-assembler.config-variant"`
}

func getVariant() (string, error) {
contents, err := ioutil.ReadFile(initConfigPath)
if err != nil {
if !os.IsNotExist(err) {
return "", err
}
contents = []byte{}
}

var variantData configVariant
if err := json.Unmarshal(contents, &variantData); err != nil {
return "", fmt.Errorf("parsing %s: %w", initConfigPath, err)
}

return variantData.Variant, nil
}

func implRunBuildCImage(c *cobra.Command, args []string) error {
if err := cmdrun.RunCmdSyncV("cosa", "build", "--prepare-only"); err != nil {
return err
}

csh, err := cosash.NewCosaSh()
if err != nil {
return err
}

basearch, err := csh.BaseArch()
if err != nil {
return err
}
variant, err := getVariant()
if err != nil {
return err
}
manifest := defaultManifest
if variant != "" {
manifest = fmt.Sprintf("src/config/manifest-%s.yaml", variant)
}

repository := args[0]

buildArgs := []string{"compose", "image", "--format", "registry", "--layer-repo", "tmp/repo"}
if BuildCImageOpts.initialize {
buildArgs = append(buildArgs, "--initialize")
}
if BuildCImageOpts.authfile != "" {
buildArgs = append(buildArgs, "--authfile", BuildCImageOpts.authfile)
}
if _, err := os.Stat("tmp/cosa-transient"); err != nil {
if !os.IsNotExist(err) {
return err
}
cachedir := "cache/buildcimage-cache"
if err := os.MkdirAll(cachedir, 0o755); err != nil {
return err
}
buildArgs = append(buildArgs, "--cachedir", cachedir)
}
manifestLock, err := pickConfigFileYamlOrJson(fmt.Sprintf("manifest-lock.%s", basearch), true)
if err != nil {
return err
}
manifestLockOverrides, err := pickConfigFileYamlOrJson("manifest-lock.overrides", false)
if err != nil {
return err
}
manifestLockArchOverrides, err := pickConfigFileYamlOrJson(fmt.Sprintf("manifest-lock.overrides.%s", basearch), false)
if err != nil {
return err
}
for _, lock := range []string{manifestLock, manifestLockOverrides, manifestLockArchOverrides} {
if lock != "" {
buildArgs = append(buildArgs, "--lockfile", lock)
}
}
buildArgs = append(buildArgs, manifest)
buildArgs = append(buildArgs, repository)

argv0 := "rpm-ostree"
priv, err := csh.HasPrivileges()
if err != nil {
return err
}
if priv {
argv0 = "sudo"
buildArgs = append([]string{"rpm-ostree"}, buildArgs...)
} else {
return fmt.Errorf("this command currently requires the ability to create nested containers")
}

if err := cmdrun.RunCmdSyncV(argv0, buildArgs...); err != nil {
return err
}

return nil
}
2 changes: 2 additions & 0 deletions cmd/coreos-assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ func run(argv []string) error {
switch cmd {
case "clean":
return runClean(argv)
case "build-cimage":
return runBuildCImage(argv)
case "update-variant":
return runUpdateVariant(argv)
case "remote-session":
Expand Down
31 changes: 31 additions & 0 deletions internal/pkg/cmdrun/cmdrun.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmdrun

import (
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)

// Synchronously invoke a command, logging the command arguments
// to stdout.
func RunCmdSyncV(cmdName string, args ...string) error {
fmt.Printf("Running: %s %s\n", cmdName, strings.Join(args, " "))
return RunCmdSync(cmdName, args...)
}

// Synchronously invoke a command, passing both stdout and stderr.
func RunCmdSync(cmdName string, args ...string) error {
cmd := exec.Command(cmdName, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM,
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running %s %s: %w", cmdName, strings.Join(args, " "), err)
}

return nil
}
5 changes: 5 additions & 0 deletions internal/pkg/cosash/cosash.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ pwd >&3
`)
}

// BaseArch returns the base architecture
func (sh *CosaSh) BaseArch() (string, error) {
return sh.ProcessWithReply(`echo $basearch >&3`)
}

// HasPrivileges checks if we can use sudo
func (sh *CosaSh) HasPrivileges() (bool, error) {
r, err := sh.ProcessWithReply(`
Expand Down
1 change: 1 addition & 0 deletions src/cmd-fetch
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ fi

prepare_build

# NOTE: Keep this logic in sync with buildcimage.go
args=
if [ -n "${UPDATE_LOCKFILE}" ]; then
# Put this under tmprepo so it gets automatically chown'ed if needed
Expand Down

0 comments on commit 4e6df71

Please sign in to comment.