Skip to content

Commit cbee168

Browse files
[wanda] refactor docker_cmd to be container_cmd
Refactoring common interface into container_cmd so we can use podman or whatever backends we like. Subsequent PR will add podman_cmd once the interface looks clean Topic: refactor-for-podman Signed-off-by: andrew <andrew@anyscale.com>
1 parent 7768028 commit cbee168

File tree

9 files changed

+775
-226
lines changed

9 files changed

+775
-226
lines changed

wanda/container_cmd.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package wanda
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"os"
9+
"os/exec"
10+
"sort"
11+
"strings"
12+
)
13+
14+
// ContainerCmd is the interface for building container images across
15+
// different container runtimes and builders.
16+
type ContainerCmd interface {
17+
// setWorkDir sets the working directory for commands.
18+
setWorkDir(dir string)
19+
20+
// run executes a command with the given arguments.
21+
run(args ...string) error
22+
23+
// pull pulls an image and optionally tags it.
24+
pull(src, asTag string) error
25+
26+
// inspectImage returns information about an image, or nil if not found.
27+
inspectImage(tag string) (*imageInfo, error)
28+
29+
// tag tags an image.
30+
tag(src, asTag string) error
31+
32+
// build builds an image from the given input.
33+
build(in *buildInput, core *buildInputCore, hints *buildInputHints) error
34+
}
35+
36+
// imageInfo contains information about a container image.
37+
type imageInfo struct {
38+
ID string `json:"Id"`
39+
RepoDigests []string
40+
RepoTags []string
41+
}
42+
43+
// baseContainerCmd provides common functionality for container commands.
44+
type baseContainerCmd struct {
45+
bin string
46+
workDir string
47+
envs []string
48+
}
49+
50+
func (c *baseContainerCmd) setWorkDir(dir string) { c.workDir = dir }
51+
52+
func (c *baseContainerCmd) cmd(args ...string) *exec.Cmd {
53+
cmd := exec.Command(c.bin, args...)
54+
cmd.Stdout = os.Stdout
55+
cmd.Stderr = os.Stderr
56+
cmd.Env = c.envs
57+
if c.workDir != "" {
58+
cmd.Dir = c.workDir
59+
}
60+
return cmd
61+
}
62+
63+
func (c *baseContainerCmd) run(args ...string) error {
64+
return c.cmd(args...).Run()
65+
}
66+
67+
func (c *baseContainerCmd) pull(src, asTag string) error {
68+
if err := c.run("pull", src); err != nil {
69+
return fmt.Errorf("pull %s: %w", src, err)
70+
}
71+
if src != asTag {
72+
if err := c.tag(src, asTag); err != nil {
73+
return fmt.Errorf("tag %s %s: %w", src, asTag, err)
74+
}
75+
}
76+
return nil
77+
}
78+
79+
func (c *baseContainerCmd) inspectImage(tag string) (*imageInfo, error) {
80+
cmd := c.cmd("image", "inspect", tag)
81+
buf := new(bytes.Buffer)
82+
cmd.Stdout = buf
83+
if err := cmd.Run(); err != nil {
84+
if exitErr, ok := err.(*exec.ExitError); ok {
85+
// Docker returns 1
86+
code := exitErr.ExitCode()
87+
if code == 1 {
88+
return nil, nil
89+
}
90+
}
91+
return nil, err
92+
}
93+
var info []*imageInfo
94+
if err := json.Unmarshal(buf.Bytes(), &info); err != nil {
95+
return nil, fmt.Errorf("unmarshal image info: %w", err)
96+
}
97+
if len(info) != 1 {
98+
return nil, fmt.Errorf("%d image(s) found, expect 1", len(info))
99+
}
100+
return info[0], nil
101+
}
102+
103+
func (c *baseContainerCmd) tag(src, asTag string) error {
104+
return c.run("tag", src, asTag)
105+
}
106+
107+
func (c *baseContainerCmd) build(in *buildInput, core *buildInputCore, hints *buildInputHints) error {
108+
if hints == nil {
109+
hints = newBuildInputHints(nil)
110+
}
111+
112+
// Pull down the required images, and tag them properly.
113+
var froms []string
114+
for from := range core.Froms {
115+
froms = append(froms, from)
116+
}
117+
sort.Strings(froms)
118+
119+
for _, from := range froms {
120+
src, ok := in.froms[from]
121+
if !ok {
122+
return fmt.Errorf("missing base image source for %q", from)
123+
}
124+
if src.local != "" { // local image, already ready.
125+
continue
126+
}
127+
if err := c.pull(src.src, src.name); err != nil {
128+
return fmt.Errorf("pull %s(%s): %w", src.name, src.src, err)
129+
}
130+
}
131+
132+
// Build the image.
133+
var args []string
134+
args = append(args, "build")
135+
args = append(args, "-f", core.Dockerfile)
136+
137+
for _, t := range in.tagList() {
138+
args = append(args, "-t", t)
139+
}
140+
141+
buildArgs := make(map[string]string)
142+
for k, v := range hints.BuildArgs {
143+
buildArgs[k] = v
144+
}
145+
// non-hint args can overwrite hint args
146+
for k, v := range core.BuildArgs {
147+
buildArgs[k] = v
148+
}
149+
150+
var buildArgKeys []string
151+
for k := range buildArgs {
152+
buildArgKeys = append(buildArgKeys, k)
153+
}
154+
sort.Strings(buildArgKeys)
155+
for _, k := range buildArgKeys {
156+
v := buildArgs[k]
157+
args = append(args, "--build-arg", fmt.Sprintf("%s=%s", k, v))
158+
}
159+
160+
// read context from stdin
161+
args = append(args, "-")
162+
163+
log.Printf("%s %s", c.bin, strings.Join(args, " "))
164+
165+
buildCmd := c.cmd(args...)
166+
if in.context != nil {
167+
buildCmd.Stdin = newWriterToReader(in.context)
168+
}
169+
170+
return buildCmd.Run()
171+
}

0 commit comments

Comments
 (0)