Skip to content

Commit 027bf63

Browse files
committed
feat: expose plan artifact in actions api
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
1 parent 56c19f1 commit 027bf63

5 files changed

Lines changed: 114 additions & 91 deletions

File tree

cmd/nelm/release_plan_install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func newReleasePlanInstallCommand(ctx context.Context, afterAllCommandsBuiltFunc
4848
cfg.Chart = args[0]
4949
}
5050

51-
if err := action.ReleasePlanInstall(ctx, cfg.ReleaseName, cfg.ReleaseNamespace, cfg.ReleasePlanInstallOptions); err != nil {
51+
if _, err := action.ReleasePlanInstall(ctx, cfg.ReleaseName, cfg.ReleaseNamespace, cfg.ReleasePlanInstallOptions); err != nil {
5252
return fmt.Errorf("release plan install: %w", err)
5353
}
5454

pkg/action/release_install.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ type ReleaseInstallOptions struct {
8888
// LegacyLogRegistryStreamOut is the output writer for Helm registry client logs.
8989
// Defaults to io.Discard if not set. Used for debugging registry operations.
9090
LegacyLogRegistryStreamOut io.Writer
91+
// LegacyPlanArtifact provides plan artifact as a result of the release plan install action.
92+
LegacyPlanArtifact *plan.Artifact
9193
// LegacyProgressReportCh, when non-nil, receives ProgressReport snapshots during deployment.
9294
// Must be a buffered channel with capacity >= 1. The caller owns the channel and is responsible
9395
// for its lifecycle. Intermediate reports may be dropped if the consumer is slow; the final
@@ -165,8 +167,6 @@ func ReleaseInstall(ctx context.Context, releaseName, releaseNamespace string, o
165167
}
166168

167169
func releaseInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc, releaseName, releaseNamespace string, opts ReleaseInstallOptions) error {
168-
usePlan := opts.PlanArtifactPath != ""
169-
170170
currentDir, err := os.Getwd()
171171
if err != nil {
172172
return fmt.Errorf("get current working directory: %w", err)
@@ -186,20 +186,27 @@ func releaseInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc, re
186186
lo.Must0(os.Setenv("WERF_SECRET_KEY", opts.SecretKey))
187187
}
188188

189-
var planArtifact *plan.PlanArtifact
190-
if usePlan {
189+
var planArtifact *plan.Artifact
190+
191+
if opts.PlanArtifactPath != "" {
191192
log.Default.Info(ctx, "Using %s plan artifact", opts.PlanArtifactPath)
192193

193194
log.Default.Debug(ctx, "Read plan artifact")
194195

195-
planArtifact, err = plan.ReadPlanArtifact(ctx, opts.PlanArtifactPath, opts.SecretKey, opts.SecretWorkDir)
196+
planArtifact, err = plan.ReadArtifact(ctx, opts.PlanArtifactPath, opts.SecretKey, opts.SecretWorkDir)
196197
if err != nil {
197198
return fmt.Errorf("read plan artifact from %s: %w", opts.PlanArtifactPath, err)
198199
}
200+
} else {
201+
planArtifact = opts.LegacyPlanArtifact
202+
}
203+
204+
usePlan := planArtifact != nil
199205

206+
if usePlan {
200207
log.Default.Debug(ctx, "Validate plan artifact")
201208

202-
if err := plan.ValidatePlanArtifact(planArtifact, opts.PlanArtifactLifetime); err != nil {
209+
if err := plan.ValidateArtifact(planArtifact, opts.PlanArtifactLifetime); err != nil {
203210
return fmt.Errorf("validate plan artifact: %w", err)
204211
}
205212

pkg/action/release_plan_install.go

Lines changed: 69 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ type ReleasePlanInstallOptions struct {
108108
}
109109

110110
// Plans the next release installation without applying changes to the cluster.
111-
func ReleasePlanInstall(ctx context.Context, releaseName, releaseNamespace string, opts ReleasePlanInstallOptions) error {
111+
func ReleasePlanInstall(ctx context.Context, releaseName, releaseNamespace string, opts ReleasePlanInstallOptions) (*plan.Artifact, error) {
112112
ctx, ctxCancelFn := context.WithCancelCause(ctx)
113113

114114
if opts.Timeout == 0 {
@@ -118,35 +118,41 @@ func ReleasePlanInstall(ctx context.Context, releaseName, releaseNamespace strin
118118
ctx, _ = context.WithTimeoutCause(ctx, opts.Timeout, fmt.Errorf("context timed out: action timed out after %s", opts.Timeout.String()))
119119
defer ctxCancelFn(fmt.Errorf("context canceled: action finished"))
120120

121-
actionCh := make(chan error, 1)
121+
type actionResult struct {
122+
artifact *plan.Artifact // Replace with your actual type
123+
err error
124+
}
125+
126+
actionCh := make(chan actionResult, 1)
122127
go func() {
123-
actionCh <- releasePlanInstall(ctx, ctxCancelFn, releaseName, releaseNamespace, opts)
128+
planArtifact, err := releasePlanInstall(ctx, ctxCancelFn, releaseName, releaseNamespace, opts)
129+
actionCh <- actionResult{artifact: planArtifact, err: err}
124130
}()
125131

126132
for {
127133
select {
128-
case err := <-actionCh:
129-
return err
134+
case res := <-actionCh:
135+
return res.artifact, res.err
130136
case <-ctx.Done():
131-
return context.Cause(ctx)
137+
return nil, context.Cause(ctx)
132138
}
133139
}
134140
}
135141

136-
func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc, releaseName, releaseNamespace string, opts ReleasePlanInstallOptions) error {
142+
func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc, releaseName, releaseNamespace string, opts ReleasePlanInstallOptions) (*plan.Artifact, error) {
137143
currentDir, err := os.Getwd()
138144
if err != nil {
139-
return fmt.Errorf("get current working directory: %w", err)
145+
return nil, fmt.Errorf("get current working directory: %w", err)
140146
}
141147

142148
homeDir, err := os.UserHomeDir()
143149
if err != nil {
144-
return fmt.Errorf("get home directory: %w", err)
150+
return nil, fmt.Errorf("get home directory: %w", err)
145151
}
146152

147153
opts, err = applyReleasePlanInstallOptionsDefaults(opts, currentDir, homeDir)
148154
if err != nil {
149-
return fmt.Errorf("build release plan install options: %w", err)
155+
return nil, fmt.Errorf("build release plan install options: %w", err)
150156
}
151157

152158
if opts.SecretKey != "" {
@@ -158,12 +164,12 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
158164
KubeContextNamespace: releaseNamespace, // TODO: unset it everywhere
159165
})
160166
if err != nil {
161-
return fmt.Errorf("construct kube config: %w", err)
167+
return nil, fmt.Errorf("construct kube config: %w", err)
162168
}
163169

164170
clientFactory, err := kube.NewClientFactory(ctx, kubeConfig)
165171
if err != nil {
166-
return fmt.Errorf("construct kube client factory: %w", err)
172+
return nil, fmt.Errorf("construct kube client factory: %w", err)
167173
}
168174

169175
helmRegistryClientOpts := []registry.ClientOption{
@@ -181,14 +187,14 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
181187

182188
helmRegistryClient, err := registry.NewClient(helmRegistryClientOpts...)
183189
if err != nil {
184-
return fmt.Errorf("construct registry client: %w", err)
190+
return nil, fmt.Errorf("construct registry client: %w", err)
185191
}
186192

187193
releaseStorage, err := release.NewReleaseStorage(ctx, releaseNamespace, opts.ReleaseStorageDriver, clientFactory, release.ReleaseStorageOptions{
188194
SQLConnection: opts.ReleaseStorageSQLConnection,
189195
})
190196
if err != nil {
191-
return fmt.Errorf("construct release storage: %w", err)
197+
return nil, fmt.Errorf("construct release storage: %w", err)
192198
}
193199

194200
helmOptions := common.HelmOptions{
@@ -213,7 +219,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
213219

214220
history, err := release.BuildHistory(releaseName, releaseStorage, release.HistoryOptions{})
215221
if err != nil {
216-
return fmt.Errorf("build release history: %w", err)
222+
return nil, fmt.Errorf("build release history: %w", err)
217223
}
218224

219225
releases := history.Releases()
@@ -257,7 +263,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
257263
TemplatesAllowDNS: opts.TemplatesAllowDNS,
258264
})
259265
if err != nil {
260-
return fmt.Errorf("render chart: %w", err)
266+
return nil, fmt.Errorf("render chart: %w", err)
261267
}
262268

263269
log.Default.Debug(ctx, "Build transformed resource specs")
@@ -267,7 +273,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
267273
spec.NewDropInvalidAnnotationsAndLabelsTransformer(),
268274
})
269275
if err != nil {
270-
return fmt.Errorf("build transformed resource specs: %w", err)
276+
return nil, fmt.Errorf("build transformed resource specs: %w", err)
271277
}
272278

273279
log.Default.Debug(ctx, "Build releasable resource specs")
@@ -283,7 +289,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
283289

284290
releasableResSpecs, err := spec.BuildReleasableResourceSpecs(ctx, releaseNamespace, transformedResSpecs, patchers)
285291
if err != nil {
286-
return fmt.Errorf("build releasable resource specs: %w", err)
292+
return nil, fmt.Errorf("build releasable resource specs: %w", err)
287293
}
288294

289295
newRelease, err := release.NewRelease(releaseName, releaseNamespace, newRevision, deployType, releasableResSpecs, renderChartResult.Chart, renderChartResult.ReleaseConfig, release.ReleaseOptions{
@@ -292,7 +298,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
292298
Notes: renderChartResult.Notes,
293299
})
294300
if err != nil {
295-
return fmt.Errorf("construct new release: %w", err)
301+
return nil, fmt.Errorf("construct new release: %w", err)
296302
}
297303

298304
log.Default.Debug(ctx, "Convert previous release to resource specs")
@@ -301,15 +307,15 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
301307
if prevRelease != nil {
302308
prevRelResSpecs, err = release.ReleaseToResourceSpecs(prevRelease, releaseNamespace, false)
303309
if err != nil {
304-
return fmt.Errorf("convert previous release to resource specs: %w", err)
310+
return nil, fmt.Errorf("convert previous release to resource specs: %w", err)
305311
}
306312
}
307313

308314
log.Default.Debug(ctx, "Convert new release to resource specs")
309315

310316
newRelResSpecs, err := release.ReleaseToResourceSpecs(newRelease, releaseNamespace, false)
311317
if err != nil {
312-
return fmt.Errorf("convert new release to resource specs: %w", err)
318+
return nil, fmt.Errorf("convert new release to resource specs: %w", err)
313319
}
314320

315321
log.Default.Debug(ctx, "Build resources")
@@ -322,13 +328,13 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
322328
DefaultDeletePropagation: metav1.DeletionPropagation(opts.DefaultDeletePropagation),
323329
})
324330
if err != nil {
325-
return fmt.Errorf("build resources: %w", err)
331+
return nil, fmt.Errorf("build resources: %w", err)
326332
}
327333

328334
log.Default.Debug(ctx, "Locally validate resources")
329335

330336
if err := resource.ValidateLocal(ctx, releaseNamespace, instResources, opts.ResourceValidationOptions); err != nil {
331-
return fmt.Errorf("locally validate resources: %w", err)
337+
return nil, fmt.Errorf("locally validate resources: %w", err)
332338
}
333339

334340
log.Default.Debug(ctx, "Build resource infos")
@@ -339,7 +345,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
339345
if lastDeployedOrLastRelease != nil {
340346
lastDeployedOrLastRelResSpecs, err = release.ReleaseToResourceSpecs(lastDeployedOrLastRelease, releaseNamespace, false)
341347
if err != nil {
342-
return fmt.Errorf("convert last deployed or last release to resource specs: %w", err)
348+
return nil, fmt.Errorf("convert last deployed or last release to resource specs: %w", err)
343349
}
344350
}
345351

@@ -349,20 +355,20 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
349355
LastDeployedOrLastRelResourceSpecs: lastDeployedOrLastRelResSpecs,
350356
})
351357
if err != nil {
352-
return fmt.Errorf("build resource infos: %w", err)
358+
return nil, fmt.Errorf("build resource infos: %w", err)
353359
}
354360

355361
log.Default.Debug(ctx, "Remotely validate resources")
356362

357363
if err := plan.ValidateRemote(releaseName, releaseNamespace, instResInfos, opts.ForceAdoption); err != nil {
358-
return fmt.Errorf("remotely validate resources: %w", err)
364+
return nil, fmt.Errorf("remotely validate resources: %w", err)
359365
}
360366

361367
log.Default.Debug(ctx, "Build release infos")
362368

363369
relInfos, err := plan.BuildReleaseInfos(ctx, deployType, releases, newRelease)
364370
if err != nil {
365-
return fmt.Errorf("build release infos: %w", err)
371+
return nil, fmt.Errorf("build release infos: %w", err)
366372
}
367373

368374
log.Default.Debug(ctx, "Build install plan")
@@ -373,18 +379,18 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
373379
if err != nil {
374380
handleBuildPlanErr(ctx, installPlan, err, opts.InstallGraphPath, opts.TempDirPath, "release-install-graph.dot")
375381

376-
return fmt.Errorf("build install plan: %w", err)
382+
return nil, fmt.Errorf("build install plan: %w", err)
377383
}
378384

379385
if opts.InstallGraphPath != "" {
380386
if err := savePlanAsDot(installPlan, opts.InstallGraphPath); err != nil {
381-
return fmt.Errorf("save release install graph: %w", err)
387+
return nil, fmt.Errorf("save release install graph: %w", err)
382388
}
383389
}
384390

385391
releaseIsUpToDate, err := release.IsReleaseUpToDate(prevRelease, newRelease)
386392
if err != nil {
387-
return fmt.Errorf("check if release is up to date: %w", err)
393+
return nil, fmt.Errorf("check if release is up to date: %w", err)
388394
}
389395

390396
installPlanIsUseless := lo.NoneBy(installPlan.Operations(), func(op *plan.Operation) bool {
@@ -400,7 +406,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
400406

401407
changes, err := plan.CalculatePlannedChanges(instResInfos, delResInfos)
402408
if err != nil {
403-
return fmt.Errorf("calculate planned changes: %w", err)
409+
return nil, fmt.Errorf("calculate planned changes: %w", err)
404410
}
405411

406412
if releaseIsUpToDate && installPlanIsUseless {
@@ -409,56 +415,56 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
409415
log.Default.Info(ctx, color.Style{color.Bold, color.Yellow}.Render(fmt.Sprintf("No resource changes planned, but still must install release %q (namespace: %q)", releaseName, releaseNamespace)))
410416
}
411417

412-
if err := logPlannedChanges(ctx, releaseName, releaseNamespace, changes, opts.ResourceDiffOptions); err != nil {
413-
return fmt.Errorf("log planned changes: %w", err)
418+
planArtifact := &plan.Artifact{
419+
APIVersion: plan.ArtifactSchemeVersion,
420+
Data: &plan.ArtifactData{
421+
Options: opts.ReleaseInstallRuntimeOptions,
422+
Release: newRelease,
423+
Plan: installPlan,
424+
Changes: changes,
425+
InstallableResourceInfos: instResInfos,
426+
ReleaseInfos: relInfos,
427+
},
428+
DeployType: deployType,
429+
Release: plan.ArtifactRelease{
430+
Name: releaseName,
431+
Namespace: releaseNamespace,
432+
Revision: newRelease.Version,
433+
},
434+
Timestamp: time.Now().UTC(),
414435
}
415436

416-
if opts.PlanArtifactPath != "" {
417-
planArtifact := &plan.PlanArtifact{
418-
APIVersion: plan.PlanArtifactSchemeVersion,
419-
Data: &plan.PlanArtifactData{
420-
Options: opts.ReleaseInstallRuntimeOptions,
421-
Release: newRelease,
422-
Plan: installPlan,
423-
Changes: changes,
424-
InstallableResourceInfos: instResInfos,
425-
ReleaseInfos: relInfos,
426-
},
427-
DeployType: deployType,
428-
Release: plan.PlanArtifactRelease{
429-
Name: releaseName,
430-
Namespace: releaseNamespace,
431-
Revision: newRelease.Version,
432-
},
433-
Timestamp: time.Now().UTC(),
434-
}
437+
if err := logPlannedChanges(ctx, planArtifact, opts.ResourceDiffOptions); err != nil {
438+
return nil, fmt.Errorf("log planned changes: %w", err)
439+
}
435440

436-
if err := plan.WritePlanArtifact(ctx, planArtifact, opts.PlanArtifactPath, opts.SecretKey, opts.SecretWorkDir); err != nil {
437-
return fmt.Errorf("save install plan to %q: %w", opts.PlanArtifactPath, err)
441+
if opts.PlanArtifactPath != "" {
442+
if err := plan.WriteArtifact(ctx, planArtifact, opts.PlanArtifactPath, opts.SecretKey, opts.SecretWorkDir); err != nil {
443+
return nil, fmt.Errorf("save install plan to %q: %w", opts.PlanArtifactPath, err)
438444
}
439445
}
440446

441447
if opts.ErrorIfChangesPlanned {
442448
if releaseIsUpToDate && installPlanIsUseless {
443-
return nil
449+
return planArtifact, nil
444450
} else if installPlanIsUseless || len(changes) == 0 {
445-
return ErrReleaseInstallPlanned
451+
return planArtifact, ErrReleaseInstallPlanned
446452
}
447453

448-
return ErrResourceChangesPlanned
454+
return planArtifact, ErrResourceChangesPlanned
449455
}
450456

451-
return nil
457+
return planArtifact, nil
452458
}
453459

454-
func logPlannedChanges(ctx context.Context, releaseName, releaseNamespace string, changes []*plan.ResourceChange, opts common.ResourceDiffOptions) error {
455-
if len(changes) == 0 {
460+
func logPlannedChanges(ctx context.Context, planArtifact *plan.Artifact, opts common.ResourceDiffOptions) error {
461+
if len(planArtifact.Data.Changes) == 0 {
456462
return nil
457463
}
458464

459465
log.Default.Info(ctx, "")
460466

461-
for _, change := range changes {
467+
for _, change := range planArtifact.Data.Changes {
462468
if err := log.Default.InfoBlockErr(ctx, log.BlockOptions{
463469
BlockTitle: buildDiffHeader(change),
464470
}, func() error {
@@ -479,10 +485,10 @@ func logPlannedChanges(ctx context.Context, releaseName, releaseNamespace string
479485
}
480486
}
481487

482-
log.Default.Info(ctx, color.Bold.Render("Planned changes summary")+" for release %q (namespace: %q):", releaseName, releaseNamespace)
488+
log.Default.Info(ctx, color.Bold.Render("Planned changes summary")+" for release %q (namespace: %q):", planArtifact.Release.Name, planArtifact.Release.Namespace)
483489

484490
for _, changeType := range []string{"create", "recreate", "update", "blind apply", "delete"} {
485-
logSummaryLine(ctx, changes, changeType)
491+
logSummaryLine(ctx, planArtifact.Data.Changes, changeType)
486492
}
487493

488494
log.Default.Info(ctx, "")

0 commit comments

Comments
 (0)