Skip to content

Commit

Permalink
Refactor wrap/unwrap to improve programmatic usage
Browse files Browse the repository at this point in the history
  • Loading branch information
juamedgod committed Feb 9, 2024
1 parent a88316d commit a39245c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 54 deletions.
101 changes: 67 additions & 34 deletions cmd/dt/unwrap/unwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ type Config struct {
Auth Auth
ContainerRegistryAuth Auth

SayYes bool
// Interactive enables interacting with the user
Interactive bool
SayYes bool
}

// Auth defines the authentication information to access the container registry
Expand Down Expand Up @@ -90,6 +92,13 @@ func WithKeepArtifacts(keepArtifacts bool) func(c *Config) {
}
}

// WithInteractive configures the Interactive of the WrapConfig
func WithInteractive(interactive bool) func(c *Config) {
return func(c *Config) {
c.Interactive = interactive
}
}

// ShouldFetchChartArtifacts returns true if the chart artifacts should be fetched
func (c *Config) ShouldFetchChartArtifacts(inputChart string) bool {
if chartutils.IsRemoteChart(inputChart) {
Expand Down Expand Up @@ -204,22 +213,33 @@ func NewConfig(opts ...Option) *Config {
}

// Chart unwraps a Helm chart
func Chart(inputChart, registryURL, pushChartURL string, opts ...Option) error {
func Chart(inputChart, registryURL, pushChartURL string, opts ...Option) (string, error) {
return unwrapChart(inputChart, registryURL, pushChartURL, opts...)
}
func unwrapChart(inputChart, registryURL, pushChartURL string, opts ...Option) error {

func askYesNoQuestion(msg string, cfg *Config) bool {
if cfg.SayYes {
return true
}
if !cfg.Interactive {
return false
}
return widgets.ShowYesNoQuestion(msg)
}

func unwrapChart(inputChart, registryURL, pushChartURL string, opts ...Option) (string, error) {

cfg := NewConfig(opts...)

ctx := cfg.Context
parentLog := cfg.GetLogger()

if registryURL == "" {
return fmt.Errorf("the registry cannot be empty")
return "", fmt.Errorf("the registry cannot be empty")
}
tempDir, err := cfg.GetTemporaryDirectory()
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
return "", fmt.Errorf("failed to create temporary directory: %w", err)
}

l := parentLog.StartSection(fmt.Sprintf("Unwrapping Helm chart %q", inputChart))
Expand All @@ -239,37 +259,41 @@ func unwrapChart(inputChart, registryURL, pushChartURL string, opts ...Option) e
),
)
if err != nil {
return err
return "", err
}

wrap, err := wrapping.Load(chartPath)
if err != nil {
return err
return "", err
}
if err := l.ExecuteStep(fmt.Sprintf("Relocating %q with prefix %q", wrap.ChartDir(), registryURL), func() error {
return relocator.RelocateChartDir(
wrap.ChartDir(), registryURL, relocator.WithLog(l),
relocator.Recursive, relocator.WithAnnotationsKey(cfg.AnnotationsKey),
)
}); err != nil {
return l.Failf("failed to relocate %q: %w", chartPath, err)
return "", l.Failf("failed to relocate %q: %w", chartPath, err)
}
l.Infof("Helm chart relocated successfully")

lenImages := showImagesSummary(wrap, l)
images := getImageList(wrap, l)

if lenImages > 0 && (cfg.SayYes || widgets.ShowYesNoQuestion(l.PrefixText("Do you want to push the wrapped images to the OCI registry?"))) {
if err := l.Section("Pushing Images", func(subLog log.SectionLogger) error {
return pushChartImagesAndVerify(ctx, wrap, cfg)
}); err != nil {
return l.Failf("Failed to push images: %w", err)
if len(images) > 0 {
// If we are not in interactive mode, we do not show the list of images
if cfg.Interactive {
showImagesSummary(images, l)
}
if askYesNoQuestion(l.PrefixText("Do you want to push the wrapped images to the OCI registry?"), cfg) {
if err := l.Section("Pushing Images", func(subLog log.SectionLogger) error {
return pushChartImagesAndVerify(ctx, wrap, NewConfig(append(opts, WithLogger(subLog))...))
}); err != nil {
return "", l.Failf("Failed to push images: %w", err)
}
l.Printf(widgets.TerminalSpacer)
}
l.Printf(widgets.TerminalSpacer)
}
var successMessage = "Helm chart unwrapped successfully"

if cfg.SayYes || widgets.ShowYesNoQuestion(l.PrefixText("Do you want to push the Helm chart to the OCI registry?")) {

if askYesNoQuestion(l.PrefixText("Do you want to push the Helm chart to the OCI registry?"), cfg) {
if pushChartURL == "" {
pushChartURL = registryURL
// we will push the chart to the same registry as the containers
Expand All @@ -286,19 +310,13 @@ func unwrapChart(inputChart, registryURL, pushChartURL string, opts ...Option) e
return pushChart(ctx, wrap, pushChartURL, cfg)
})
}); err != nil {
return l.Failf("Failed to push Helm chart: %w", err)
return "", l.Failf("Failed to push Helm chart: %w", err)
}

l.Infof("Helm chart successfully pushed")

successMessage = fmt.Sprintf(`%s: You can use it now by running "helm install %s --generate-name"`, successMessage, fullChartURL)
return fullChartURL, nil
}

l.Printf(widgets.TerminalSpacer)

parentLog.Successf(successMessage)

return nil
return "", nil
}

func pushChartImagesAndVerify(ctx context.Context, wrap wrapping.Wrap, cfg *Config) error {
Expand Down Expand Up @@ -334,24 +352,28 @@ func pushChartImagesAndVerify(ctx context.Context, wrap wrapping.Wrap, cfg *Conf
return nil
}

func showImagesSummary(wrap wrapping.Lockable, l log.SectionLogger) int {
func getImageList(wrap wrapping.Lockable, l log.SectionLogger) imagelock.ImageList {
lock, err := wrap.GetImagesLock()

if err != nil {
l.Debugf("failed to load list of images: failed to load lock file: %v", err)
return 0
return imagelock.ImageList{}
}
if len(lock.Images) == 0 {
l.Warnf("The bundle does not include any image")
return 0
return imagelock.ImageList{}
}
_ = l.Section(fmt.Sprintf("The wrap includes the following %d images:\n", len(lock.Images)), func(log.SectionLogger) error {
for _, img := range lock.Images {
return lock.Images
}

func showImagesSummary(images imagelock.ImageList, l log.SectionLogger) {
_ = l.Section(fmt.Sprintf("The wrap includes the following %d images:\n", len(images)), func(log.SectionLogger) error {
for _, img := range images {
l.Printf(img.Image)
}
l.Printf(widgets.TerminalSpacer)
return nil
})
return len(lock.Images)
}

func normalizeOCIURL(url string) string {
Expand Down Expand Up @@ -429,15 +451,26 @@ func NewCmd(cfg *config.Config) *cobra.Command {
if err != nil {
return fmt.Errorf("failed to create temporary directory: %v", err)
}
return unwrapChart(inputChart, registryURL, pushChartURL,
fullChartURL, err := unwrapChart(inputChart, registryURL, pushChartURL,
WithLogger(l),
WithSayYes(sayYes),
WithContext(ctx),
WithVersion(version),
WithInteractive(true),
WithInsecure(cfg.Insecure),
WithTempDirectory(tempDir),
WithUsePlainHTTP(cfg.UsePlainHTTP),
)
if err != nil {
return err
}
var successMessage = "Helm chart unwrapped successfully"
if fullChartURL != "" {
successMessage = fmt.Sprintf(`%s: You can use it now by running "helm install %s --generate-name"`, successMessage, fullChartURL)
}
l.Printf(widgets.TerminalSpacer)
l.Successf(successMessage)
return nil
},
}

Expand Down
6 changes: 4 additions & 2 deletions cmd/dt/unwrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func testChartUnwrap(t *testing.T, sb *tu.Sandbox, inputChart string, targetRegi
unwrap.WithAuth(cfg.Auth.Username, cfg.Auth.Password),
unwrap.WithContainerRegistryAuth(cfg.ContainerRegistryAuth.Username, cfg.ContainerRegistryAuth.Password),
}
require.NoError(t, unwrap.Chart(inputChart, targetRegistry, chartTargetRegistry, opts...))
_, err := unwrap.Chart(inputChart, targetRegistry, chartTargetRegistry, opts...)
require.NoError(t, err)
} else {
dt(args...).AssertSuccessMatch(t, "")
}
Expand Down Expand Up @@ -256,7 +257,8 @@ func (suite *CmdSuite) TestUnwrapCommand() {
unwrap.WithSayYes(true),
unwrap.WithContainerRegistryAuth(username, password),
}
require.NoError(unwrap.Chart(wrapDir, targetRegistry, "", opts...))
_, err := unwrap.Chart(wrapDir, targetRegistry, "", opts...)
require.NoError(err)
} else {
dt(args...).AssertSuccessMatch(suite.T(), "")
}
Expand Down
48 changes: 31 additions & 17 deletions cmd/dt/wrap/wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Config struct {
FetchArtifacts bool
Auth Auth
ContainerRegistryAuth Auth
OutputFile string
}

// WithKeepArtifacts configures the KeepArtifacts of the WrapConfig
Expand All @@ -55,6 +56,13 @@ func WithKeepArtifacts(keepArtifacts bool) func(c *Config) {
}
}

// WithOutputFile configures the OutputFile of the WrapConfig
func WithOutputFile(outputFile string) func(c *Config) {
return func(c *Config) {
c.OutputFile = outputFile
}
}

// WithAuth configures the Auth of the wrap Config
func WithAuth(username, password string) func(c *Config) {
return func(c *Config) {
Expand Down Expand Up @@ -195,8 +203,8 @@ func NewConfig(opts ...Option) *Config {
}

// Chart wraps a Helm chart
func Chart(inputPath string, outputFile string, opts ...Option) error {
return wrapChart(inputPath, outputFile, opts...)
func Chart(inputPath string, opts ...Option) (string, error) {
return wrapChart(inputPath, opts...)
}

// ResolveInputChartPath resolves the input chart into a local uncompressed chart path
Expand Down Expand Up @@ -344,7 +352,7 @@ func pullImages(wrap wrapping.Wrap, cfg *Config) error {
return nil
}

func wrapChart(inputPath string, outputFile string, opts ...Option) error {
func wrapChart(inputPath string, opts ...Option) (string, error) {
cfg := NewConfig(opts...)

ctx := cfg.Context
Expand All @@ -356,33 +364,35 @@ func wrapChart(inputPath string, outputFile string, opts ...Option) error {

chartPath, err := ResolveInputChartPath(inputPath, subCfg)
if err != nil {
return err
return "", err
}
tmpDir, err := cfg.GetTemporaryDirectory()
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
return "", fmt.Errorf("failed to create temporary directory: %w", err)
}
wrap, err := wrapping.Create(chartPath, filepath.Join(tmpDir, "wrap"),
chartutils.WithAnnotationsKey(cfg.AnnotationsKey),
)
if err != nil {
return l.Failf("failed to create wrap: %v", err)
return "", l.Failf("failed to create wrap: %v", err)
}

chart := wrap.Chart()

if cfg.ShouldFetchChartArtifacts(inputPath) {
chartURL := fmt.Sprintf("%s:%s", inputPath, chart.Version())
if err := fetchArtifacts(chartURL, filepath.Join(wrap.RootDir(), artifacts.HelmChartArtifactMetadataDir), subCfg); err != nil {
return err
return "", err
}
}

chartRoot := chart.RootDir()
if err := validateWrapLock(wrap, subCfg); err != nil {
return err
return "", err
}

outputFile := cfg.OutputFile

if outputFile == "" {
outputBaseName := fmt.Sprintf("%s-%s.wrap.tgz", chart.Name(), chart.Version())
if outputFile, err = filepath.Abs(outputBaseName); err != nil {
Expand All @@ -391,7 +401,7 @@ func wrapChart(inputPath string, outputFile string, opts ...Option) error {
}
}
if err := pullImages(wrap, subCfg); err != nil {
return err
return "", err
}

if cfg.Carvelize {
Expand All @@ -402,7 +412,7 @@ func wrapChart(inputPath string, outputFile string, opts ...Option) error {
chartutils.WithLog(childLog),
)
}); err != nil {
return l.Failf("%w", err)
return "", l.Failf("%w", err)
}
l.Infof("Carvel bundle created successfully")
}
Expand All @@ -415,14 +425,11 @@ func wrapChart(inputPath string, outputFile string, opts ...Option) error {
})
},
); err != nil {
return l.Failf("failed to wrap Helm chart: %w", err)
return "", l.Failf("failed to wrap Helm chart: %w", err)
}
l.Infof("Compressed into %q", outputFile)

l.Printf(widgets.TerminalSpacer)

parentLog.Successf("Helm chart wrapped into %q", outputFile)
return nil
return outputFile, nil
}

// NewCmd builds a new wrap command
Expand Down Expand Up @@ -460,20 +467,27 @@ This command will pull all the container images and wrap it into a single tarbal

parentLog := cfg.Logger()

if err := wrapChart(chartPath, outputFile,
wrappedChart, err := wrapChart(chartPath,
WithLogger(parentLog),
WithAnnotationsKey(cfg.AnnotationsKey), WithContext(ctx),
WithPlatforms(platforms), WithVersion(version),
WithFetchArtifacts(fetchArtifacts), WithCarvelize(carvelize),
WithUsePlainHTTP(cfg.UsePlainHTTP), WithInsecure(cfg.Insecure),
WithOutputFile(outputFile),
WithTempDirectory(tmpDir),
); err != nil {
)

if err != nil {
if _, ok := err.(*log.LoggedError); ok {
// We already logged it, lets be less verbose
return fmt.Errorf("failed to wrap Helm chart: %v", err)
}
return err
}

parentLog.Printf(widgets.TerminalSpacer)
parentLog.Successf("Helm chart wrapped into %q", wrappedChart)

return nil
},
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/dt/wrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,11 @@ func testChartWrap(t *testing.T, sb *tu.Sandbox, inputChart string, expectedLock
wrap.WithCarvelize(cfg.GenerateCarvelBundle),
wrap.WithFetchArtifacts(cfg.FetchArtifacts),
wrap.WithAuth(cfg.Auth.Username, cfg.Auth.Password),
wrap.WithOutputFile(expectedWrapFile),
wrap.WithContainerRegistryAuth(cfg.ContainerRegistryAuth.Username, cfg.ContainerRegistryAuth.Password),
}
require.NoError(t, wrap.Chart(inputChart, expectedWrapFile, opts...))
_, err := wrap.Chart(inputChart, opts...)
require.NoError(t, err)
} else {
if len(cfg.Images) == 0 {
dt(args...).AssertSuccessMatch(t, "No images found in Images.lock")
Expand Down

0 comments on commit a39245c

Please sign in to comment.