Skip to content
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

RHTAPINST-40: Installer Actor #30

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 138 additions & 3 deletions pkg/installer/installer.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,152 @@
package installer

import (
"context"
"fmt"
"log/slog"
"os"

"github.com/redhat-appstudio/rhtap-cli/pkg/chartfs"
"github.com/redhat-appstudio/rhtap-cli/pkg/config"
"github.com/redhat-appstudio/rhtap-cli/pkg/deployer"
"github.com/redhat-appstudio/rhtap-cli/pkg/engine"
"github.com/redhat-appstudio/rhtap-cli/pkg/flags"
"github.com/redhat-appstudio/rhtap-cli/pkg/hooks"
"github.com/redhat-appstudio/rhtap-cli/pkg/k8s"
"github.com/redhat-appstudio/rhtap-cli/pkg/printer"

"helm.sh/helm/v3/pkg/chartutil"
)

type Installer struct {
cfg *config.Spec // installer configuration
logger *slog.Logger // application logger
flags *flags.Flags // global flags
kube *k8s.Kube // kubernetes client
dep *config.Dependency // dependency to install
cfs *chartfs.ChartFS // chart file system

valuesBytes []byte // rendered values
values chartutil.Values // helm chart values
}

// prepareHelmClient prepares the Helm client for the given dependency, which also
// specifies the default namespace for the Helm Chart.
func (i *Installer) prepareHelmClient() (*deployer.Helm, error) {
i.logger.Debug("Loading dependency Helm chart (from CFS)")
chart, err := i.cfs.GetChartForDep(i.dep)
if err != nil {
return nil, err
}

i.logger.Debug("Loading Helm client for dependency and namespace")
return deployer.NewHelm(i.logger, i.flags, i.kube, i.dep.Namespace, chart)
}

// SetValues prepares the values template for the Helm chart installation.
func (i *Installer) SetValues(
ctx context.Context,
cfg *config.Spec,
valuesTmpl string,
) error {
i.logger.Debug("Preparing values template context")
variables := engine.NewVariables()
err := variables.SetInstaller(cfg)
if err != nil {
return err
}
if err = variables.SetOpenShift(ctx, i.kube); err != nil {
return err
}

i.logger.Debug("Rendering values template")
i.valuesBytes, err = engine.NewEngine(i.kube, valuesTmpl).Render(variables)
return err
}

// PrintRawValues prints the raw values template to the console.
func (i *Installer) PrintRawValues() {
i.logger.Debug("Showing raw results of rendered values template")
fmt.Printf("#\n# Values (Raw)\n#\n\n%s\n", i.valuesBytes)
}

// RenderValues parses the values template and prepares the Helm chart values.
func (i *Installer) RenderValues() error {
if i.valuesBytes == nil {
return fmt.Errorf("values not set")
}

i.logger.Debug("Preparing rendered values for Helm installation")
var err error
i.values, err = chartutil.ReadValues(i.valuesBytes)
return err
}

// PrintValues prints the parsed values to the console.
func (i *Installer) PrintValues() {
i.logger.Debug("Showing parsed values")
printer.ValuesPrinter("Values", i.values)
}

// Install performs the installation of the Helm chart, including the pre and post
// hooks execution.
func (i *Installer) Install() error {
if i.values == nil {
return fmt.Errorf("values not set")
}
hc, err := i.prepareHelmClient()
if err != nil {
return err
}

hook := hooks.NewHooks(i.cfs, i.dep, os.Stdout, os.Stderr)
if !i.flags.DryRun {
i.logger.Debug("Running pre-deploy hook script...")
if err = hook.PreDeploy(i.values); err != nil {
return err
}
} else {
i.logger.Debug("Skipping pre-deploy hook script (dry-run)")
}

// Performing the installation, or upgrade, of the Helm chart dependency,
// using the values rendered before hand.
i.logger.Debug("Installing the Helm chart")
if err = hc.Install(i.values); err != nil {
return err
}
// Verifying if the instaltion was successful, by running the Helm chart
// tests interactively.
i.logger.Debug("Verifying the Helm chart release")
if err = hc.Verify(); err != nil {
return err
}

if !i.flags.DryRun {
i.logger.Debug("Running post-deploy hook script...")
if err = hook.PostDeploy(i.values); err != nil {
return err
}
} else {
i.logger.Debug("Skipping post-deploy hook script (dry-run)")
}

i.logger.Info("Helm chart installed!")
return nil
}

