Skip to content

Commit 16a0085

Browse files
committed
Add new build-cimage command
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.
1 parent 0083086 commit 16a0085

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

cmd/buildcimage.go

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// See usage below
2+
package main
3+
4+
// build-cimage is a wrapper for `rpm-ostree compose image`; for more
5+
// on that, see https://coreos.github.io/rpm-ostree/container/
6+
//
7+
// A key motivation here is to sever the dependency on S3 (and meta.json) for
8+
// our container image builds. As part of the ostree native container work,
9+
// the core of CoreOS becomes a container image. Our disk images
10+
// have always been derivatives of the container, and this is pushing things
11+
// farther in that direction.
12+
// See https://github.com/coreos/coreos-assembler/issues/2685
13+
//
14+
// This command is opinionated on reading and writing to a remote registry,
15+
// whereas the underlying `rpm-ostree compose image` defaults to
16+
// an ociarchive.
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
"io/ioutil"
22+
"os"
23+
24+
"github.com/coreos/coreos-assembler/internal/pkg/cmdrun"
25+
"github.com/coreos/coreos-assembler/internal/pkg/cosash"
26+
"github.com/spf13/cobra"
27+
)
28+
29+
const initConfigPath = "src/config.json"
30+
const defaultManifest = "src/config/manifest.yaml"
31+
32+
type BuildCImageOptions struct {
33+
authfile string
34+
initialize bool
35+
}
36+
37+
var (
38+
BuildCImageOpts BuildCImageOptions
39+
40+
cmdBuildCImage = &cobra.Command{
41+
Use: "build-cimage",
42+
Short: "cosa build-cimage [repository]",
43+
Args: cobra.ExactArgs(1),
44+
Long: "Initialize directory for ostree container image build",
45+
RunE: implRunBuildCImage,
46+
}
47+
)
48+
49+
func init() {
50+
cmdBuildCImage.Flags().BoolVarP(
51+
&BuildCImageOpts.initialize, "initialize", "i", false,
52+
"Assume target image does not exist")
53+
cmdBuildCImage.Flags().StringVar(
54+
&BuildCImageOpts.authfile, "authfile", "",
55+
"Path to container authentication file")
56+
}
57+
58+
func runBuildCImage(argv []string) error {
59+
cmdBuildCImage.SetArgs(argv)
60+
return cmdBuildCImage.Execute()
61+
}
62+
63+
// This is a Go reipmlementation of pick_yaml_or_else_json() from cmdlib.sh
64+
func pickConfigFileYamlOrJson(name string, preferJson bool) (string, error) {
65+
jsonPath := fmt.Sprintf("src/config/%s.json", name)
66+
yamlPath := fmt.Sprintf("src/config/%s.yaml", name)
67+
if _, err := os.Stat(jsonPath); err != nil {
68+
if !os.IsNotExist(err) {
69+
return "", err
70+
}
71+
jsonPath = ""
72+
}
73+
if _, err := os.Stat(yamlPath); err != nil {
74+
if !os.IsNotExist(err) {
75+
return "", err
76+
}
77+
yamlPath = ""
78+
}
79+
if jsonPath != "" && yamlPath != "" {
80+
return "", fmt.Errorf("found both %s and %s", jsonPath, yamlPath)
81+
}
82+
if jsonPath != "" {
83+
return jsonPath, nil
84+
}
85+
return yamlPath, nil
86+
}
87+
88+
type configVariant struct {
89+
Variant string `json:"coreos-assembler.config-variant"`
90+
}
91+
92+
func getVariant() (string, error) {
93+
contents, err := ioutil.ReadFile(initConfigPath)
94+
if err != nil {
95+
if !os.IsNotExist(err) {
96+
return "", err
97+
}
98+
contents = []byte{}
99+
}
100+
101+
var variantData configVariant
102+
if err := json.Unmarshal(contents, &variantData); err != nil {
103+
return "", fmt.Errorf("parsing %s: %w", initConfigPath, err)
104+
}
105+
106+
return variantData.Variant, nil
107+
}
108+
109+
func implRunBuildCImage(c *cobra.Command, args []string) error {
110+
if err := cmdrun.RunCmdSyncV("cosa", "build", "--prepare-only"); err != nil {
111+
return err
112+
}
113+
114+
csh, err := cosash.NewCosaSh()
115+
if err != nil {
116+
return err
117+
}
118+
119+
basearch, err := csh.BaseArch()
120+
if err != nil {
121+
return err
122+
}
123+
variant, err := getVariant()
124+
if err != nil {
125+
return err
126+
}
127+
manifest := defaultManifest
128+
if variant != "" {
129+
manifest = fmt.Sprintf("src/config/manifest-%s.yaml", variant)
130+
}
131+
132+
repository := args[0]
133+
134+
buildArgs := []string{"compose", "image", "--format", "registry", "--layer-repo", "tmp/repo"}
135+
if BuildCImageOpts.initialize {
136+
buildArgs = append(buildArgs, "--initialize")
137+
}
138+
if BuildCImageOpts.authfile != "" {
139+
buildArgs = append(buildArgs, "--authfile", BuildCImageOpts.authfile)
140+
}
141+
if _, err := os.Stat("tmp/cosa-transient"); err != nil {
142+
if !os.IsNotExist(err) {
143+
return err
144+
}
145+
cachedir := "cache/buildcimage-cache"
146+
if err := os.MkdirAll(cachedir, 0o755); err != nil {
147+
return err
148+
}
149+
buildArgs = append(buildArgs, "--cachedir", cachedir)
150+
}
151+
manifestLock, err := pickConfigFileYamlOrJson(fmt.Sprintf("manifest-lock.%s", basearch), true)
152+
if err != nil {
153+
return err
154+
}
155+
manifestLockOverrides, err := pickConfigFileYamlOrJson("manifest-lock.overrides", false)
156+
if err != nil {
157+
return err
158+
}
159+
manifestLockArchOverrides, err := pickConfigFileYamlOrJson(fmt.Sprintf("manifest-lock.overrides.%s", basearch), false)
160+
if err != nil {
161+
return err
162+
}
163+
for _, lock := range []string{manifestLock, manifestLockOverrides, manifestLockArchOverrides} {
164+
if lock != "" {
165+
buildArgs = append(buildArgs, "--lockfile", lock)
166+
}
167+
}
168+
buildArgs = append(buildArgs, manifest)
169+
buildArgs = append(buildArgs, repository)
170+
171+
argv0 := "rpm-ostree"
172+
priv, err := csh.HasPrivileges()
173+
if err != nil {
174+
return err
175+
}
176+
if priv {
177+
argv0 = "sudo"
178+
buildArgs = append([]string{"rpm-ostree"}, buildArgs...)
179+
} else {
180+
return fmt.Errorf("this command currently requires the ability to create nested containers")
181+
}
182+
183+
if err := cmdrun.RunCmdSyncV(argv0, buildArgs...); err != nil {
184+
return err
185+
}
186+
187+
return nil
188+
}

