podman
181 строка · 6.8 Кб
1package config
2
3import (
4"fmt"
5"os"
6"strings"
7
8"github.com/containers/buildah/docker"
9"github.com/containers/image/v5/manifest"
10v1 "github.com/opencontainers/image-spec/specs-go/v1"
11"github.com/openshift/imagebuilder"
12)
13
14// firstStringElseSecondString takes two strings, and returns the first
15// string if it isn't empty, else the second string
16func firstStringElseSecondString(first, second string) string {
17if first != "" {
18return first
19}
20return second
21}
22
23// firstSliceElseSecondSlice takes two string slices, and returns the first
24// slice of strings if it has contents, else the second slice
25func firstSliceElseSecondSlice(first, second []string) []string {
26if len(first) > 0 {
27return append([]string{}, first...)
28}
29return append([]string{}, second...)
30}
31
32// firstSlicePairElseSecondSlicePair takes two pairs of string slices, and
33// returns the first pair of slices if either has contents, else the second
34// pair
35func firstSlicePairElseSecondSlicePair(firstA, firstB, secondA, secondB []string) ([]string, []string) {
36if len(firstA) > 0 || len(firstB) > 0 {
37return append([]string{}, firstA...), append([]string{}, firstB...)
38}
39return append([]string{}, secondA...), append([]string{}, secondB...)
40}
41
42// mergeEnv combines variables from a and b into a single environment slice. if
43// a and b both provide values for the same variable, the value from b is
44// preferred
45func mergeEnv(a, b []string) []string {
46index := make(map[string]int)
47results := make([]string, 0, len(a)+len(b))
48for _, kv := range append(append([]string{}, a...), b...) {
49k, _, specifiesValue := strings.Cut(kv, "=")
50if !specifiesValue {
51if value, ok := os.LookupEnv(kv); ok {
52kv = kv + "=" + value
53} else {
54kv = kv + "="
55}
56}
57if i, seen := index[k]; seen {
58results[i] = kv
59} else {
60index[k] = len(results)
61results = append(results, kv)
62}
63}
64return results
65}
66
67// Override takes a buildah docker config and an OCI ImageConfig, and applies a
68// mixture of a slice of Dockerfile-style instructions and fields from a config
69// blob to them both
70func Override(dconfig *docker.Config, oconfig *v1.ImageConfig, overrideChanges []string, overrideConfig *manifest.Schema2Config) error {
71if len(overrideChanges) > 0 {
72if overrideConfig == nil {
73overrideConfig = &manifest.Schema2Config{}
74}
75// Parse the set of changes as we would a Dockerfile.
76changes := strings.Join(overrideChanges, "\n")
77parsed, err := imagebuilder.ParseDockerfile(strings.NewReader(changes))
78if err != nil {
79return fmt.Errorf("parsing change set %+v: %w", changes, err)
80}
81// Create a dummy builder object to process configuration-related
82// instructions.
83subBuilder := imagebuilder.NewBuilder(nil)
84// Convert the incoming data into an initial RunConfig.
85subBuilder.RunConfig = *GoDockerclientConfigFromSchema2Config(overrideConfig)
86// Process the change instructions one by one.
87for _, node := range parsed.Children {
88var step imagebuilder.Step
89if err := step.Resolve(node); err != nil {
90return fmt.Errorf("resolving change %q: %w", node.Original, err)
91}
92if err := subBuilder.Run(&step, &configOnlyExecutor{}, true); err != nil {
93return fmt.Errorf("processing change %q: %w", node.Original, err)
94}
95}
96// Pull settings out of the dummy builder's RunConfig.
97overrideConfig = Schema2ConfigFromGoDockerclientConfig(&subBuilder.RunConfig)
98}
99if overrideConfig != nil {
100// Apply changes from a possibly-provided possibly-changed config struct.
101dconfig.Hostname = firstStringElseSecondString(overrideConfig.Hostname, dconfig.Hostname)
102dconfig.Domainname = firstStringElseSecondString(overrideConfig.Domainname, dconfig.Domainname)
103dconfig.User = firstStringElseSecondString(overrideConfig.User, dconfig.User)
104oconfig.User = firstStringElseSecondString(overrideConfig.User, oconfig.User)
105dconfig.AttachStdin = overrideConfig.AttachStdin
106dconfig.AttachStdout = overrideConfig.AttachStdout
107dconfig.AttachStderr = overrideConfig.AttachStderr
108if len(overrideConfig.ExposedPorts) > 0 {
109dexposedPorts := make(map[docker.Port]struct{})
110oexposedPorts := make(map[string]struct{})
111for port := range dconfig.ExposedPorts {
112dexposedPorts[port] = struct{}{}
113}
114for port := range overrideConfig.ExposedPorts {
115dexposedPorts[docker.Port(port)] = struct{}{}
116}
117for port := range oconfig.ExposedPorts {
118oexposedPorts[port] = struct{}{}
119}
120for port := range overrideConfig.ExposedPorts {
121oexposedPorts[string(port)] = struct{}{}
122}
123dconfig.ExposedPorts = dexposedPorts
124oconfig.ExposedPorts = oexposedPorts
125}
126dconfig.Tty = overrideConfig.Tty
127dconfig.OpenStdin = overrideConfig.OpenStdin
128dconfig.StdinOnce = overrideConfig.StdinOnce
129if len(overrideConfig.Env) > 0 {
130dconfig.Env = mergeEnv(dconfig.Env, overrideConfig.Env)
131oconfig.Env = mergeEnv(oconfig.Env, overrideConfig.Env)
132}
133dconfig.Entrypoint, dconfig.Cmd = firstSlicePairElseSecondSlicePair(overrideConfig.Entrypoint, overrideConfig.Cmd, dconfig.Entrypoint, dconfig.Cmd)
134oconfig.Entrypoint, oconfig.Cmd = firstSlicePairElseSecondSlicePair(overrideConfig.Entrypoint, overrideConfig.Cmd, oconfig.Entrypoint, oconfig.Cmd)
135if overrideConfig.Healthcheck != nil {
136dconfig.Healthcheck = &docker.HealthConfig{
137Test: append([]string{}, overrideConfig.Healthcheck.Test...),
138Interval: overrideConfig.Healthcheck.Interval,
139Timeout: overrideConfig.Healthcheck.Timeout,
140StartPeriod: overrideConfig.Healthcheck.StartPeriod,
141Retries: overrideConfig.Healthcheck.Retries,
142}
143}
144dconfig.ArgsEscaped = overrideConfig.ArgsEscaped
145dconfig.Image = firstStringElseSecondString(overrideConfig.Image, dconfig.Image)
146if len(overrideConfig.Volumes) > 0 {
147if dconfig.Volumes == nil {
148dconfig.Volumes = make(map[string]struct{})
149}
150if oconfig.Volumes == nil {
151oconfig.Volumes = make(map[string]struct{})
152}
153for volume := range overrideConfig.Volumes {
154dconfig.Volumes[volume] = struct{}{}
155oconfig.Volumes[volume] = struct{}{}
156}
157}
158dconfig.WorkingDir = firstStringElseSecondString(overrideConfig.WorkingDir, dconfig.WorkingDir)
159oconfig.WorkingDir = firstStringElseSecondString(overrideConfig.WorkingDir, oconfig.WorkingDir)
160dconfig.NetworkDisabled = overrideConfig.NetworkDisabled
161dconfig.MacAddress = overrideConfig.MacAddress
162dconfig.OnBuild = overrideConfig.OnBuild
163if len(overrideConfig.Labels) > 0 {
164if dconfig.Labels == nil {
165dconfig.Labels = make(map[string]string)
166}
167if oconfig.Labels == nil {
168oconfig.Labels = make(map[string]string)
169}
170for k, v := range overrideConfig.Labels {
171dconfig.Labels[k] = v
172oconfig.Labels[k] = v
173}
174}
175dconfig.StopSignal = overrideConfig.StopSignal
176oconfig.StopSignal = overrideConfig.StopSignal
177dconfig.StopTimeout = overrideConfig.StopTimeout
178dconfig.Shell = firstSliceElseSecondSlice(overrideConfig.Shell, dconfig.Shell)
179}
180return nil
181}
182