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 }