github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/planner/build.go (about)

     1  package planner
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  
     7  	"github.com/go-kit/kit/log"
     8  	"github.com/go-kit/kit/log/level"
     9  	"github.com/pkg/errors"
    10  	"github.com/replicatedhq/libyaml"
    11  	"github.com/replicatedhq/ship/pkg/api"
    12  	"github.com/replicatedhq/ship/pkg/images"
    13  	"github.com/replicatedhq/ship/pkg/lifecycle/daemon/daemontypes"
    14  	"github.com/replicatedhq/ship/pkg/lifecycle/render/root"
    15  	"github.com/replicatedhq/ship/pkg/templates"
    16  )
    17  
    18  type buildProgress struct {
    19  	StepNumber int `json:"step_number"`
    20  	TotalSteps int `json:"total_steps"`
    21  }
    22  
    23  // Build builds a plan in memory from assets+resolved config
    24  func (p *CLIPlanner) Build(renderRoot string, assets []api.Asset, configGroups []libyaml.ConfigGroup, meta api.ReleaseMetadata, templateContext map[string]interface{}) (Plan, []string, error) {
    25  	defer p.Status.ClearProgress()
    26  	debug := level.Debug(log.With(p.Logger, "step.type", "render", "phase", "plan"))
    27  
    28  	debug.Log("renderRoot", renderRoot)
    29  	rootFs := root.NewRootFS(p.Fs, renderRoot)
    30  
    31  	builder, err := p.BuilderBuilder.FullBuilder(meta, configGroups, templateContext)
    32  	if err != nil {
    33  		return nil, nil, errors.Wrap(err, "init builder")
    34  	}
    35  
    36  	var plan Plan
    37  	for i, asset := range assets {
    38  		progress := buildProgress{
    39  			StepNumber: i,
    40  			TotalSteps: len(assets),
    41  		}
    42  		p.Status.SetProgress(daemontypes.JSONProgress("build", progress))
    43  
    44  		if asset.Inline != nil {
    45  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.Inline.AssetShared.When)
    46  			if err != nil {
    47  				return nil, nil, err
    48  			}
    49  
    50  			p.logAssetResolve(debug, evaluatedWhen, "inline")
    51  			if evaluatedWhen {
    52  				plan = append(plan, p.inlineStep(rootFs, *asset.Inline, configGroups, meta, templateContext))
    53  			}
    54  		} else if asset.Docker != nil {
    55  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.Docker.AssetShared.When)
    56  			if err != nil {
    57  				return nil, nil, err
    58  			}
    59  
    60  			p.logAssetResolve(debug, evaluatedWhen, "docker")
    61  			if evaluatedWhen {
    62  				plan = append(plan, p.dockerStep(rootFs, *asset.Docker, meta, templateContext, configGroups))
    63  			}
    64  		} else if asset.Helm != nil {
    65  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.Helm.AssetShared.When)
    66  			if err != nil {
    67  				return nil, nil, err
    68  			}
    69  
    70  			p.logAssetResolve(debug, evaluatedWhen, "helm")
    71  			if evaluatedWhen {
    72  				plan = append(plan, p.helmStep(rootFs, *asset.Helm, meta, templateContext, configGroups))
    73  			}
    74  		} else if asset.Local != nil {
    75  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.Local.AssetShared.When)
    76  			if err != nil {
    77  				return nil, nil, err
    78  			}
    79  
    80  			p.logAssetResolve(debug, evaluatedWhen, "local")
    81  			if evaluatedWhen {
    82  				plan = append(plan, p.localStep(*asset.Local, meta, templateContext, configGroups))
    83  			}
    84  		} else if asset.DockerLayer != nil {
    85  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.DockerLayer.AssetShared.When)
    86  			if err != nil {
    87  				return nil, nil, err
    88  			}
    89  
    90  			p.logAssetResolve(debug, evaluatedWhen, "dockerlayer")
    91  			if evaluatedWhen {
    92  				plan = append(plan, p.dockerLayerStep(rootFs, *asset.DockerLayer, meta, templateContext, configGroups))
    93  			}
    94  		} else if asset.Web != nil {
    95  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.Web.AssetShared.When)
    96  			if err != nil {
    97  				return nil, nil, err
    98  			}
    99  
   100  			p.logAssetResolve(debug, evaluatedWhen, "web")
   101  			if evaluatedWhen {
   102  				plan = append(plan, p.webStep(rootFs, *asset.Web, meta, configGroups, templateContext))
   103  			}
   104  		} else if asset.GitHub != nil {
   105  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.GitHub.AssetShared.When)
   106  			if err != nil {
   107  				return nil, nil, err
   108  			}
   109  
   110  			p.logAssetResolve(debug, evaluatedWhen, "github")
   111  			if evaluatedWhen {
   112  				plan = append(plan, p.githubStep(rootFs, *asset.GitHub, configGroups, renderRoot, meta, templateContext))
   113  			}
   114  		} else if asset.Terraform != nil {
   115  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.Terraform.AssetShared.When)
   116  			if err != nil {
   117  				return nil, nil, err
   118  			}
   119  
   120  			p.logAssetResolve(debug, evaluatedWhen, "terraform")
   121  			if evaluatedWhen {
   122  				plan = append(plan, p.terraformStep(rootFs, *asset.Terraform, meta, templateContext, configGroups))
   123  			}
   124  		} else if asset.AmazonEKS != nil {
   125  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.AmazonEKS.AssetShared.When)
   126  			if err != nil {
   127  				return nil, nil, err
   128  			}
   129  			p.logAssetResolve(debug, evaluatedWhen, "amazon kubernetes cluster")
   130  			if evaluatedWhen {
   131  				plan = append(plan, p.amazonEKSStep(rootFs, *asset.AmazonEKS, meta, templateContext, configGroups))
   132  			}
   133  		} else if asset.GoogleGKE != nil {
   134  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.GoogleGKE.AssetShared.When)
   135  			if err != nil {
   136  				return nil, nil, err
   137  			}
   138  			p.logAssetResolve(debug, evaluatedWhen, "google kubernetes cluster")
   139  			if evaluatedWhen {
   140  				plan = append(plan, p.googleGKEStep(rootFs, *asset.GoogleGKE, meta, templateContext, configGroups))
   141  			}
   142  		} else if asset.AzureAKS != nil {
   143  			evaluatedWhen, err := p.evalAssetWhen(debug, *builder, asset, asset.AzureAKS.AssetShared.When)
   144  			if err != nil {
   145  				return nil, nil, err
   146  			}
   147  			p.logAssetResolve(debug, evaluatedWhen, "azure kubernetes cluster")
   148  			if evaluatedWhen {
   149  				plan = append(plan, p.azureAKSStep(rootFs, *asset.AzureAKS, meta, templateContext, configGroups))
   150  			}
   151  		} else {
   152  			debug.Log("event", "asset.resolve.fail", "asset", fmt.Sprintf("%#v", asset))
   153  			return nil, nil, errors.New(
   154  				"Unknown asset: type is not one of " +
   155  					"[inline docker helm dockerlayer github terraform amazon_eks google_gke azure_aks]",
   156  			)
   157  		}
   158  	}
   159  
   160  	dests, err := planToDests(plan, builder)
   161  	if err != nil {
   162  		return nil, nil, err
   163  	}
   164  
   165  	return plan, dests, nil
   166  }
   167  
   168  func (p *CLIPlanner) inlineStep(
   169  	rootFs root.Fs,
   170  	inline api.InlineAsset,
   171  	configGroups []libyaml.ConfigGroup,
   172  	meta api.ReleaseMetadata,
   173  	templateContext map[string]interface{},
   174  ) Step {
   175  	return Step{
   176  		Dest:        inline.Dest,
   177  		Description: inline.Description,
   178  		Execute:     p.Inline.Execute(rootFs, inline, meta, templateContext, configGroups),
   179  	}
   180  }
   181  
   182  func (p *CLIPlanner) webStep(
   183  	rootFs root.Fs,
   184  	web api.WebAsset,
   185  	meta api.ReleaseMetadata,
   186  	configGroups []libyaml.ConfigGroup,
   187  	templateContext map[string]interface{},
   188  ) Step {
   189  	return Step{
   190  		Dest:        web.Dest,
   191  		Description: web.Description,
   192  		Execute:     p.Web.Execute(rootFs, web, meta, templateContext, configGroups),
   193  	}
   194  }
   195  
   196  func (p *CLIPlanner) dockerStep(
   197  	rootFs root.Fs,
   198  	asset api.DockerAsset,
   199  	meta api.ReleaseMetadata,
   200  	templateContext map[string]interface{},
   201  	configGroups []libyaml.ConfigGroup,
   202  ) Step {
   203  	return Step{
   204  		Dest:        asset.Dest,
   205  		Description: asset.Description,
   206  		Execute: p.Docker.Execute(
   207  			rootFs,
   208  			asset,
   209  			meta,
   210  			p.watchProgress,
   211  			asset.Dest,
   212  			templateContext,
   213  			configGroups,
   214  		),
   215  	}
   216  }
   217  
   218  func (p *CLIPlanner) helmStep(
   219  	rootFs root.Fs,
   220  	asset api.HelmAsset,
   221  	meta api.ReleaseMetadata,
   222  	templateContext map[string]interface{},
   223  	configGroups []libyaml.ConfigGroup,
   224  ) Step {
   225  	return Step{
   226  		Dest:        asset.Dest,
   227  		Description: asset.Description,
   228  		Execute:     p.Helm.Execute(rootFs, asset, meta, templateContext, configGroups),
   229  	}
   230  }
   231  
   232  func (p *CLIPlanner) localStep(
   233  	asset api.LocalAsset,
   234  	meta api.ReleaseMetadata,
   235  	templateContext map[string]interface{},
   236  	configGroups []libyaml.ConfigGroup,
   237  ) Step {
   238  	return Step{
   239  		Dest:        asset.Dest,
   240  		Description: asset.Description,
   241  		Execute:     p.Local.Execute(asset, meta, templateContext, configGroups),
   242  	}
   243  }
   244  
   245  func (p *CLIPlanner) dockerLayerStep(
   246  	rootFs root.Fs,
   247  	asset api.DockerLayerAsset,
   248  	metadata api.ReleaseMetadata,
   249  	templateContext map[string]interface{},
   250  	configGroups []libyaml.ConfigGroup,
   251  ) Step {
   252  	return Step{
   253  		Dest:        asset.Dest,
   254  		Description: asset.Description,
   255  		Execute: p.DockerLayer.Execute(
   256  			rootFs,
   257  			asset,
   258  			metadata,
   259  			p.watchProgress,
   260  			templateContext,
   261  			configGroups,
   262  		),
   263  	}
   264  }
   265  
   266  func (p *CLIPlanner) terraformStep(
   267  	rootFs root.Fs,
   268  	asset api.TerraformAsset,
   269  	meta api.ReleaseMetadata,
   270  	templateContext map[string]interface{},
   271  	configGroups []libyaml.ConfigGroup,
   272  ) Step {
   273  	return Step{
   274  		Dest:        asset.Dest,
   275  		Description: asset.Description,
   276  		Execute:     p.Terraform.Execute(rootFs, asset, meta, templateContext, configGroups),
   277  	}
   278  }
   279  
   280  func (p *CLIPlanner) amazonEKSStep(
   281  	rootFs root.Fs,
   282  	asset api.EKSAsset,
   283  	meta api.ReleaseMetadata,
   284  	templateContext map[string]interface{},
   285  	configGroups []libyaml.ConfigGroup,
   286  ) Step {
   287  	return Step{
   288  		Dest:        asset.Dest,
   289  		Description: asset.Description,
   290  		Execute:     p.AmazonEKS.Execute(rootFs, asset, meta, templateContext, configGroups),
   291  	}
   292  }
   293  
   294  func (p *CLIPlanner) googleGKEStep(
   295  	rootFs root.Fs,
   296  	asset api.GKEAsset,
   297  	meta api.ReleaseMetadata,
   298  	templateContext map[string]interface{},
   299  	configGroups []libyaml.ConfigGroup,
   300  ) Step {
   301  	return Step{
   302  		Dest:        asset.Dest,
   303  		Description: asset.Description,
   304  		Execute:     p.GoogleGKE.Execute(rootFs, asset, meta, templateContext, configGroups),
   305  	}
   306  }
   307  
   308  func (p *CLIPlanner) azureAKSStep(
   309  	rootFs root.Fs,
   310  	asset api.AKSAsset,
   311  	meta api.ReleaseMetadata,
   312  	templateContext map[string]interface{},
   313  	configGroups []libyaml.ConfigGroup,
   314  ) Step {
   315  	return Step{
   316  		Dest:        asset.Dest,
   317  		Description: asset.Description,
   318  		Execute:     p.AzureAKS.Execute(rootFs, asset, meta, templateContext, configGroups),
   319  	}
   320  }
   321  
   322  func (p *CLIPlanner) evalAssetWhen(debug log.Logger, builder templates.Builder, asset api.Asset, assetWhen string) (bool, error) {
   323  	builtWhen, err := builder.String(assetWhen)
   324  	if err != nil {
   325  		debug.Log("event", "asset.when.fail", "asset", fmt.Sprintf("%#v", asset))
   326  		return false, err
   327  	}
   328  
   329  	builtWhenBool, err := builder.Bool(builtWhen, true)
   330  	if err != nil {
   331  		debug.Log("event", "asset.when.fail", "asset", fmt.Sprintf("%#v", asset))
   332  		return false, err
   333  	}
   334  
   335  	return builtWhenBool, nil
   336  }
   337  
   338  func (p *CLIPlanner) logAssetResolve(debug log.Logger, when bool, assetType string) {
   339  	if when {
   340  		debug.Log("event", "asset.when.true", "asset.type", assetType)
   341  		debug.Log("event", "asset.resolve", "asset.type", assetType)
   342  	} else {
   343  		debug.Log("event", "asset.when.false", "asset.type", assetType)
   344  	}
   345  }
   346  
   347  func (p *CLIPlanner) watchProgress(ch chan interface{}, debug log.Logger) error {
   348  	var saveError error
   349  	for msg := range ch {
   350  		if msg == nil {
   351  			continue
   352  		}
   353  		switch v := msg.(type) {
   354  		case error:
   355  			// continue reading on error to ensure channel is not blocked
   356  			saveError = v
   357  		case images.Progress:
   358  			p.Status.SetProgress(daemontypes.JSONProgress("docker", v))
   359  		case string:
   360  			p.Status.SetProgress(daemontypes.StringProgress("docker", v))
   361  		default:
   362  			debug.Log("event", "progress", "message", fmt.Sprintf("%#v", v))
   363  		}
   364  	}
   365  	return saveError
   366  }
   367  
   368  func planToDests(plan Plan, builder *templates.Builder) ([]string, error) {
   369  	var dests []string
   370  
   371  	for _, step := range plan {
   372  		dest, err := builder.String(step.Dest)
   373  		if err != nil {
   374  			return nil, errors.Wrapf(err, "building dest %q", step.Dest)
   375  		}
   376  
   377  		// special case for docker URL dests - don't attempt to remove url paths
   378  		destinationURL, err := url.Parse(dest)
   379  		// if there was an error parsing the dest as a url, or the scheme was not 'docker', add to the dests list as normal
   380  		if err == nil && destinationURL.Scheme == "docker" {
   381  			continue
   382  		}
   383  
   384  		dests = append(dests, dest)
   385  	}
   386  
   387  	return dests, nil
   388  }