-
Notifications
You must be signed in to change notification settings - Fork 54
add ability to configure and run preflight checks during install, upgrade, and uninstall operations #327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add ability to configure and run preflight checks during install, upgrade, and uninstall operations #327
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,11 +58,13 @@ type ActionInterface interface { | |
| Reconcile(rel *release.Release) error | ||
| } | ||
|
|
||
| type GetOption func(*action.Get) error | ||
| type InstallOption func(*action.Install) error | ||
| type UpgradeOption func(*action.Upgrade) error | ||
| type UninstallOption func(*action.Uninstall) error | ||
| type RollbackOption func(*action.Rollback) error | ||
| type ( | ||
| GetOption func(*action.Get) error | ||
| InstallOption func(*action.Install) error | ||
| UpgradeOption func(*action.Upgrade) error | ||
| UninstallOption func(*action.Uninstall) error | ||
| RollbackOption func(*action.Rollback) error | ||
| ) | ||
|
|
||
| type ActionClientGetterOption func(*actionClientGetter) error | ||
|
|
||
|
|
@@ -115,6 +117,13 @@ func AppendPostRenderers(postRendererFns ...PostRendererProvider) ActionClientGe | |
| } | ||
| } | ||
|
|
||
| func AppendPreflight(preflights ...Preflight) ActionClientGetterOption { | ||
| return func(getter *actionClientGetter) error { | ||
| getter.preflights = append(getter.preflights, preflights...) | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func NewActionClientGetter(acg ActionConfigGetter, opts ...ActionClientGetterOption) (ActionClientGetter, error) { | ||
| actionClientGetter := &actionClientGetter{acg: acg} | ||
| for _, opt := range opts { | ||
|
|
@@ -125,6 +134,39 @@ func NewActionClientGetter(acg ActionConfigGetter, opts ...ActionClientGetterOpt | |
| return actionClientGetter, nil | ||
| } | ||
|
|
||
| type Preflight interface { | ||
| Run(string, *release.Release) error | ||
| Name() string | ||
| } | ||
|
|
||
| const ( | ||
| PreflightOperationInstall = "install" | ||
| PreflightOperationUpgrade = "upgrade" | ||
| PreflightOperationUninstall = "uninstall" | ||
| ) | ||
|
|
||
| type PreflightFunc func(string, *release.Release) error | ||
|
|
||
| func NewPreflightFunc(name string, pf PreflightFunc) Preflight { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open to naming suggestions here. I'm thinking something like Going to spend some time looking into how other packages tend to do this to get some inspiration but figured I shouldn't hold getting a PR up for review on this. |
||
| return &preflightFuncImpl{ | ||
| preflightFunc: pf, | ||
| name: name, | ||
| } | ||
| } | ||
|
|
||
| type preflightFuncImpl struct { | ||
| preflightFunc PreflightFunc | ||
| name string | ||
| } | ||
|
|
||
| func (pfi *preflightFuncImpl) Run(operation string, rel *release.Release) error { | ||
| return pfi.preflightFunc(operation, rel) | ||
| } | ||
|
|
||
| func (pfi *preflightFuncImpl) Name() string { | ||
| return pfi.name | ||
| } | ||
|
|
||
| type actionClientGetter struct { | ||
| acg ActionConfigGetter | ||
|
|
||
|
|
@@ -136,6 +178,8 @@ type actionClientGetter struct { | |
| installFailureUninstallOpts []UninstallOption | ||
| upgradeFailureRollbackOpts []RollbackOption | ||
|
|
||
| preflights []Preflight | ||
|
|
||
| postRendererProviders []PostRendererProvider | ||
| } | ||
|
|
||
|
|
@@ -150,7 +194,7 @@ func (hcg *actionClientGetter) ActionClientFor(ctx context.Context, obj client.O | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| var cpr = chainedPostRenderer{} | ||
| cpr := chainedPostRenderer{} | ||
| for _, provider := range hcg.postRendererProviders { | ||
| cpr = append(cpr, provider(rm, actionConfig.KubeClient, obj)) | ||
| } | ||
|
|
@@ -167,6 +211,8 @@ func (hcg *actionClientGetter) ActionClientFor(ctx context.Context, obj client.O | |
| defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(cpr)}, hcg.defaultUpgradeOpts...), | ||
| defaultUninstallOpts: hcg.defaultUninstallOpts, | ||
|
|
||
| preflights: hcg.preflights, | ||
|
|
||
| installFailureUninstallOpts: hcg.installFailureUninstallOpts, | ||
| upgradeFailureRollbackOpts: hcg.upgradeFailureRollbackOpts, | ||
| }, nil | ||
|
|
@@ -180,6 +226,8 @@ type actionClient struct { | |
| defaultUpgradeOpts []UpgradeOption | ||
| defaultUninstallOpts []UninstallOption | ||
|
|
||
| preflights []Preflight | ||
|
|
||
| installFailureUninstallOpts []UninstallOption | ||
| upgradeFailureRollbackOpts []RollbackOption | ||
| } | ||
|
|
@@ -205,6 +253,25 @@ func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals m | |
| } | ||
| install.ReleaseName = name | ||
| install.Namespace = namespace | ||
|
|
||
| // TODO: Open Question - should we always run preflight checks if they exist? | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open to suggestions here. My thinking behind not running the preflight checks if dry run is specified is because the caller may have a certain expectation of the output with a dry run and including preflight checks in that could pollute the dry run results. |
||
| if len(c.preflights) > 0 && !install.DryRun { | ||
| install.DryRun = true | ||
| c.conf.Log("Running preflight checks") | ||
| c.conf.Log("Performing install dry run") | ||
| rel, err := install.Run(chrt, vals) | ||
| if err != nil { | ||
| c.conf.Log("Install dry run failed") | ||
| return nil, fmt.Errorf("preflight checks were specified, install dry run failed: %w", err) | ||
| } | ||
| for _, preflight := range c.preflights { | ||
| if err := preflight.Run(PreflightOperationInstall, rel); err != nil { | ||
| return nil, fmt.Errorf("preflight check %q failed: %w", preflight.Name(), err) | ||
| } | ||
| } | ||
| install.DryRun = false | ||
| } | ||
|
|
||
| c.conf.Log("Starting install") | ||
| rel, err := install.Run(chrt, vals) | ||
| if err != nil { | ||
|
|
@@ -241,6 +308,25 @@ func (c *actionClient) Upgrade(name, namespace string, chrt *chart.Chart, vals m | |
| } | ||
| } | ||
| upgrade.Namespace = namespace | ||
|
|
||
| // TODO: Open Question - should we always run preflight checks if they exist? | ||
| if len(c.preflights) > 0 && !upgrade.DryRun { | ||
| upgrade.DryRun = true | ||
| c.conf.Log("Running preflight checks") | ||
| c.conf.Log("Performing upgrade dry run") | ||
| rel, err := upgrade.Run(name, chrt, vals) | ||
| if err != nil { | ||
| c.conf.Log("Upgrade dry run failed") | ||
| return nil, fmt.Errorf("preflight checks were specified, upgrade dry run failed: %w", err) | ||
| } | ||
| for _, preflight := range c.preflights { | ||
| if err := preflight.Run(PreflightOperationUpgrade, rel); err != nil { | ||
| return nil, fmt.Errorf("preflight check %q failed: %w", preflight.Name(), err) | ||
| } | ||
| } | ||
| upgrade.DryRun = false | ||
| } | ||
|
|
||
| rel, err := upgrade.Run(name, chrt, vals) | ||
| if err != nil { | ||
| if rel != nil { | ||
|
|
@@ -286,6 +372,25 @@ func (c *actionClient) uninstall(name string, opts ...UninstallOption) (*release | |
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| // TODO: Open Question - should we always run preflight checks if they exist? | ||
| if len(c.preflights) > 0 && !uninstall.DryRun { | ||
| uninstall.DryRun = true | ||
| c.conf.Log("Running preflight checks") | ||
| c.conf.Log("Performing uninstall dry run") | ||
| rel, err := uninstall.Run(name) | ||
| if err != nil { | ||
| c.conf.Log("Uninstall dry run failed") | ||
| return nil, fmt.Errorf("preflight checks were specified, uninstall dry run failed: %w", err) | ||
| } | ||
| for _, preflight := range c.preflights { | ||
| if err := preflight.Run(PreflightOperationUninstall, rel.Release); err != nil { | ||
| return nil, fmt.Errorf("preflight check %q failed: %w", preflight.Name(), err) | ||
| } | ||
| } | ||
| uninstall.DryRun = false | ||
| } | ||
|
|
||
| return uninstall.Run(name) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing a string representing whether it's an install/upgrade/uninstall, could we have a struct that implements separate functions for each phase as a no-op by default where a user can provide "overrides" for just the preflight phases they care about. Basically something something similar to controller-runtime's handler.Funcs