github.com/grahambrereton-form3/tilt@v0.10.18/pkg/model/image_target.go (about)

     1  package model
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"reflect"
     7  	"sort"
     8  
     9  	"github.com/docker/distribution/reference"
    10  
    11  	"github.com/windmilleng/tilt/internal/container"
    12  	"github.com/windmilleng/tilt/internal/sliceutils"
    13  )
    14  
    15  type ImageTarget struct {
    16  	ConfigurationRef container.RefSelector
    17  	DeploymentRef    reference.Named
    18  	BuildDetails     BuildDetails
    19  	MatchInEnvVars   bool
    20  
    21  	// User-supplied command to run when the container runs
    22  	// (i.e. overrides k8s yaml "command", container ENTRYPOINT, etc.)
    23  	OverrideCmd Cmd
    24  
    25  	cachePaths []string
    26  
    27  	// TODO(nick): It might eventually make sense to represent
    28  	// Tiltfile as a separate nodes in the build graph, rather
    29  	// than duplicating it in each ImageTarget.
    30  	tiltFilename  string
    31  	dockerignores []Dockerignore
    32  	repos         []LocalGitRepo
    33  	dependencyIDs []TargetID
    34  }
    35  
    36  func NewImageTarget(ref container.RefSelector) ImageTarget {
    37  	return ImageTarget{ConfigurationRef: ref, DeploymentRef: ref.AsNamedOnly()}
    38  }
    39  
    40  func ImageID(ref container.RefSelector) TargetID {
    41  	name := TargetName("")
    42  	if !ref.Empty() {
    43  		name = TargetName(ref.String())
    44  	}
    45  	return TargetID{
    46  		Type: TargetTypeImage,
    47  		Name: name,
    48  	}
    49  }
    50  
    51  func (i ImageTarget) ID() TargetID {
    52  	return ImageID(i.ConfigurationRef)
    53  }
    54  
    55  func (i ImageTarget) DependencyIDs() []TargetID {
    56  	return i.dependencyIDs
    57  }
    58  
    59  func (i ImageTarget) WithDependencyIDs(ids []TargetID) ImageTarget {
    60  	i.dependencyIDs = DedupeTargetIDs(ids)
    61  	return i
    62  }
    63  
    64  func (i ImageTarget) Validate() error {
    65  	if i.ConfigurationRef.Empty() {
    66  		return fmt.Errorf("[Validate] Image target missing image ref: %+v", i.BuildDetails)
    67  	}
    68  
    69  	switch bd := i.BuildDetails.(type) {
    70  	case DockerBuild:
    71  		if bd.BuildPath == "" {
    72  			return fmt.Errorf("[Validate] Image %q missing build path", i.ConfigurationRef)
    73  		}
    74  	case FastBuild:
    75  		if bd.BaseDockerfile == "" {
    76  			return fmt.Errorf("[Validate] Image %q missing base dockerfile", i.ConfigurationRef)
    77  		}
    78  
    79  		for _, mnt := range bd.Syncs {
    80  			if !filepath.IsAbs(mnt.LocalPath) {
    81  				return fmt.Errorf(
    82  					"[Validate] Image %q: mount must be an absolute path (got: %s)", i.ConfigurationRef, mnt.LocalPath)
    83  			}
    84  		}
    85  	case CustomBuild:
    86  		if bd.Command == "" {
    87  			return fmt.Errorf(
    88  				"[Validate] CustomBuild command must not be empty",
    89  			)
    90  		}
    91  	default:
    92  		return fmt.Errorf("[Validate] Image %q has neither DockerBuildInfo nor FastBuildInfo", i.ConfigurationRef)
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  type BuildDetails interface {
    99  	buildDetails()
   100  }
   101  
   102  func (i ImageTarget) DockerBuildInfo() DockerBuild {
   103  	ret, _ := i.BuildDetails.(DockerBuild)
   104  	return ret
   105  }
   106  
   107  func (i ImageTarget) IsDockerBuild() bool {
   108  	_, ok := i.BuildDetails.(DockerBuild)
   109  	return ok
   110  }
   111  
   112  func (i ImageTarget) AnyLiveUpdateInfo() LiveUpdate {
   113  	switch details := i.BuildDetails.(type) {
   114  	case DockerBuild:
   115  		return details.LiveUpdate
   116  	case CustomBuild:
   117  		return details.LiveUpdate
   118  	default:
   119  		return LiveUpdate{}
   120  	}
   121  }
   122  
   123  func (i ImageTarget) AnyFastBuildInfo() FastBuild {
   124  	switch details := i.BuildDetails.(type) {
   125  	case FastBuild:
   126  		return details
   127  	case DockerBuild:
   128  		return details.FastBuild
   129  	case CustomBuild:
   130  		return details.Fast
   131  	}
   132  	return FastBuild{}
   133  }
   134  
   135  // FastBuildInfo returns the TOP LEVEL BUILD DETAILS, if a FastBuild.
   136  // Does not return nested FastBuild details.
   137  func (i ImageTarget) TopFastBuildInfo() FastBuild {
   138  	ret, _ := i.BuildDetails.(FastBuild)
   139  	return ret
   140  }
   141  
   142  // IsFastBuild checks if the TOP LEVEL BUILD DETAILS are for a FastBuild.
   143  // (If this target is a DockerBuild || CustomBuild with a nested FastBuild, returns FALSE.)
   144  func (i ImageTarget) IsFastBuild() bool {
   145  	_, ok := i.BuildDetails.(FastBuild)
   146  	return ok
   147  }
   148  
   149  func (i ImageTarget) CustomBuildInfo() CustomBuild {
   150  	ret, _ := i.BuildDetails.(CustomBuild)
   151  	return ret
   152  }
   153  
   154  func (i ImageTarget) IsCustomBuild() bool {
   155  	_, ok := i.BuildDetails.(CustomBuild)
   156  	return ok
   157  }
   158  
   159  func (i ImageTarget) WithBuildDetails(details BuildDetails) ImageTarget {
   160  	i.BuildDetails = details
   161  	return i
   162  }
   163  
   164  func (i ImageTarget) WithCachePaths(paths []string) ImageTarget {
   165  	i.cachePaths = append(append([]string{}, i.cachePaths...), paths...)
   166  	sort.Strings(i.cachePaths)
   167  	return i
   168  }
   169  
   170  func (i ImageTarget) CachePaths() []string {
   171  	return i.cachePaths
   172  }
   173  
   174  func (i ImageTarget) WithRepos(repos []LocalGitRepo) ImageTarget {
   175  	i.repos = append(append([]LocalGitRepo{}, i.repos...), repos...)
   176  	return i
   177  }
   178  
   179  func (i ImageTarget) WithDockerignores(dockerignores []Dockerignore) ImageTarget {
   180  	i.dockerignores = append(append([]Dockerignore{}, i.dockerignores...), dockerignores...)
   181  	return i
   182  }
   183  
   184  func (i ImageTarget) WithOverrideCommand(cmd Cmd) ImageTarget {
   185  	i.OverrideCmd = cmd
   186  	return i
   187  }
   188  
   189  func (i ImageTarget) Dockerignores() []Dockerignore {
   190  	return append([]Dockerignore{}, i.dockerignores...)
   191  }
   192  
   193  func (i ImageTarget) LocalPaths() []string {
   194  	switch bd := i.BuildDetails.(type) {
   195  	case DockerBuild:
   196  		return []string{bd.BuildPath}
   197  	case FastBuild:
   198  		result := make([]string, len(bd.Syncs))
   199  		for i, mount := range bd.Syncs {
   200  			result[i] = mount.LocalPath
   201  		}
   202  		return result
   203  	case CustomBuild:
   204  		return append([]string(nil), bd.Deps...)
   205  	}
   206  	return nil
   207  }
   208  
   209  func (i ImageTarget) LocalRepos() []LocalGitRepo {
   210  	return i.repos
   211  }
   212  
   213  func (i ImageTarget) IgnoredLocalDirectories() []string {
   214  	return nil
   215  }
   216  
   217  func (i ImageTarget) TiltFilename() string {
   218  	return i.tiltFilename
   219  }
   220  
   221  func (i ImageTarget) WithTiltFilename(f string) ImageTarget {
   222  	i.tiltFilename = f
   223  	return i
   224  }
   225  
   226  // TODO(nick): This method should be deleted. We should just de-dupe and sort LocalPaths once
   227  // when we create it, rather than have a duplicate method that does the "right" thing.
   228  func (i ImageTarget) Dependencies() []string {
   229  	return sliceutils.DedupedAndSorted(i.LocalPaths())
   230  }
   231  
   232  func ImageTargetsByID(iTargets []ImageTarget) map[TargetID]ImageTarget {
   233  	result := make(map[TargetID]ImageTarget, len(iTargets))
   234  	for _, target := range iTargets {
   235  		result[target.ID()] = target
   236  	}
   237  	return result
   238  }
   239  
   240  type DockerBuild struct {
   241  	Dockerfile  string
   242  	BuildPath   string // the absolute path to the files
   243  	BuildArgs   DockerBuildArgs
   244  	FastBuild   FastBuild  // Optionally, can use FastBuild to update this build in place.
   245  	LiveUpdate  LiveUpdate // Optionally, can use LiveUpdate to update this build in place.
   246  	TargetStage DockerBuildTarget
   247  }
   248  
   249  func (DockerBuild) buildDetails() {}
   250  
   251  type DockerBuildTarget string
   252  
   253  func (s DockerBuildTarget) String() string { return string(s) }
   254  
   255  type FastBuild struct {
   256  	BaseDockerfile string
   257  	Syncs          []Sync
   258  	Runs           []Run
   259  	Entrypoint     Cmd
   260  
   261  	// A HotReload container image knows how to automatically
   262  	// reload any changes in the container. No need to restart it.
   263  	HotReload bool
   264  }
   265  
   266  func (FastBuild) buildDetails()  {}
   267  func (fb FastBuild) Empty() bool { return reflect.DeepEqual(fb, FastBuild{}) }
   268  
   269  type CustomBuild struct {
   270  	Command string
   271  	// Deps is a list of file paths that are dependencies of this command.
   272  	Deps []string
   273  
   274  	// Optional: tag we expect the image to be built with (we use this to check that
   275  	// the expected image+tag has been created).
   276  	// If empty, we create an expected tag at the beginning of CustomBuild (and
   277  	// export $EXPECTED_REF=name:expected_tag )
   278  	Tag string
   279  
   280  	Fast        FastBuild
   281  	LiveUpdate  LiveUpdate // Optionally, can use LiveUpdate to update this build in place.
   282  	DisablePush bool
   283  }
   284  
   285  func (CustomBuild) buildDetails() {}
   286  
   287  func (cb CustomBuild) WithTag(t string) CustomBuild {
   288  	cb.Tag = t
   289  	return cb
   290  }
   291  
   292  var _ TargetSpec = ImageTarget{}