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

Add new build-cimage command #3128

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
}
return "", nil
}

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 @@ -173,6 +173,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