cmd/coreos-assembler.go

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func run(argv []string) error {
8888
switch cmd {
8989
case "clean":
9090
return runClean(argv)
91+
case "build-cimage":
92+
return runBuildCImage(argv)
9193
case "update-variant":
9294
return runUpdateVariant(argv)
9395
case "remote-session":

internal/pkg/cmdrun/cmdrun.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmdrun
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"strings"
8+
"syscall"
9+
)
10+
11+
// Synchronously invoke a command, logging the command arguments
12+
// to stdout.
13+
func RunCmdSyncV(cmdName string, args ...string) error {
14+
fmt.Printf("Running: %s %s\n", cmdName, strings.Join(args, " "))
15+
return RunCmdSync(cmdName, args...)
16+
}
17+
18+
// Synchronously invoke a command, passing both stdout and stderr.
19+
func RunCmdSync(cmdName string, args ...string) error {
20+
cmd := exec.Command(cmdName, args...)
21+
cmd.SysProcAttr = &syscall.SysProcAttr{
22+
Pdeathsig: syscall.SIGTERM,
23+
}
24+
cmd.Stdout = os.Stdout
25+
cmd.Stderr = os.Stderr
26+
if err := cmd.Run(); err != nil {
27+
return fmt.Errorf("error running %s %s: %w", cmdName, strings.Join(args, " "), err)
28+
}
29+
30+
return nil
31+
}

internal/pkg/cosash/cosash.go

+5
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ pwd >&3
173173
`)
174174
}
175175

176+
// BaseArch returns the base architecture
177+
func (sh *CosaSh) BaseArch() (string, error) {
178+
return sh.ProcessWithReply(`echo $basearch >&3`)
179+
}
180+
176181
// HasPrivileges checks if we can use sudo
177182
func (sh *CosaSh) HasPrivileges() (bool, error) {
178183
r, err := sh.ProcessWithReply(`

src/cmd-fetch

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fi
8585

8686
prepare_build
8787

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

0 commit comments

Comments
 (0)