diff --git a/pkg/build/build.go b/pkg/build/build.go index cf9ebd43..051ec1a4 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -6,31 +6,32 @@ import ( "path/filepath" "time" - "github.com/suse-edge/edge-image-builder/pkg/combustion" "github.com/suse-edge/edge-image-builder/pkg/image" "github.com/suse-edge/edge-image-builder/pkg/log" ) -type configureCombustion func(ctx *image.Context) error +type imageConfigurator interface { + Configure(ctx *image.Context) error +} type Builder struct { - context *image.Context - configureCombustion configureCombustion + context *image.Context + imageConfigurator imageConfigurator } -func NewBuilder(ctx *image.Context) *Builder { +func NewBuilder(ctx *image.Context, imageConfigurator imageConfigurator) *Builder { return &Builder{ - context: ctx, - configureCombustion: combustion.Configure, + context: ctx, + imageConfigurator: imageConfigurator, } } func (b *Builder) Build() error { log.Audit("Generating image customization components...") - if err := b.configureCombustion(b.context); err != nil { + if err := b.imageConfigurator.Configure(b.context); err != nil { log.Audit("Error configuring customization components, check the logs under the build directory for more information.") - return fmt.Errorf("configuring combustion: %w", err) + return fmt.Errorf("configuring image: %w", err) } switch b.context.ImageDefinition.Image.ImageType { diff --git a/pkg/cli/build/build.go b/pkg/cli/build/build.go index 45fa2485..0e1c602a 100644 --- a/pkg/cli/build/build.go +++ b/pkg/cli/build/build.go @@ -85,7 +85,12 @@ func Run(_ *cli.Context) error { appendHelm(ctx) - if cmdErr = bootstrapDependencyServices(ctx, rootBuildDir); cmdErr != nil { + combustionClient := &combustion.Combustion{ + NetworkConfigGenerator: network.ConfigGenerator{}, + NetworkConfiguratorInstaller: network.ConfiguratorInstaller{}, + } + + if cmdErr = bootstrapDependencyServices(ctx, rootBuildDir, combustionClient); cmdErr != nil { cmd.LogError(cmdErr, checkBuildLogMessage) os.Exit(1) } @@ -97,7 +102,7 @@ func Run(_ *cli.Context) error { } }() - builder := build.NewBuilder(ctx) + builder := build.NewBuilder(ctx, combustionClient) if err = builder.Build(); err != nil { zap.S().Fatalf("An error occurred building the image: %s", err) } @@ -154,13 +159,11 @@ func parseImageDefinition(configDir, definitionFile string) (*image.Definition, // Assembles the image build context with user-provided values and implementation defaults. func buildContext(buildDir, combustionDir, artefactsDir, configDir string, imageDefinition *image.Definition) *image.Context { ctx := &image.Context{ - ImageConfigDir: configDir, - BuildDir: buildDir, - CombustionDir: combustionDir, - ArtefactsDir: artefactsDir, - ImageDefinition: imageDefinition, - NetworkConfigGenerator: network.ConfigGenerator{}, - NetworkConfiguratorInstaller: network.ConfiguratorInstaller{}, + ImageConfigDir: configDir, + BuildDir: buildDir, + CombustionDir: combustionDir, + ArtefactsDir: artefactsDir, + ImageDefinition: imageDefinition, } return ctx } @@ -242,7 +245,7 @@ func appendHelm(ctx *image.Context) { } // If the image definition requires it, starts the necessary services, returning an error in the event of failure. -func bootstrapDependencyServices(ctx *image.Context, rootDir string) *cmd.Error { +func bootstrapDependencyServices(ctx *image.Context, rootDir string, combustionClient *combustion.Combustion) *cmd.Error { if !combustion.SkipRPMComponent(ctx) { p, err := podman.New(ctx.BuildDir) if err != nil { @@ -256,14 +259,13 @@ func bootstrapDependencyServices(ctx *image.Context, rootDir string) *cmd.Error imgType := ctx.ImageDefinition.Image.ImageType baseBuilder := resolver.NewTarballBuilder(ctx.BuildDir, imgPath, imgType, p) - rpmResolver := resolver.New(ctx.BuildDir, p, baseBuilder, "") - ctx.RPMResolver = rpmResolver - ctx.RPMRepoCreator = rpm.NewRepoCreator(ctx.BuildDir) + combustionClient.RPMResolver = resolver.New(ctx.BuildDir, p, baseBuilder, "") + combustionClient.RPMRepoCreator = rpm.NewRepoCreator(ctx.BuildDir) } if combustion.IsEmbeddedArtifactRegistryConfigured(ctx) { certsDir := filepath.Join(ctx.ImageConfigDir, combustion.K8sDir, combustion.HelmDir, combustion.CertsDir) - ctx.HelmClient = helm.New(ctx.BuildDir, certsDir) + combustionClient.HelmClient = helm.New(ctx.BuildDir, certsDir) } if ctx.ImageDefinition.Kubernetes.Version != "" { @@ -275,8 +277,8 @@ func bootstrapDependencyServices(ctx *image.Context, rootDir string) *cmd.Error } } - ctx.KubernetesScriptDownloader = kubernetes.ScriptDownloader{} - ctx.KubernetesArtefactDownloader = kubernetes.ArtefactDownloader{ + combustionClient.KubernetesScriptDownloader = kubernetes.ScriptDownloader{} + combustionClient.KubernetesArtefactDownloader = kubernetes.ArtefactDownloader{ Cache: c, } } diff --git a/pkg/combustion/combustion.go b/pkg/combustion/combustion.go index 6bded40b..b66b0c1b 100644 --- a/pkg/combustion/combustion.go +++ b/pkg/combustion/combustion.go @@ -3,6 +3,7 @@ package combustion import ( "errors" "fmt" + "io" "io/fs" "os" "path/filepath" @@ -21,9 +22,44 @@ import ( // Result can also be an empty slice or nil if this is not necessary. type configureComponent func(context *image.Context) ([]string, error) +type networkConfigGenerator interface { + GenerateNetworkConfig(configDir, outputDir string, outputWriter io.Writer) error +} + +type networkConfiguratorInstaller interface { + InstallConfigurator(sourcePath, installPath string) error +} + +type kubernetesScriptDownloader interface { + DownloadInstallScript(distribution, destinationPath string) (string, error) +} + +type kubernetesArtefactDownloader interface { + DownloadRKE2Artefacts(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error + DownloadK3sArtefacts(arch image.Arch, version, installPath, imagesPath string) error +} + +type rpmResolver interface { + Resolve(packages *image.Packages, localRPMConfig *image.LocalRPMConfig, outputDir string) (rpmDirPath string, pkgList []string, err error) +} + +type rpmRepoCreator interface { + Create(path string) error +} + +type Combustion struct { + NetworkConfigGenerator networkConfigGenerator + NetworkConfiguratorInstaller networkConfiguratorInstaller + KubernetesScriptDownloader kubernetesScriptDownloader + KubernetesArtefactDownloader kubernetesArtefactDownloader + RPMResolver rpmResolver + RPMRepoCreator rpmRepoCreator + HelmClient image.HelmClient +} + // Configure iterates over all separate Combustion components and configures them independently. // If all of those are successful, the Combustion script is assembled and written to the file system. -func Configure(ctx *image.Context) error { +func (c *Combustion) Configure(ctx *image.Context) error { var combustionScripts []string // EIB Combustion script prefix ranges: @@ -59,7 +95,7 @@ func Configure(ctx *image.Context) error { }, { name: networkComponentName, - runnable: configureNetwork, + runnable: c.configureNetwork, }, { name: groupsComponentName, @@ -75,7 +111,7 @@ func Configure(ctx *image.Context) error { }, { name: rpmComponentName, - runnable: configureRPMs, + runnable: c.configureRPMs, }, { name: systemdComponentName, @@ -91,7 +127,7 @@ func Configure(ctx *image.Context) error { }, { name: registryComponentName, - runnable: configureRegistry, + runnable: c.configureRegistry, }, { name: keymapComponentName, @@ -99,7 +135,7 @@ func Configure(ctx *image.Context) error { }, { name: k8sComponentName, - runnable: configureKubernetes, + runnable: c.configureKubernetes, }, { name: certsComponentName, diff --git a/pkg/combustion/kubernetes.go b/pkg/combustion/kubernetes.go index 9f7f721b..3dcc569b 100644 --- a/pkg/combustion/kubernetes.go +++ b/pkg/combustion/kubernetes.go @@ -50,7 +50,7 @@ var ( k8sVIPManifest string ) -func configureKubernetes(ctx *image.Context) ([]string, error) { +func (c *Combustion) configureKubernetes(ctx *image.Context) ([]string, error) { version := ctx.ImageDefinition.Kubernetes.Version if version == "" { @@ -58,7 +58,7 @@ func configureKubernetes(ctx *image.Context) ([]string, error) { return nil, nil } - configureFunc := kubernetesConfigurator(version) + configureFunc := c.kubernetesConfigurator(version) if configureFunc == nil { log.AuditComponentFailed(k8sComponentName) return nil, fmt.Errorf("cannot configure kubernetes version: %s", version) @@ -102,21 +102,21 @@ func configureKubernetes(ctx *image.Context) ([]string, error) { return []string{script}, nil } -func kubernetesConfigurator(version string) func(*image.Context, *kubernetes.Cluster) (string, error) { +func (c *Combustion) kubernetesConfigurator(version string) func(*image.Context, *kubernetes.Cluster) (string, error) { switch { case strings.Contains(version, image.KubernetesDistroRKE2): - return configureRKE2 + return c.configureRKE2 case strings.Contains(version, image.KubernetesDistroK3S): - return configureK3S + return c.configureK3S default: return nil } } -func downloadKubernetesInstallScript(ctx *image.Context, distribution string) (string, error) { +func (c *Combustion) downloadKubernetesInstallScript(ctx *image.Context, distribution string) (string, error) { path := kubernetesArtefactsPath(ctx) - installScript, err := ctx.KubernetesScriptDownloader.DownloadInstallScript(distribution, path) + installScript, err := c.KubernetesScriptDownloader.DownloadInstallScript(distribution, path) if err != nil { return "", fmt.Errorf("downloading install script: %w", err) } @@ -124,15 +124,15 @@ func downloadKubernetesInstallScript(ctx *image.Context, distribution string) (s return prependArtefactPath(filepath.Join(K8sDir, installScript)), nil } -func configureK3S(ctx *image.Context, cluster *kubernetes.Cluster) (string, error) { +func (c *Combustion) configureK3S(ctx *image.Context, cluster *kubernetes.Cluster) (string, error) { zap.S().Info("Configuring K3s cluster") - installScript, err := downloadKubernetesInstallScript(ctx, image.KubernetesDistroK3S) + installScript, err := c.downloadKubernetesInstallScript(ctx, image.KubernetesDistroK3S) if err != nil { return "", fmt.Errorf("downloading k3s install script: %w", err) } - binaryPath, imagesPath, err := downloadK3sArtefacts(ctx) + binaryPath, imagesPath, err := c.downloadK3sArtefacts(ctx) if err != nil { return "", fmt.Errorf("downloading k3s artefacts: %w", err) } @@ -178,7 +178,7 @@ func configureK3S(ctx *image.Context, cluster *kubernetes.Cluster) (string, erro return storeKubernetesInstaller(ctx, "multi-node-k3s", k3sMultiNodeInstaller, templateValues) } -func downloadK3sArtefacts(ctx *image.Context) (binaryPath, imagesPath string, err error) { +func (c *Combustion) downloadK3sArtefacts(ctx *image.Context) (binaryPath, imagesPath string, err error) { imagesPath = filepath.Join(K8sDir, k8sImagesDir) imagesDestination := filepath.Join(ctx.ArtefactsDir, imagesPath) if err = os.MkdirAll(imagesDestination, os.ModePerm); err != nil { @@ -191,7 +191,7 @@ func downloadK3sArtefacts(ctx *image.Context) (binaryPath, imagesPath string, er return "", "", fmt.Errorf("creating kubernetes install dir: %w", err) } - if err = ctx.KubernetesArtefactDownloader.DownloadK3sArtefacts( + if err = c.KubernetesArtefactDownloader.DownloadK3sArtefacts( ctx.ImageDefinition.Image.Arch, ctx.ImageDefinition.Kubernetes.Version, installDestination, @@ -219,15 +219,15 @@ func downloadK3sArtefacts(ctx *image.Context) (binaryPath, imagesPath string, er return prependArtefactPath(binaryPath), prependArtefactPath(imagesPath), nil } -func configureRKE2(ctx *image.Context, cluster *kubernetes.Cluster) (string, error) { +func (c *Combustion) configureRKE2(ctx *image.Context, cluster *kubernetes.Cluster) (string, error) { zap.S().Info("Configuring RKE2 cluster") - installScript, err := downloadKubernetesInstallScript(ctx, image.KubernetesDistroRKE2) + installScript, err := c.downloadKubernetesInstallScript(ctx, image.KubernetesDistroRKE2) if err != nil { return "", fmt.Errorf("downloading RKE2 install script: %w", err) } - installPath, imagesPath, err := downloadRKE2Artefacts(ctx, cluster) + installPath, imagesPath, err := c.downloadRKE2Artefacts(ctx, cluster) if err != nil { return "", fmt.Errorf("downloading RKE2 artefacts: %w", err) } @@ -280,7 +280,7 @@ func storeKubernetesInstaller(ctx *image.Context, templateName, templateContents return k8sInstallScript, nil } -func downloadRKE2Artefacts(ctx *image.Context, cluster *kubernetes.Cluster) (installPath, imagesPath string, err error) { +func (c *Combustion) downloadRKE2Artefacts(ctx *image.Context, cluster *kubernetes.Cluster) (installPath, imagesPath string, err error) { cni, multusEnabled, err := cluster.ExtractCNI() if err != nil { return "", "", fmt.Errorf("extracting CNI from cluster config: %w", err) @@ -298,7 +298,7 @@ func downloadRKE2Artefacts(ctx *image.Context, cluster *kubernetes.Cluster) (ins return "", "", fmt.Errorf("creating kubernetes install dir: %w", err) } - if err = ctx.KubernetesArtefactDownloader.DownloadRKE2Artefacts( + if err = c.KubernetesArtefactDownloader.DownloadRKE2Artefacts( ctx.ImageDefinition.Image.Arch, ctx.ImageDefinition.Kubernetes.Version, cni, diff --git a/pkg/combustion/kubernetes_integration_test.go b/pkg/combustion/kubernetes_integration_test.go index 077c4d4c..1db4871d 100644 --- a/pkg/combustion/kubernetes_integration_test.go +++ b/pkg/combustion/kubernetes_integration_test.go @@ -59,14 +59,17 @@ func TestConfigureKubernetes_SuccessfulRKE2ServerWithManifests(t *testing.T) { APIHost: "api.cluster01.hosted.on.edge.suse.com", }, } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return "install-k8s.sh", nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return "install-k8s.sh", nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { - return nil + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { + return nil + }, }, } @@ -84,7 +87,7 @@ func TestConfigureKubernetes_SuccessfulRKE2ServerWithManifests(t *testing.T) { err := fileio.CopyFile(localSampleManifestPath, filepath.Join(localManifestsSrcDir, "sample-crd.yaml"), fileio.NonExecutablePerms) require.NoError(t, err) - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.NoError(t, err) require.Len(t, scripts, 1) diff --git a/pkg/combustion/kubernetes_test.go b/pkg/combustion/kubernetes_test.go index dfd57c69..22a4abff 100644 --- a/pkg/combustion/kubernetes_test.go +++ b/pkg/combustion/kubernetes_test.go @@ -60,7 +60,9 @@ func TestConfigureKubernetes_Skipped(t *testing.T) { ImageDefinition: &image.Definition{}, } - scripts, err := configureKubernetes(ctx) + var c Combustion + + scripts, err := c.configureKubernetes(ctx) require.NoError(t, err) assert.Nil(t, scripts) } @@ -74,7 +76,9 @@ func TestConfigureKubernetes_UnsupportedVersion(t *testing.T) { }, } - scripts, err := configureKubernetes(ctx) + var c Combustion + + scripts, err := c.configureKubernetes(ctx) require.Error(t, err) assert.EqualError(t, err, "cannot configure kubernetes version: v1.29.0") assert.Nil(t, scripts) @@ -87,13 +91,16 @@ func TestConfigureKubernetes_ScriptInstallerErrorK3s(t *testing.T) { ctx.ImageDefinition.Kubernetes = image.Kubernetes{ Version: "v1.29.0+k3s1", } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return "", fmt.Errorf("some error") + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return "", fmt.Errorf("some error") + }, }, } - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.Error(t, err) assert.EqualError(t, err, "configuring kubernetes components: downloading k3s install script: downloading install script: some error") assert.Nil(t, scripts) @@ -106,13 +113,16 @@ func TestConfigureKubernetes_ScriptInstallerErrorRKE2(t *testing.T) { ctx.ImageDefinition.Kubernetes = image.Kubernetes{ Version: "v1.29.0+rke2r1", } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return "", fmt.Errorf("some error") + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return "", fmt.Errorf("some error") + }, }, } - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.Error(t, err) assert.EqualError(t, err, "configuring kubernetes components: downloading RKE2 install script: downloading install script: some error") assert.Nil(t, scripts) @@ -125,18 +135,21 @@ func TestConfigureKubernetes_ArtefactDownloaderErrorK3s(t *testing.T) { ctx.ImageDefinition.Kubernetes = image.Kubernetes{ Version: "v1.29.0+k3s1", } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadK3sArtefacts: func(arch image.Arch, version string, installPath, imagesPath string) error { - return fmt.Errorf("some error") + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadK3sArtefacts: func(arch image.Arch, version string, installPath, imagesPath string) error { + return fmt.Errorf("some error") + }, }, } - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.Error(t, err) assert.EqualError(t, err, "configuring kubernetes components: downloading k3s artefacts: downloading artefacts: some error") assert.Nil(t, scripts) @@ -149,18 +162,21 @@ func TestConfigureKubernetes_ArtefactDownloaderErrorRKE2(t *testing.T) { ctx.ImageDefinition.Kubernetes = image.Kubernetes{ Version: "v1.29.0+rke2r1", } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { - return fmt.Errorf("some error") + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { + return fmt.Errorf("some error") + }, }, } - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.Error(t, err) assert.EqualError(t, err, "configuring kubernetes components: downloading RKE2 artefacts: downloading artefacts: some error") assert.Nil(t, scripts) @@ -177,19 +193,22 @@ func TestConfigureKubernetes_SuccessfulSingleNodeK3sCluster(t *testing.T) { APIHost: "api.cluster01.hosted.on.edge.suse.com", }, } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadK3sArtefacts: func(arch image.Arch, version string, installPath, imagesPath string) error { - binary := filepath.Join(installPath, "cool-k3s-binary") - return os.WriteFile(binary, nil, os.ModePerm) + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadK3sArtefacts: func(arch image.Arch, version string, installPath, imagesPath string) error { + binary := filepath.Join(installPath, "cool-k3s-binary") + return os.WriteFile(binary, nil, os.ModePerm) + }, }, } - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.NoError(t, err) require.Len(t, scripts, 1) @@ -256,15 +275,18 @@ func TestConfigureKubernetes_SuccessfulMultiNodeK3sCluster(t *testing.T) { }, }, } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadK3sArtefacts: func(arch image.Arch, version, installPath, imagesPath string) error { - binary := filepath.Join(installPath, "cool-k3s-binary") - return os.WriteFile(binary, nil, os.ModePerm) + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadK3sArtefacts: func(arch image.Arch, version, installPath, imagesPath string) error { + binary := filepath.Join(installPath, "cool-k3s-binary") + return os.WriteFile(binary, nil, os.ModePerm) + }, }, } @@ -282,7 +304,7 @@ func TestConfigureKubernetes_SuccessfulMultiNodeK3sCluster(t *testing.T) { require.NoError(t, os.MkdirAll(configDir, os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(configDir, "server.yaml"), b, os.ModePerm)) - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.NoError(t, err) require.Len(t, scripts, 1) @@ -374,18 +396,21 @@ func TestConfigureKubernetes_SuccessfulSingleNodeRKE2Cluster(t *testing.T) { APIHost: "api.cluster01.hosted.on.edge.suse.com", }, } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { - return nil + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { + return nil + }, }, } - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.NoError(t, err) require.Len(t, scripts, 1) @@ -449,14 +474,17 @@ func TestConfigureKubernetes_SuccessfulMultiNodeRKE2Cluster(t *testing.T) { }, }, } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { - return nil + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error { + return nil + }, }, } @@ -475,7 +503,7 @@ func TestConfigureKubernetes_SuccessfulMultiNodeRKE2Cluster(t *testing.T) { require.NoError(t, os.MkdirAll(configDir, os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(configDir, "server.yaml"), b, os.ModePerm)) - scripts, err := configureKubernetes(ctx) + scripts, err := c.configureKubernetes(ctx) require.NoError(t, err) require.Len(t, scripts, 1) @@ -556,23 +584,27 @@ func TestConfigureKubernetes_InvalidManifestURL(t *testing.T) { ctx.ImageDefinition.Kubernetes = image.Kubernetes{ Version: "v1.29.0+rke2r1", } - ctx.KubernetesScriptDownloader = mockKubernetesScriptDownloader{ - downloadScript: func(distribution, destPath string) (string, error) { - return kubernetesScriptInstaller, nil - }, - } - ctx.KubernetesArtefactDownloader = mockKubernetesArtefactDownloader{ - downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installpath, imagesPath string) error { - return nil - }, - } ctx.ImageDefinition.Kubernetes.Manifests.URLs = []string{ "k8s.io/examples/application/nginx-app.yaml", } + + c := Combustion{ + KubernetesScriptDownloader: mockKubernetesScriptDownloader{ + downloadScript: func(distribution, destPath string) (string, error) { + return kubernetesScriptInstaller, nil + }, + }, + KubernetesArtefactDownloader: mockKubernetesArtefactDownloader{ + downloadRKE2Artefacts: func(arch image.Arch, version, cni string, multusEnabled bool, installpath, imagesPath string) error { + return nil + }, + }, + } + k8sCombDir := filepath.Join(ctx.CombustionDir, K8sDir) require.NoError(t, os.Mkdir(k8sCombDir, os.ModePerm)) - _, err := configureKubernetes(ctx) + _, err := c.configureKubernetes(ctx) require.ErrorContains(t, err, "configuring kubernetes manifests: downloading manifests to combustion dir: downloading manifest 'k8s.io/examples/application/nginx-app.yaml': executing request: Get \"k8s.io/examples/application/nginx-app.yaml\": unsupported protocol scheme \"\"") } diff --git a/pkg/combustion/network.go b/pkg/combustion/network.go index 480c3c4b..2910604c 100644 --- a/pkg/combustion/network.go +++ b/pkg/combustion/network.go @@ -49,7 +49,7 @@ var configureNetworkScript string // │ └── host_config.yaml // ├── nmc // └── 05-configure-network.sh -func configureNetwork(ctx *image.Context) (scripts []string, err error) { +func (c *Combustion) configureNetwork(ctx *image.Context) (scripts []string, err error) { zap.S().Info("Configuring network component...") if !isComponentConfigured(ctx, networkConfigDir) { @@ -71,7 +71,7 @@ func configureNetwork(ctx *image.Context) (scripts []string, err error) { return nil, fmt.Errorf("network directory is present but empty") } - if err = installNetworkConfigurator(ctx); err != nil { + if err = c.installNetworkConfigurator(ctx); err != nil { return nil, fmt.Errorf("installing configurator: %w", err) } @@ -88,7 +88,7 @@ func configureNetwork(ctx *image.Context) (scripts []string, err error) { return nil, fmt.Errorf("copying custom network script: %w", err) } - if err = generateNetworkConfig(ctx); err != nil { + if err = c.generateNetworkConfig(ctx); err != nil { return nil, fmt.Errorf("generating network config: %w", err) } @@ -99,7 +99,7 @@ func configureNetwork(ctx *image.Context) (scripts []string, err error) { return scripts, nil } -func generateNetworkConfig(ctx *image.Context) error { +func (c *Combustion) generateNetworkConfig(ctx *image.Context) error { const networkConfigLogFile = "network-config.log" logFilename := filepath.Join(ctx.BuildDir, networkConfigLogFile) @@ -117,14 +117,14 @@ func generateNetworkConfig(ctx *image.Context) error { configDir := generateComponentPath(ctx, networkConfigDir) outputDir := filepath.Join(ctx.CombustionDir, networkConfigDir) - return ctx.NetworkConfigGenerator.GenerateNetworkConfig(configDir, outputDir, logFile) + return c.NetworkConfigGenerator.GenerateNetworkConfig(configDir, outputDir, logFile) } -func installNetworkConfigurator(ctx *image.Context) error { +func (c *Combustion) installNetworkConfigurator(ctx *image.Context) error { sourcePath := "/usr/bin/nmc" installPath := filepath.Join(ctx.CombustionDir, nmcExecutable) - return ctx.NetworkConfiguratorInstaller.InstallConfigurator(sourcePath, installPath) + return c.NetworkConfiguratorInstaller.InstallConfigurator(sourcePath, installPath) } func writeNetworkConfigurationScript(scriptPath string) error { diff --git a/pkg/combustion/network_test.go b/pkg/combustion/network_test.go index 44bbd3d7..6d39bbd7 100644 --- a/pkg/combustion/network_test.go +++ b/pkg/combustion/network_test.go @@ -51,7 +51,9 @@ func TestConfigureNetwork_NotConfigured(t *testing.T) { ctx, teardown := setupContext(t) defer teardown() - scripts, err := configureNetwork(ctx) + var c Combustion + + scripts, err := c.configureNetwork(ctx) require.NoError(t, err) assert.Nil(t, scripts) } @@ -63,7 +65,9 @@ func TestConfigureNetwork_EmptyDirectory(t *testing.T) { networkDir := filepath.Join(ctx.ImageConfigDir, networkConfigDir) require.NoError(t, os.Mkdir(networkDir, 0o700)) - scripts, err := configureNetwork(ctx) + var c Combustion + + scripts, err := c.configureNetwork(ctx) require.Error(t, err) assert.EqualError(t, err, "network directory is present but empty") assert.Nil(t, scripts) @@ -125,10 +129,12 @@ func TestConfigureNetwork(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx.NetworkConfigGenerator = test.configGenerator - ctx.NetworkConfiguratorInstaller = test.configuratorInstaller + c := Combustion{ + NetworkConfigGenerator: test.configGenerator, + NetworkConfiguratorInstaller: test.configuratorInstaller, + } - scripts, err := configureNetwork(ctx) + scripts, err := c.configureNetwork(ctx) if test.expectedErr != "" { require.Error(t, err) @@ -148,9 +154,11 @@ func TestConfigureNetwork_CustomScript(t *testing.T) { ctx, teardown := setupContext(t) defer teardown() - ctx.NetworkConfiguratorInstaller = mockNetworkConfiguratorInstaller{ - installConfiguratorFunc: func(sourcePath, installPath string) error { - return nil + c := Combustion{ + NetworkConfiguratorInstaller: mockNetworkConfiguratorInstaller{ + installConfiguratorFunc: func(sourcePath, installPath string) error { + return nil + }, }, } @@ -162,7 +170,7 @@ func TestConfigureNetwork_CustomScript(t *testing.T) { require.NoError(t, os.WriteFile(customScriptPath, customScriptContents, 0o600)) - scripts, err := configureNetwork(ctx) + scripts, err := c.configureNetwork(ctx) require.NoError(t, err) assert.Equal(t, []string{networkConfigScriptName}, scripts) diff --git a/pkg/combustion/registry.go b/pkg/combustion/registry.go index bfba8659..247accfc 100644 --- a/pkg/combustion/registry.go +++ b/pkg/combustion/registry.go @@ -20,7 +20,6 @@ import ( ) const ( - haulerManifestYamlName = "hauler-manifest.yaml" registryScriptName = "26-embedded-registry.sh" registryTarSuffix = "registry.tar.zst" registryComponentName = "embedded artifact registry" @@ -43,13 +42,13 @@ var ( k8sRegistryMirrors string ) -func configureRegistry(ctx *image.Context) ([]string, error) { +func (c *Combustion) configureRegistry(ctx *image.Context) ([]string, error) { if !IsEmbeddedArtifactRegistryConfigured(ctx) { log.AuditComponentSkipped(registryComponentName) return nil, nil } - configured, err := configureEmbeddedArtifactRegistry(ctx) + configured, err := c.configureEmbeddedArtifactRegistry(ctx) if err != nil { log.AuditComponentFailed(registryComponentName) return nil, fmt.Errorf("configuring embedded artifact registry: %w", err) @@ -203,8 +202,8 @@ func writeRegistryMirrors(ctx *image.Context, hostnames []string) error { return nil } -func configureEmbeddedArtifactRegistry(ctx *image.Context) (bool, error) { - helmCharts, err := parseHelmCharts(ctx) +func (c *Combustion) configureEmbeddedArtifactRegistry(ctx *image.Context) (bool, error) { + helmCharts, err := c.parseHelmCharts(ctx) if err != nil { return false, fmt.Errorf("parsing helm charts: %w", err) } @@ -289,7 +288,7 @@ func parseManifests(ctx *image.Context) ([]string, error) { return registry.ManifestImages(ctx.ImageDefinition.Kubernetes.Manifests.URLs, manifestSrcDir) } -func parseHelmCharts(ctx *image.Context) ([]*registry.HelmChart, error) { +func (c *Combustion) parseHelmCharts(ctx *image.Context) ([]*registry.HelmChart, error) { if len(ctx.ImageDefinition.Kubernetes.Helm.Charts) == 0 { return nil, nil } @@ -305,7 +304,7 @@ func parseHelmCharts(ctx *image.Context) ([]*registry.HelmChart, error) { helmValuesDir := filepath.Join(ctx.ImageConfigDir, K8sDir, HelmDir, ValuesDir) - return registry.HelmCharts(&ctx.ImageDefinition.Kubernetes.Helm, helmValuesDir, buildDir, ctx.ImageDefinition.Kubernetes.Version, ctx.HelmClient) + return registry.HelmCharts(&ctx.ImageDefinition.Kubernetes.Helm, helmValuesDir, buildDir, ctx.ImageDefinition.Kubernetes.Version, c.HelmClient) } func storeHelmCharts(ctx *image.Context, helmCharts []*registry.HelmChart) error { diff --git a/pkg/combustion/rpm.go b/pkg/combustion/rpm.go index bef961d1..9e37644b 100644 --- a/pkg/combustion/rpm.go +++ b/pkg/combustion/rpm.go @@ -26,7 +26,7 @@ const ( //go:embed templates/10-rpm-install.sh.tpl var installRPMsScript string -func configureRPMs(ctx *image.Context) ([]string, error) { +func (c *Combustion) configureRPMs(ctx *image.Context) ([]string, error) { if SkipRPMComponent(ctx) { log.AuditComponentSkipped(rpmComponentName) zap.L().Info("Skipping RPM component. Configuration is not provided") @@ -60,13 +60,13 @@ func configureRPMs(ctx *image.Context) ([]string, error) { } log.Audit("Resolving package dependencies...") - repoPath, pkgsList, err := ctx.RPMResolver.Resolve(packages, localRPMConfig, artefactsPath) + repoPath, pkgsList, err := c.RPMResolver.Resolve(packages, localRPMConfig, artefactsPath) if err != nil { log.AuditComponentFailed(rpmComponentName) return nil, fmt.Errorf("resolving rpm/package dependencies: %w", err) } - if err = ctx.RPMRepoCreator.Create(repoPath); err != nil { + if err = c.RPMRepoCreator.Create(repoPath); err != nil { log.AuditComponentFailed(rpmComponentName) return nil, fmt.Errorf("creating resolved rpm repository: %w", err) } diff --git a/pkg/combustion/rpm_test.go b/pkg/combustion/rpm_test.go index ab4de4d7..2ddcb1ae 100644 --- a/pkg/combustion/rpm_test.go +++ b/pkg/combustion/rpm_test.go @@ -131,7 +131,9 @@ func TestConfigureRPMs_Skipped(t *testing.T) { ctx, teardown := setupContext(t) defer teardown() - scripts, err := configureRPMs(ctx) + var c Combustion + + scripts, err := c.configureRPMs(ctx) require.NoError(t, err) assert.Nil(t, scripts) @@ -212,10 +214,12 @@ func TestConfigureRPMs_ResolutionFailures(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx.RPMResolver = test.rpmResolver - ctx.RPMRepoCreator = test.rpmRepoCreator + c := Combustion{ + RPMResolver: test.rpmResolver, + RPMRepoCreator: test.rpmRepoCreator, + } - _, err := configureRPMs(ctx) + _, err := c.configureRPMs(ctx) require.Error(t, err) assert.EqualError(t, err, test.expectedErr) }) @@ -269,7 +273,9 @@ func TestConfigureRPMs_GPGFailures(t *testing.T) { require.NoError(t, os.Mkdir(gpgDir, 0o755)) } - _, err := configureRPMs(ctx) + var c Combustion + + _, err := c.configureRPMs(ctx) require.Error(t, err) assert.EqualError(t, err, test.expectedErr) @@ -306,29 +312,30 @@ func TestConfigureRPMs_SuccessfulConfig(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join(gpgDir, "some-key"), nil, 0o600)) - ctx.RPMRepoCreator = mockRPMRepoCreator{ - createFunc: func(path string) error { - return nil + c := Combustion{ + RPMRepoCreator: mockRPMRepoCreator{ + createFunc: func(path string) error { + return nil + }, }, - } - - ctx.RPMResolver = mockRPMResolver{ - resolveFunc: func(packages *image.Packages, localRPMConfig *image.LocalRPMConfig, outputDir string) (string, []string, error) { - if localRPMConfig == nil { - return "", nil, fmt.Errorf("local rpm config is nil") - } - if rpmDir != localRPMConfig.RPMPath { - return "", nil, fmt.Errorf("rpm path mismatch. Expected %s, got %s", rpmDir, localRPMConfig.RPMPath) - } - if gpgDir != localRPMConfig.GPGKeysPath { - return "", nil, fmt.Errorf("gpg path mismatch. Expected %s, got %s", gpgDir, localRPMConfig.GPGKeysPath) - } - - return expectedDir, expectedPkg, nil + RPMResolver: mockRPMResolver{ + resolveFunc: func(packages *image.Packages, localRPMConfig *image.LocalRPMConfig, outputDir string) (string, []string, error) { + if localRPMConfig == nil { + return "", nil, fmt.Errorf("local rpm config is nil") + } + if rpmDir != localRPMConfig.RPMPath { + return "", nil, fmt.Errorf("rpm path mismatch. Expected %s, got %s", rpmDir, localRPMConfig.RPMPath) + } + if gpgDir != localRPMConfig.GPGKeysPath { + return "", nil, fmt.Errorf("gpg path mismatch. Expected %s, got %s", gpgDir, localRPMConfig.GPGKeysPath) + } + + return expectedDir, expectedPkg, nil + }, }, } - scripts, err := configureRPMs(ctx) + scripts, err := c.configureRPMs(ctx) require.NoError(t, err) require.NotNil(t, scripts) require.Len(t, scripts, 1) diff --git a/pkg/image/context.go b/pkg/image/context.go index 7de2ebef..aeea7bb2 100644 --- a/pkg/image/context.go +++ b/pkg/image/context.go @@ -1,9 +1,5 @@ package image -import ( - "io" -) - type HelmClient interface { AddRepo(repository *HelmRepository) error RegistryLogin(repository *HelmRepository) error @@ -11,23 +7,6 @@ type HelmClient interface { Template(chart, repository, version, valuesFilePath, kubeVersion, targetNamespace string) ([]map[string]any, error) } -type networkConfigGenerator interface { - GenerateNetworkConfig(configDir, outputDir string, outputWriter io.Writer) error -} - -type networkConfiguratorInstaller interface { - InstallConfigurator(sourcePath, installPath string) error -} - -type kubernetesScriptDownloader interface { - DownloadInstallScript(distribution, destinationPath string) (string, error) -} - -type kubernetesArtefactDownloader interface { - DownloadRKE2Artefacts(arch Arch, version, cni string, multusEnabled bool, installPath, imagesPath string) error - DownloadK3sArtefacts(arch Arch, version, installPath, imagesPath string) error -} - type LocalRPMConfig struct { // RPMPath is the path to the directory holding RPMs that will be side-loaded RPMPath string @@ -35,14 +14,6 @@ type LocalRPMConfig struct { GPGKeysPath string } -type rpmResolver interface { - Resolve(packages *Packages, localRPMConfig *LocalRPMConfig, outputDir string) (rpmDirPath string, pkgList []string, err error) -} - -type rpmRepoCreator interface { - Create(path string) error -} - type Context struct { // ImageConfigDir is the root directory storing all configuration files. ImageConfigDir string @@ -53,13 +24,5 @@ type Context struct { // ArtefactsDir is a subdirectory under BuildDir containing the larger Combustion related files. ArtefactsDir string // ImageDefinition contains the image definition properties. - ImageDefinition *Definition - NetworkConfigGenerator networkConfigGenerator - NetworkConfiguratorInstaller networkConfiguratorInstaller - KubernetesScriptDownloader kubernetesScriptDownloader - KubernetesArtefactDownloader kubernetesArtefactDownloader - // RPMResolver responsible for resolving rpm/package dependencies - RPMResolver rpmResolver - RPMRepoCreator rpmRepoCreator - HelmClient HelmClient + ImageDefinition *Definition }