func NewInstaller() *Installer {
return &Installer{}
// NewInstaller instantiates a new installer for the given dependency.
func NewInstaller(
logger *slog.Logger,
f *flags.Flags,
kube *k8s.Kube,
cfs *chartfs.ChartFS,
dep *config.Dependency,
) *Installer {
return &Installer{
logger: dep.LoggerWith(logger),
flags: f,
kube: kube,
cfs: cfs,
dep: dep,
}
}
86 changes: 20 additions & 66 deletions pkg/subcmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@ package subcmd
import (
"fmt"
"log/slog"
"os"

"github.com/redhat-appstudio/rhtap-cli/pkg/chartfs"
"github.com/redhat-appstudio/rhtap-cli/pkg/config"
"github.com/redhat-appstudio/rhtap-cli/pkg/deployer"
"github.com/redhat-appstudio/rhtap-cli/pkg/engine"
"github.com/redhat-appstudio/rhtap-cli/pkg/flags"
"github.com/redhat-appstudio/rhtap-cli/pkg/hooks"
"github.com/redhat-appstudio/rhtap-cli/pkg/installer"
"github.com/redhat-appstudio/rhtap-cli/pkg/k8s"

"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/chartutil"
)

// Deploy is the deploy subcommand.
Expand All @@ -25,7 +21,7 @@ type Deploy struct {
cfg *config.Spec // installer configuration
kube *k8s.Kube // kubernetes client

valuesTemplatePath string // path to the values template file
valuesTmplPath string // path to the values template file
}

var _ Interface = &Deploy{}
Expand All @@ -50,17 +46,11 @@ func (d *Deploy) Cmd() *cobra.Command {
// log logger with contextual information.
func (d *Deploy) log() *slog.Logger {
return d.flags.LoggerWith(
d.logger.With("values-template", d.valuesTemplatePath))
d.logger.With("values-template", d.valuesTmplPath))
}

// Complete verifies the object is complete.
func (d *Deploy) Complete(_ []string) error {
if d.cfg == nil {
return fmt.Errorf("configuration is not informed")
}
if d.kube == nil {
return fmt.Errorf("kubernetes client is not informed")
}
return nil
}

Expand All @@ -74,78 +64,42 @@ func (d *Deploy) Validate() error {
)
}

// Run deploys the dependencies listed on the configuration.
func (d *Deploy) Run() error {
cfs := chartfs.NewChartFSForCWD()

d.log().Debug("Loading values template file (from CFS)")
valuesTemplatePayload, err := cfs.ReadFile(d.valuesTemplatePath)
d.log().Debug("Reading values template file")
valuesTmpl, err := cfs.ReadFile(d.valuesTmplPath)
if err != nil {
return err
}

d.log().Debug("Preparing values template context")
variables := engine.NewVariables()
if err := variables.SetInstaller(d.cfg); err != nil {
return err
}
if err := variables.SetOpenShift(d.cmd.Context(), d.kube); err != nil {
return err
return fmt.Errorf("failed to read values template file: %w", err)
}

d.log().Debug("Searching Helm charts from the current directory")
eng := engine.NewEngine(d.kube, string(valuesTemplatePayload))

// Installing each Helm Chart dependency from the configuration.
d.log().Debug("Installing dependencies...")
for _, dep := range d.cfg.Dependencies {
logger := dep.LoggerWith(d.log())

logger.Debug("Loading dependency Helm chart (from CFS)")
chart, err := cfs.GetChartForDep(&dep)
if err != nil {
return err
}

hc, err := deployer.NewHelm(logger, d.flags, d.kube, dep.Namespace, chart)
if err != nil {
return err
}
i := installer.NewInstaller(d.log(), d.flags, d.kube, cfs, &dep)

logger.Debug("Rendering values from template")
valuesBytes, err := eng.Render(variables)
err := i.SetValues(d.cmd.Context(), d.cfg, string(valuesTmpl))
if err != nil {
return err
}

logger.Debug("Preparing rendered values for Helm installation")
values, err := chartutil.ReadValues(valuesBytes)
if err != nil {
return err
if d.flags.Debug {
i.PrintRawValues()
}

hook := hooks.NewHooks(cfs, &dep, os.Stdout, os.Stderr)
logger.Debug("Running pre-deploy hook script...")
if err = hook.PreDeploy(values); err != nil {
if err := i.RenderValues(); err != nil {
return err
}

// Performing the installation, or upgrade, of the Helm chart dependency,
// using the values rendered before hand.
logger.Debug("Installing the Helm chart")
if err = hc.Install(values); err != nil {
return err
}
// Verifying if the instaltion was successful, by running the Helm chart
// tests interactively.
logger.Debug("Verifying the Helm chart release")
if err = hc.Verify(); err != nil {
return err
if d.flags.Debug {
i.PrintValues()
}

logger.Debug("Running post-deploy hook script...")
if err = hook.PostDeploy(values); err != nil {
if err = i.Install(); err != nil {
return err
}
logger.Info("Helm chart installed!")
}

d.log().Info("Deployment complete!")
return nil
}

Expand All @@ -167,6 +121,6 @@ func NewDeploy(
cfg: cfg,
kube: kube,
}
flags.SetValuesTmplFlag(d.cmd.PersistentFlags(), &d.valuesTemplatePath)
flags.SetValuesTmplFlag(d.cmd.PersistentFlags(), &d.valuesTmplPath)
return d
}
Loading