istio
316 строк · 10.0 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18"context"
19"fmt"
20"os"
21"os/exec"
22"path/filepath"
23"runtime"
24"strings"
25"time"
26
27"github.com/spf13/cobra"
28"sigs.k8s.io/yaml"
29
30"istio.io/istio/pkg/log"
31testenv "istio.io/istio/pkg/test/env"
32"istio.io/istio/pkg/tracing"
33"istio.io/istio/pkg/util/sets"
34pkgversion "istio.io/istio/pkg/version"
35)
36
37func main() {
38rootCmd.Flags().StringSliceVar(&globalArgs.Hubs, "hub", globalArgs.Hubs, "docker hub(s)")
39rootCmd.Flags().StringSliceVar(&globalArgs.Tags, "tag", globalArgs.Tags, "docker tag(s)")
40
41rootCmd.Flags().StringVar(&globalArgs.BaseVersion, "base-version", globalArgs.BaseVersion, "base version to use")
42rootCmd.Flags().StringVar(&globalArgs.BaseImageRegistry, "image-base-registry", globalArgs.BaseImageRegistry, "base image registry to use")
43rootCmd.Flags().StringVar(&globalArgs.ProxyVersion, "proxy-version", globalArgs.ProxyVersion, "proxy version to use")
44rootCmd.Flags().StringVar(&globalArgs.ZtunnelVersion, "ztunnel-version", globalArgs.ZtunnelVersion, "ztunnel version to use")
45rootCmd.Flags().StringVar(&globalArgs.IstioVersion, "istio-version", globalArgs.IstioVersion, "istio version to use")
46
47rootCmd.Flags().StringSliceVar(&globalArgs.Targets, "targets", globalArgs.Targets, "targets to build")
48rootCmd.Flags().StringSliceVar(&globalArgs.Variants, "variants", globalArgs.Variants, "variants to build")
49rootCmd.Flags().StringSliceVar(&globalArgs.Architectures, "architectures", globalArgs.Architectures, "architectures to build")
50rootCmd.Flags().BoolVar(&globalArgs.Push, "push", globalArgs.Push, "push targets to registry")
51rootCmd.Flags().BoolVar(&globalArgs.Save, "save", globalArgs.Save, "save targets to tar.gz")
52rootCmd.Flags().BoolVar(&globalArgs.NoCache, "no-cache", globalArgs.NoCache, "disable caching")
53rootCmd.Flags().BoolVar(&globalArgs.NoClobber, "no-clobber", globalArgs.NoClobber, "do not allow pushing images that already exist")
54rootCmd.Flags().StringVar(&globalArgs.Builder, "builder", globalArgs.Builder, "type of builder to use. options are crane or docker")
55rootCmd.Flags().BoolVar(&version, "version", version, "show build version")
56
57rootCmd.Flags().BoolVar(&globalArgs.SupportsEmulation, "qemu", globalArgs.SupportsEmulation, "if enable, allows building images that require emulation")
58
59if err := rootCmd.Execute(); err != nil {
60os.Exit(-1)
61}
62}
63
64var privilegedHubs = sets.New[string](
65"docker.io/istio",
66"istio",
67"gcr.io/istio-release",
68"gcr.io/istio-testing",
69)
70
71var rootCmd = &cobra.Command{
72SilenceUsage: true,
73Short: "Builds Istio docker images",
74RunE: func(cmd *cobra.Command, _ []string) error {
75t0 := time.Now()
76defer func() {
77log.WithLabels("runtime", time.Since(t0)).Infof("build complete")
78}()
79ctx, shutdown, err := tracing.InitializeFullBinary("docker-builder")
80if err != nil {
81return err
82}
83defer shutdown()
84if version {
85fmt.Println(pkgversion.Info.GitRevision)
86os.Exit(0)
87}
88log.Infof("Args: %s", globalArgs)
89if err := ValidateArgs(globalArgs); err != nil {
90return err
91}
92
93args, err := ReadPlan(ctx, globalArgs)
94if err != nil {
95return fmt.Errorf("plan: %v", err)
96}
97
98// The Istio image builder has two building modes - one utilizing docker, and one manually constructing
99// images using the go-containerregistry (crane) libraries.
100// The crane builder is much faster but less tested.
101// Neither builder is doing standard logic; see each builder for details.
102if args.Builder == CraneBuilder {
103return RunCrane(ctx, args)
104}
105
106return RunDocker(args)
107},
108}
109
110func ValidateArgs(a Args) error {
111if len(a.Targets) == 0 {
112return fmt.Errorf("no targets specified")
113}
114if a.Push && a.Save {
115// TODO(https://github.com/moby/buildkit/issues/1555) support both
116return fmt.Errorf("--push and --save are mutually exclusive")
117}
118_, inCI := os.LookupEnv("CI")
119if a.Push && len(privilegedHubs.Intersection(sets.New(a.Hubs...))) > 0 && !inCI {
120// Safety check against developer error. If they have a legitimate use case, they can set CI var
121return fmt.Errorf("pushing to official registry only supported in CI")
122}
123if !sets.New(DockerBuilder, CraneBuilder).Contains(a.Builder) {
124return fmt.Errorf("unknown builder %v", a.Builder)
125}
126
127if a.Builder == CraneBuilder && a.Save {
128return fmt.Errorf("crane builder does not support save")
129}
130if a.Builder == CraneBuilder && a.NoClobber {
131return fmt.Errorf("crane builder does not support no-clobber")
132}
133if a.Builder == CraneBuilder && a.NoCache {
134return fmt.Errorf("crane builder does not support no-cache")
135}
136if a.Builder == CraneBuilder && !a.Push {
137return fmt.Errorf("crane builder only supports pushing")
138}
139return nil
140}
141
142func ReadPlanTargets() ([]string, []string, error) {
143by, err := os.ReadFile(filepath.Join(testenv.IstioSrc, "tools", "docker.yaml"))
144if err != nil {
145return nil, nil, err
146}
147plan := BuildPlan{}
148if err := yaml.Unmarshal(by, &plan); err != nil {
149return nil, nil, err
150}
151bases := sets.New[string]()
152nonBases := sets.New[string]()
153for _, i := range plan.Images {
154if i.Base {
155bases.Insert(i.Name)
156} else {
157nonBases.Insert(i.Name)
158}
159}
160return sets.SortedList(bases), sets.SortedList(nonBases), nil
161}
162
163var LocalArch = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
164
165func ReadPlan(ctx context.Context, a Args) (Args, error) {
166_, span := tracing.Start(ctx, "ReadPlan")
167defer span.End()
168by, err := os.ReadFile(filepath.Join(testenv.IstioSrc, "tools", "docker.yaml"))
169if err != nil {
170return a, err
171}
172a.Plan = map[string]BuildPlan{}
173for _, arch := range a.Architectures {
174plan := BuildPlan{}
175
176// We allow variables in the plan
177input := os.Expand(string(by), func(s string) string {
178data := archToEnvMap(arch)
179data["SIDECAR"] = "envoy"
180if _, f := os.LookupEnv("DEBUG_IMAGE"); f {
181data["RELEASE_MODE"] = "debug"
182} else {
183data["RELEASE_MODE"] = "release"
184}
185if r, f := data[s]; f {
186return r
187}
188
189// Fallback to env
190return os.Getenv(s)
191})
192if err := yaml.Unmarshal([]byte(input), &plan); err != nil {
193return a, err
194}
195
196// Check targets are valid
197tgt := sets.New(a.Targets...)
198known := sets.New[string]()
199for _, img := range plan.Images {
200known.Insert(img.Name)
201}
202if unknown := sets.SortedList(tgt.Difference(known)); len(unknown) > 0 {
203return a, fmt.Errorf("unknown targets: %v", unknown)
204}
205
206// Filter down to requested targets
207// This is not arch specific, so we can just let it run for each arch.
208desiredImages := []ImagePlan{}
209for _, i := range plan.Images {
210canBuild := !i.EmulationRequired || (arch == LocalArch)
211if tgt.Contains(i.Name) {
212if !canBuild {
213log.Infof("Skipping %s for %s as --qemu is not passed", i.Name, arch)
214continue
215}
216desiredImages = append(desiredImages, i)
217}
218}
219plan.Images = desiredImages
220
221a.Plan[arch] = plan
222}
223return a, nil
224}
225
226// VerboseCommand runs a command, outputting stderr and stdout
227func VerboseCommand(name string, arg ...string) *exec.Cmd {
228log.Infof("Running command: %v %v", name, strings.Join(arg, " "))
229cmd := exec.Command(name, arg...)
230cmd.Stderr = os.Stderr
231cmd.Stdout = os.Stdout
232return cmd
233}
234
235func StandardEnv(args Args) []string {
236env := os.Environ()
237if len(sets.New(args.Targets...).Delete("proxyv2")) <= 1 {
238// If we are building multiple, it is faster to build all binaries in a single invocation
239// Otherwise, build just the single item. proxyv2 is special since it is always built separately with tag=agent.
240// Ideally we would just always build the targets we need but our Makefile is not that smart
241env = append(env, "BUILD_ALL=false")
242}
243
244env = append(env,
245// Build should already run in container, having multiple layers of docker causes issues
246"BUILD_WITH_CONTAINER=0",
247)
248return env
249}
250
251var SkipMake = os.Getenv("SKIP_MAKE")
252
253// RunMake runs a make command for the repo, with standard environment variables set
254func RunMake(ctx context.Context, args Args, arch string, c ...string) error {
255_, span := tracing.Start(ctx, "RunMake")
256defer span.End()
257if len(c) == 0 {
258log.Infof("nothing to make")
259return nil
260}
261if SkipMake == "true" {
262return nil
263}
264shortArgs := []string{}
265// Shorten output to avoid a ton of long redundant paths
266for _, cs := range c {
267shortArgs = append(shortArgs, filepath.Base(cs))
268}
269if len(c) == 0 {
270log.Infof("Nothing to make")
271return nil
272}
273log.Infof("Running make for %v: %v", arch, strings.Join(shortArgs, " "))
274env := StandardEnv(args)
275env = append(env, archToGoFlags(arch)...)
276makeArgs := []string{"--no-print-directory"}
277makeArgs = append(makeArgs, c...)
278cmd := exec.Command("make", makeArgs...)
279log.Infof("env: %v", archToGoFlags(arch))
280cmd.Env = env
281cmd.Stderr = os.Stderr
282cmd.Stdout = os.Stdout
283cmd.Dir = testenv.IstioSrc
284if err := cmd.Run(); err != nil {
285return err
286}
287return nil
288}
289
290func archToGoFlags(a string) []string {
291s := []string{}
292for k, v := range archToEnvMap(a) {
293s = append(s, k+"="+v)
294}
295return s
296}
297
298func archToEnvMap(a string) map[string]string {
299os, arch, _ := strings.Cut(a, "/")
300return map[string]string{
301"TARGET_OS": os,
302"TARGET_ARCH": arch,
303"TARGET_OUT": filepath.Join(testenv.IstioSrc, "out", fmt.Sprintf("%s_%s", os, arch)),
304"TARGET_OUT_LINUX": filepath.Join(testenv.IstioSrc, "out", fmt.Sprintf("linux_%s", arch)),
305}
306}
307
308// RunCommand runs a command for the repo, with standard environment variables set
309func RunCommand(args Args, c string, cargs ...string) error {
310cmd := VerboseCommand(c, cargs...)
311cmd.Env = StandardEnv(args)
312cmd.Stderr = os.Stderr
313cmd.Stdout = os.Stdout
314cmd.Dir = testenv.IstioSrc
315return cmd.Run()
316}
317