istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/docker-builder/types.go (about)

     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  
    15  package main
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"istio.io/istio/pkg/env"
    26  	"istio.io/istio/pkg/log"
    27  	testenv "istio.io/istio/pkg/test/env"
    28  	"istio.io/istio/pkg/util/sets"
    29  )
    30  
    31  // Types mirrored from https://github.com/docker/buildx/blob/master/bake/bake.go
    32  type Group struct {
    33  	Targets []string `json:"targets" hcl:"targets"`
    34  }
    35  
    36  type BakeFile struct {
    37  	Target map[string]Target `json:"target,omitempty"`
    38  	Group  map[string]Group  `json:"group,omitempty"`
    39  }
    40  
    41  type Target struct {
    42  	Context          *string           `json:"context,omitempty" hcl:"context,optional"`
    43  	Dockerfile       *string           `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
    44  	DockerfileInline *string           `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional"`
    45  	Args             map[string]string `json:"args,omitempty" hcl:"args,optional"`
    46  	Labels           map[string]string `json:"labels,omitempty" hcl:"labels,optional"`
    47  	Tags             []string          `json:"tags,omitempty" hcl:"tags,optional"`
    48  	CacheFrom        []string          `json:"cache-from,omitempty"  hcl:"cache-from,optional"`
    49  	CacheTo          []string          `json:"cache-to,omitempty"  hcl:"cache-to,optional"`
    50  	Target           *string           `json:"target,omitempty" hcl:"target,optional"`
    51  	Secrets          []string          `json:"secret,omitempty" hcl:"secret,optional"`
    52  	SSH              []string          `json:"ssh,omitempty" hcl:"ssh,optional"`
    53  	Platforms        []string          `json:"platforms,omitempty" hcl:"platforms,optional"`
    54  	Outputs          []string          `json:"output,omitempty" hcl:"output,optional"`
    55  	Pull             *bool             `json:"pull,omitempty" hcl:"pull,optional"`
    56  	NoCache          *bool             `json:"no-cache,omitempty" hcl:"no-cache,optional"`
    57  }
    58  
    59  type Args struct {
    60  	Push              bool
    61  	Save              bool
    62  	Builder           string
    63  	SupportsEmulation bool
    64  	NoClobber         bool
    65  	NoCache           bool
    66  	Targets           []string
    67  	Variants          []string
    68  	Architectures     []string
    69  	BaseVersion       string
    70  	BaseImageRegistry string
    71  	ProxyVersion      string
    72  	ZtunnelVersion    string
    73  	IstioVersion      string
    74  	Tags              []string
    75  	Hubs              []string
    76  	// Suffix on artifacts, used for multi-arch images where we cannot use manifests
    77  	suffix string
    78  
    79  	// Plan describes the build plan, read from file.
    80  	// This is a map of architecture -> plan, as the plan is arch specific.
    81  	Plan map[string]BuildPlan
    82  }
    83  
    84  func (a Args) PlanFor(arch string) BuildPlan {
    85  	return a.Plan[arch]
    86  }
    87  
    88  func (a Args) String() string {
    89  	var b strings.Builder
    90  	b.WriteString("Push:              " + fmt.Sprint(a.Push) + "\n")
    91  	b.WriteString("Save:              " + fmt.Sprint(a.Save) + "\n")
    92  	b.WriteString("NoClobber:         " + fmt.Sprint(a.NoClobber) + "\n")
    93  	b.WriteString("NoCache:           " + fmt.Sprint(a.NoCache) + "\n")
    94  	b.WriteString("Targets:           " + fmt.Sprint(a.Targets) + "\n")
    95  	b.WriteString("Variants:          " + fmt.Sprint(a.Variants) + "\n")
    96  	b.WriteString("Architectures:     " + fmt.Sprint(a.Architectures) + "\n")
    97  	b.WriteString("BaseVersion:       " + fmt.Sprint(a.BaseVersion) + "\n")
    98  	b.WriteString("BaseImageRegistry: " + fmt.Sprint(a.BaseImageRegistry) + "\n")
    99  	b.WriteString("ProxyVersion:      " + fmt.Sprint(a.ProxyVersion) + "\n")
   100  	b.WriteString("ZtunnelVersion:    " + fmt.Sprint(a.ZtunnelVersion) + "\n")
   101  	b.WriteString("IstioVersion:      " + fmt.Sprint(a.IstioVersion) + "\n")
   102  	b.WriteString("Tags:              " + fmt.Sprint(a.Tags) + "\n")
   103  	b.WriteString("Hubs:              " + fmt.Sprint(a.Hubs) + "\n")
   104  	b.WriteString("Builder:           " + fmt.Sprint(a.Builder) + "\n")
   105  	return b.String()
   106  }
   107  
   108  type ImagePlan struct {
   109  	// Name of the image. For example, "pilot"
   110  	Name string `json:"name"`
   111  	// Dockerfile path to build from
   112  	Dockerfile string `json:"dockerfile"`
   113  	// Files list files that are copied as-is into the image
   114  	Files []string `json:"files"`
   115  	// Targets list make targets that are ran and then copied into the image
   116  	Targets []string `json:"targets"`
   117  	// Base indicates if this is a base image or not
   118  	Base bool `json:"base"`
   119  	// EmulationRequired indicates if emulation is required when cross-compiling. It typically is not,
   120  	// as most building in Istio is done outside of docker.
   121  	// When this is set, cross-compile is disabled for components unless emulation is epxlicitly specified
   122  	EmulationRequired bool `json:"emulationRequired"`
   123  }
   124  
   125  func (p ImagePlan) Dependencies() []string {
   126  	v := []string{p.Dockerfile}
   127  	v = append(v, p.Files...)
   128  	v = append(v, p.Targets...)
   129  	return v
   130  }
   131  
   132  type BuildPlan struct {
   133  	Images []ImagePlan `json:"images"`
   134  }
   135  
   136  func (p BuildPlan) Targets() []string {
   137  	tgts := sets.New("init") // All targets depend on init
   138  	for _, img := range p.Images {
   139  		tgts.InsertAll(img.Targets...)
   140  	}
   141  	return sets.SortedList(tgts)
   142  }
   143  
   144  func (p BuildPlan) Find(n string) *ImagePlan {
   145  	for _, i := range p.Images {
   146  		if i.Name == n {
   147  			return &i
   148  		}
   149  	}
   150  	return nil
   151  }
   152  
   153  // Define variants, which control the base image of an image.
   154  // Tags will have the variant append (like 1.0-distroless).
   155  // The DefaultVariant is a special variant that has no explicit tag (like 1.0); it
   156  // is not a unique variant though. Currently, it represents DebugVariant.
   157  // If both DebugVariant and DefaultVariant are built, there will be a single build but multiple tags
   158  const (
   159  	// PrimaryVariant is the variant that DefaultVariant actually builds
   160  	PrimaryVariant = DebugVariant
   161  
   162  	DefaultVariant    = "default"
   163  	DebugVariant      = "debug"
   164  	DistrolessVariant = "distroless"
   165  )
   166  
   167  const (
   168  	CraneBuilder  = "crane"
   169  	DockerBuilder = "docker"
   170  )
   171  
   172  func DefaultArgs() Args {
   173  	// By default, we build all targets
   174  	var targets []string
   175  	_, nonBaseImages, err := ReadPlanTargets()
   176  	if err == nil {
   177  		targets = nonBaseImages
   178  	}
   179  	if legacy, f := os.LookupEnv("DOCKER_TARGETS"); f {
   180  		// Allow env var config. It is a string separated list like "docker.pilot docker.proxy"
   181  		targets = []string{}
   182  		for _, v := range strings.Split(legacy, " ") {
   183  			if v == "" {
   184  				continue
   185  			}
   186  			targets = append(targets, strings.TrimPrefix(v, "docker."))
   187  		}
   188  	}
   189  	pv, err := testenv.ReadDepsSHA("PROXY_REPO_SHA")
   190  	if err != nil {
   191  		log.Warnf("failed to read proxy sha")
   192  		pv = "unknown"
   193  	}
   194  	zv, err := testenv.ReadDepsSHA("ZTUNNEL_REPO_SHA")
   195  	if err != nil {
   196  		log.Warnf("failed to read ztunnel sha")
   197  		zv = "unknown"
   198  	}
   199  	variants := []string{DefaultVariant}
   200  	if legacy, f := os.LookupEnv("DOCKER_BUILD_VARIANTS"); f {
   201  		variants = strings.Split(legacy, " ")
   202  	}
   203  
   204  	if os.Getenv("INCLUDE_UNTAGGED_DEFAULT") == "true" {
   205  		// This legacy env var was to workaround the old build logic not being very smart
   206  		// In the new builder, we automagically detect this. So just insert the 'default' variant
   207  		cur := sets.New(variants...)
   208  		cur.Insert(DefaultVariant)
   209  		variants = sets.SortedList(cur)
   210  	}
   211  
   212  	arch := []string{"linux/amd64"}
   213  	if legacy, f := os.LookupEnv("DOCKER_ARCHITECTURES"); f {
   214  		arch = strings.Split(legacy, ",")
   215  	}
   216  
   217  	hub := []string{env.Register("HUB", "localhost:5000", "").Get()}
   218  	if hubs, f := os.LookupEnv("HUBS"); f {
   219  		hub = strings.Split(hubs, " ")
   220  	}
   221  	tag := []string{env.Register("TAG", "latest", "").Get()}
   222  	if tags, f := os.LookupEnv("TAGS"); f {
   223  		tag = strings.Split(tags, " ")
   224  	}
   225  
   226  	builder := DockerBuilder
   227  	if b, f := os.LookupEnv("ISTIO_DOCKER_BUILDER"); f {
   228  		builder = b
   229  	}
   230  
   231  	// TODO: consider automatically detecting Qemu support
   232  	qemu := false
   233  	if b, f := os.LookupEnv("ISTIO_DOCKER_QEMU"); f {
   234  		q, err := strconv.ParseBool(b)
   235  		if err == nil {
   236  			qemu = q
   237  		}
   238  	}
   239  
   240  	return Args{
   241  		Push:              false,
   242  		Save:              false,
   243  		NoCache:           false,
   244  		Hubs:              hub,
   245  		Tags:              tag,
   246  		BaseVersion:       fetchBaseVersion(),
   247  		BaseImageRegistry: fetchIstioBaseReg(),
   248  		IstioVersion:      fetchIstioVersion(),
   249  		ProxyVersion:      pv,
   250  		ZtunnelVersion:    zv,
   251  		Architectures:     arch,
   252  		Targets:           targets,
   253  		Variants:          variants,
   254  		Builder:           builder,
   255  		SupportsEmulation: qemu,
   256  	}
   257  }
   258  
   259  var (
   260  	globalArgs = DefaultArgs()
   261  	version    = false
   262  )
   263  
   264  var baseVersionRegexp = regexp.MustCompile(`BASE_VERSION \?= (.*)`)
   265  
   266  func fetchBaseVersion() string {
   267  	if b, f := os.LookupEnv("BASE_VERSION"); f {
   268  		return b
   269  	}
   270  	b, err := os.ReadFile(filepath.Join(testenv.IstioSrc, "Makefile.core.mk"))
   271  	if err != nil {
   272  		log.Fatalf("failed to read file: %v", err)
   273  		return "unknown"
   274  	}
   275  	match := baseVersionRegexp.FindSubmatch(b)
   276  	if len(match) < 2 {
   277  		log.Fatalf("failed to find match")
   278  		return "unknown"
   279  	}
   280  	return string(match[1])
   281  }
   282  
   283  var istioVersionRegexp = regexp.MustCompile(`VERSION \?= (.*)`)
   284  
   285  func fetchIstioVersion() string {
   286  	if b, f := os.LookupEnv("VERSION"); f {
   287  		return b
   288  	}
   289  	b, err := os.ReadFile(filepath.Join(testenv.IstioSrc, "Makefile.core.mk"))
   290  	if err != nil {
   291  		log.Fatalf("failed to read file: %v", err)
   292  		return "unknown"
   293  	}
   294  	match := istioVersionRegexp.FindSubmatch(b)
   295  	if len(match) < 2 {
   296  		log.Fatalf("failed to find match")
   297  		return "unknown"
   298  	}
   299  	return string(match[1])
   300  }
   301  
   302  var istioBaseRegRegexp = regexp.MustCompile(`ISTIO_BASE_REGISTRY \?= (.*)`)
   303  
   304  func fetchIstioBaseReg() string {
   305  	if b, f := os.LookupEnv("ISTIO_BASE_REGISTRY"); f {
   306  		return b
   307  	}
   308  	b, err := os.ReadFile(filepath.Join(testenv.IstioSrc, "Makefile.core.mk"))
   309  	if err != nil {
   310  		log.Fatalf("failed to read file: %v", err)
   311  		return "unknown"
   312  	}
   313  	match := istioBaseRegRegexp.FindSubmatch(b)
   314  	if len(match) < 2 {
   315  		log.Fatalf("failed to find match")
   316  		return "unknown"
   317  	}
   318  	return string(match[1])
   319  }