diff --git a/pkg/clusterstack/config.go b/pkg/clusterstack/config.go index 46b4e3e9..9149f54e 100644 --- a/pkg/clusterstack/config.go +++ b/pkg/clusterstack/config.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/SovereignCloudStack/cluster-stack-operator/pkg/kubernetesversion" + "github.com/SovereignCloudStack/cluster-stack-operator/pkg/version" "gopkg.in/yaml.v3" ) @@ -44,45 +45,45 @@ type CsmctlConfig struct { } // GetCsmctlConfig returns CsmctlConfig. -func GetCsmctlConfig(path string) (*CsmctlConfig, error) { +func GetCsmctlConfig(path string) (CsmctlConfig, error) { configPath := filepath.Join(path, "csmctl.yaml") configFileData, err := os.ReadFile(filepath.Clean(configPath)) if err != nil { - return nil, fmt.Errorf("failed to read csmctl config: %w", err) + return CsmctlConfig{}, fmt.Errorf("failed to read csmctl config: %w", err) } - cs := &CsmctlConfig{} + cs := CsmctlConfig{} if err := yaml.Unmarshal(configFileData, &cs); err != nil { - return nil, fmt.Errorf("failed to unmarshal csmctl yaml: %w", err) + return CsmctlConfig{}, fmt.Errorf("failed to unmarshal csmctl yaml: %w", err) } if cs.Config.Provider.Type == "" { - return nil, fmt.Errorf("provider type must not be empty") + return CsmctlConfig{}, fmt.Errorf("provider type must not be empty") } if len(cs.Config.Provider.Type) > 253 { - return nil, fmt.Errorf("provider name must not be greater than 253") + return CsmctlConfig{}, fmt.Errorf("provider name must not be greater than 253") } match, err := regexp.MatchString(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`, cs.Config.Provider.Type) if err != nil { - return nil, fmt.Errorf("failed to provider name match regex: %w", err) + return CsmctlConfig{}, fmt.Errorf("failed to provider name match regex: %w", err) } if !match { - return nil, fmt.Errorf("invalid provider type: %q", cs.Config.Provider.Type) + return CsmctlConfig{}, fmt.Errorf("invalid provider type: %q", cs.Config.Provider.Type) } if cs.Config.ClusterStackName == "" { - return nil, fmt.Errorf("cluster stack name must not be empty") + return CsmctlConfig{}, fmt.Errorf("cluster stack name must not be empty") } // Validate kubernetes version matched, err := regexp.MatchString(`^v\d+\.\d+\.\d+$`, cs.Config.KubernetesVersion) if err != nil { - return nil, fmt.Errorf("failed to kubernetes match regex: %w", err) + return CsmctlConfig{}, fmt.Errorf("failed to kubernetes match regex: %w", err) } if !matched { - return nil, fmt.Errorf("invalid kubernetes version: %q", cs.Config.KubernetesVersion) + return CsmctlConfig{}, fmt.Errorf("invalid kubernetes version: %q", cs.Config.KubernetesVersion) } return cs, nil @@ -111,3 +112,20 @@ func (c *CsmctlConfig) ParseKubernetesVersion() (kubernetesversion.KubernetesVer Minor: minor, }, nil } + +func GetClusterStackReleaseDirectoryName(metadata MetaData, config CsmctlConfig) (string, error) { + // Parse the cluster stack version from dot format `v1-alpha.0` to a version way of struct + // and parse the kubernetes version from `v1.27.3` to a major minor way + // and create the release directory at the end. + clusterStackVersion, err := version.New(metadata.Versions.ClusterStack) + if err != nil { + return "", fmt.Errorf("failed to parse cluster stack version: %w", err) + } + kubernetesVerion, err := config.ParseKubernetesVersion() + if err != nil { + return "", fmt.Errorf("failed to parse kubernetes version: %w", err) + } + clusterStackReleaseDirName := fmt.Sprintf("%s-%s-%s-%s", config.Config.Provider.Type, config.Config.ClusterStackName, kubernetesVerion.String(), clusterStackVersion.String()) + + return clusterStackReleaseDirName, nil +} diff --git a/pkg/clusterstack/metadata.go b/pkg/clusterstack/metadata.go index 1fa0ed30..485546bf 100644 --- a/pkg/clusterstack/metadata.go +++ b/pkg/clusterstack/metadata.go @@ -24,40 +24,43 @@ import ( "gopkg.in/yaml.v3" ) -// MetaData contains information of the metadata.yaml. +type Component struct { + ClusterAddon string `yaml:"clusterAddon"` + NodeImage string `yaml:"nodeImage"` +} + +type Versions struct { + ClusterStack string `yaml:"clusterStack"` + Kubernetes string `yaml:"kubernetes"` + Components Component `yaml:"components"` +} + type MetaData struct { - APIVersion string `yaml:"apiVersion"` - Versions struct { - ClusterStack string `yaml:"clusterStack"` - Kubernetes string `yaml:"kubernetes"` - Components struct { - ClusterAddon string `yaml:"clusterAddon"` - NodeImage string `yaml:"nodeImage,omitempty"` - } `yaml:"components"` - } `yaml:"versions"` + APIVersion string `yaml:"apiVersion"` + Versions Versions `yaml:"versions"` } // ParseMetaData parse the metadata file. -func ParseMetaData(path string) (*MetaData, error) { +func ParseMetaData(path string) (MetaData, error) { entries, err := os.ReadDir(path) if err != nil { - return nil, fmt.Errorf("failed to read metadata directory: %w", err) + return MetaData{}, fmt.Errorf("failed to read metadata directory: %w", err) } if len(entries) != 1 { - return nil, fmt.Errorf("ambiguous release found") + return MetaData{}, fmt.Errorf("ambiguous release found") } metadataPath := filepath.Join(path, entries[0].Name(), "metadata.yaml") fileInfo, err := os.ReadFile(filepath.Clean(metadataPath)) if err != nil { - return nil, fmt.Errorf("failed to read metadata file: %w", err) + return MetaData{}, fmt.Errorf("failed to read metadata file: %w", err) } - metaData := &MetaData{} + metaData := MetaData{} if err := yaml.Unmarshal(fileInfo, &metaData); err != nil { - return nil, fmt.Errorf("failed to unmarshal metadata yaml: %w", err) + return MetaData{}, fmt.Errorf("failed to unmarshal metadata yaml: %w", err) } return metaData, nil diff --git a/pkg/clusterstack/mode.go b/pkg/clusterstack/mode.go new file mode 100644 index 00000000..5f357ad6 --- /dev/null +++ b/pkg/clusterstack/mode.go @@ -0,0 +1,59 @@ +package clusterstack + +import ( + "fmt" + + "github.com/SovereignCloudStack/csmctl/pkg/hash" +) + +func HandleStableMode(kubernetesVersion, gitHubReleasePath string, currentReleaseHash, latestReleaseHash hash.ReleaseHash) (MetaData, error) { + metadata, err := ParseMetaData(gitHubReleasePath) + if err != nil { + return MetaData{}, fmt.Errorf("failed to parse metadata: %w", err) + } + + metadata.Versions.ClusterStack, err = BumpVersion(metadata.Versions.ClusterStack) + if err != nil { + return MetaData{}, fmt.Errorf("failed to bump cluster stack: %w", err) + } + fmt.Printf("Bumped ClusterStack Version: %s\n", metadata.Versions.ClusterStack) + + if currentReleaseHash.ClusterAddonDir != latestReleaseHash.ClusterAddonDir || currentReleaseHash.ClusterAddonValues != latestReleaseHash.ClusterAddonValues { + metadata.Versions.Components.ClusterAddon, err = BumpVersion(metadata.Versions.Components.ClusterAddon) + if err != nil { + return MetaData{}, fmt.Errorf("failed to bump cluster addon: %w", err) + } + fmt.Printf("Bumped ClusterAddon Version: %s\n", metadata.Versions.Components.ClusterAddon) + } else { + fmt.Printf("ClusterAddon Version unchanged: %s\n", metadata.Versions.Components.ClusterAddon) + } + + if currentReleaseHash.NodeImageDir != latestReleaseHash.NodeImageDir { + metadata.Versions.Components.NodeImage, err = BumpVersion(metadata.Versions.Components.NodeImage) + if err != nil { + return MetaData{}, fmt.Errorf("failed to bump node image: %w", err) + } + fmt.Printf("Bumped NodeImage Version: %s\n", metadata.Versions.Components.NodeImage) + } else { + fmt.Printf("NodeImage Version unchanged: %s\n", metadata.Versions.Components.NodeImage) + } + + return metadata, nil +} + +func HandleHashMode(currentRelease hash.ReleaseHash, kubernetesVersion string) MetaData { + clusterStackHash := currentRelease.GetClusterStackHash() + clusterStackHash = fmt.Sprintf("v0-sha.%s", clusterStackHash) + + return MetaData{ + APIVersion: "metadata.clusterstack.x-k8s.io/v1alpha1", + Versions: Versions{ + Kubernetes: kubernetesVersion, + ClusterStack: clusterStackHash, + Components: Component{ + ClusterAddon: clusterStackHash, + NodeImage: clusterStackHash, + }, + }, + } +} diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index 0fcf5f83..1457bc5f 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -22,23 +22,31 @@ import ( "os" "path/filepath" - "github.com/SovereignCloudStack/cluster-stack-operator/pkg/version" - csmctlclusterstack "github.com/SovereignCloudStack/csmctl/pkg/clusterstack" - "github.com/SovereignCloudStack/csmctl/pkg/git" + "github.com/SovereignCloudStack/csmctl/pkg/clusterstack" "github.com/SovereignCloudStack/csmctl/pkg/github" "github.com/SovereignCloudStack/csmctl/pkg/hash" "github.com/SovereignCloudStack/csmctl/pkg/template" "github.com/spf13/cobra" - "gopkg.in/yaml.v3" + "gopkg.in/yaml.v2" ) const ( stableMode = "stable" - alphaMode = "alpha" - betaMode = "beta" hashMode = "hash" ) +var ( + shortDescription = "Creates a cluster stack release with the help of given cluster stack" + longDescription = `It takes cluster stacks and mode as an input and based on that creates + the cluster stack release in the current directory named "release/". + Supported modes are - stable, alpha, beta, hash + + note - Hash mode takes the last hash of the git commit.` + example = `csmctl create tests/cluster-stacks/docker/ferrol -m hash (for hash mode) + + csmctl create tests/cluster-stacks/docker/ferrol -m hash --github-release github-release/ (for stable mode)` +) + var ( mode string outputDirectory string @@ -48,23 +56,22 @@ var ( // CreateOptions contains config for creating a release. type CreateOptions struct { - config *csmctlclusterstack.CsmctlConfig - metadata *csmctlclusterstack.MetaData - clusterStackPath string - clusterStackReleaseDir string - currentHash hash.ReleaseHash + ClusterStackPath string + ClusterStackReleaseDir string + Config clusterstack.CsmctlConfig + Metadata clusterstack.MetaData + CurrentReleaseHash hash.ReleaseHash + LatestReleaseHash hash.ReleaseHash } // createCmd represents the create command. var createCmd = &cobra.Command{ - Use: "create", - Short: "Creates a cluster stack release with the help of given cluster stack", - Long: `It takes cluster stacks and mode as an input and based on that creates -the cluster stack release in the current directory named "release/". -Supported modes are - stable, alpha, beta, hash - -note - Hash mode takes the last hash of the git commit.`, - RunE: createAction, + Use: "create", + Short: shortDescription, + Long: longDescription, + Example: example, + RunE: createAction, + SilenceUsage: true, } func init() { @@ -74,217 +81,145 @@ func init() { createCmd.Flags().StringVar(&githubReleasePath, "github-release", "github-release", "It is used to get the path to local github release (for stable mode only)") } -func createAction(_ *cobra.Command, args []string) error { - if len(args) != 1 { - return fmt.Errorf("please provide a valid command, create only accept one argument to path to the cluster stacks") +// GetCreateOptions create a Create Option for create command. +func GetCreateOptions(clusterStackPath string) (*CreateOptions, error) { + createOption := &CreateOptions{} + + // ClusterAddon config + config, err := clusterstack.GetCsmctlConfig(clusterStackPath) + if err != nil { + return nil, fmt.Errorf("failed to get config: %w", err) } - clusterStackPath := args[0] + createOption.ClusterStackPath = clusterStackPath + createOption.Config = config - // Get csmctl config form cluster stacks. - config, err := csmctlclusterstack.GetCsmctlConfig(clusterStackPath) + currentHash, err := hash.GetHash(clusterStackPath) if err != nil { - return fmt.Errorf("failed to get config: %w", err) + return nil, fmt.Errorf("failed to get hash: %w", err) } + createOption.CurrentReleaseHash = currentHash - // Skip downloading github release for the hash mode. - // var latestRepoRelease *string - if mode == hashMode { - // Handle metadata based on mode and create the - - // release directory and creates the metadata.yaml file in the release directory. - metadata, csrDirName, err := handleMetadataAndGetCSRDirectoryName(mode, githubReleasePath, config, hash.ReleaseHash{}, hash.ReleaseHash{}) + switch mode { + case hashMode: + createOption.Metadata = clusterstack.HandleHashMode(createOption.CurrentReleaseHash, config.Config.KubernetesVersion) + case stableMode: + createOption.Metadata, err = clusterstack.HandleStableMode(createOption.Config.Config.KubernetesVersion, githubReleasePath, createOption.CurrentReleaseHash, createOption.LatestReleaseHash) if err != nil { - return err + return nil, fmt.Errorf("failed to handle stable mode: %w", err) } - // Calculate the current Hash, and check if anything changed in the cluster stacks. - currentHash, err := hash.GetHash(clusterStackPath) + // TODO: remove + latestReleaseInfoWithHash, err := github.GetLocalReleaseInfoWithHash(githubReleasePath) if err != nil { - return fmt.Errorf("failed to get hash: %w", err) - } - - create := &CreateOptions{ - config: config, - metadata: metadata, - clusterStackPath: clusterStackPath, - clusterStackReleaseDir: csrDirName, - currentHash: currentHash, - } - if err := create.buildPackerAndGenerateRelease(); err != nil { - return fmt.Errorf("failed to build packer and generate release: %w", err) + return nil, fmt.Errorf("failed to get latest github local release: %w", err) } - return nil + createOption.LatestReleaseHash = latestReleaseInfoWithHash.Hash } - // TODO: delete it later once we have github release available - // Get Release with Hash information - latestReleaseInfoWithHash, err := github.GetLocalReleaseInfoWithHash(githubReleasePath) + releaseDirName, err := clusterstack.GetClusterStackReleaseDirectoryName(createOption.Metadata, createOption.Config) if err != nil { - return fmt.Errorf("failed to get local release info with hash: %w", err) + return nil, fmt.Errorf("failed to get cluster stack release directory name: %w", err) } + // Release directory name `release/docker-ferrol-1-27-v1` + createOption.ClusterStackReleaseDir = filepath.Join(outputDirectory, releaseDirName) - // check if release directory exists or not, if not create it. - if err := os.MkdirAll(outputDirectory, os.ModePerm); err != nil { - return fmt.Errorf("failed to create release directory: %w", err) - } + return createOption, nil +} - // Calculate the current Hash, and check if anything changed in the cluster stacks. - currentHash, err := hash.GetHash(clusterStackPath) - if err != nil { - return fmt.Errorf("failed to get hash: %w", err) +func createAction(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("please provide a valid command, create only accept one argument to path to the cluster stacks") } - if err := currentHash.ValidateWithLatestReleaseHash(latestReleaseInfoWithHash.Hash); err != nil { - return fmt.Errorf("failed to validate with latest release hash: %w", err) + clusterStackPath := args[0] + + if mode != stableMode && mode != hashMode { + fmt.Println("The mode is ", mode) + return fmt.Errorf("mode is not supported please choose from - stable, hash") } - // Handle metadata based on mode and create the - - // release directory and creates the metadata.yaml file in the release directory. - metadata, csrDirName, err := handleMetadataAndGetCSRDirectoryName(mode, githubReleasePath, config, latestReleaseInfoWithHash.Hash, currentHash) + createOpts, err := GetCreateOptions(clusterStackPath) if err != nil { - return err + return fmt.Errorf("failed to create create options: %w", err) } - create := &CreateOptions{ - config: config, - metadata: metadata, - clusterStackPath: clusterStackPath, - clusterStackReleaseDir: csrDirName, - currentHash: currentHash, + // Validate if there any change or not + if err := createOpts.validateHash(); err != nil { + return fmt.Errorf("failed to validate with latest release hash: %w", err) } - if err := create.buildPackerAndGenerateRelease(); err != nil { - return fmt.Errorf("failed to build packer and generate release: %w", err) + + if err := createOpts.generateRelease(); err != nil { + return fmt.Errorf("failed to generate release: %w", err) } return nil } -func handleMetadataAndGetCSRDirectoryName(mode, githubReleasePath string, config *csmctlclusterstack.CsmctlConfig, latestReleaseHash, currentHash hash.ReleaseHash) (metadata *csmctlclusterstack.MetaData, csrDirName string, err error) { - metadata = &csmctlclusterstack.MetaData{} - - if mode != stableMode && mode != alphaMode && mode != betaMode && mode != hashMode { - fmt.Println("The mode is ", mode) - return nil, "", fmt.Errorf("mode is not supported please choose from - stable, alpha, beta, hash") - } - - metadata.APIVersion = "metadata.clusterstack.x-k8s.io/v1alpha1" - metadata.Versions.Kubernetes = config.Config.KubernetesVersion - - switch mode { - case stableMode: - metadata, err = handleStableMode(githubReleasePath, latestReleaseHash, currentHash) - if err != nil { - return nil, "", fmt.Errorf("failed to handle stable mode: %w", err) - } - case hashMode: - metadata, err = handleHashMode(metadata) - if err != nil { - return nil, "", fmt.Errorf("failed to handle hash mode: %w", err) - } +// validateHash returns if some hash changes or not +func (c *CreateOptions) validateHash() error { + if c.CurrentReleaseHash.ClusterStack == c.LatestReleaseHash.ClusterStack { + return fmt.Errorf("no change in the cluster stack") } - // Parse the cluster stack version from dot format `v1-alpha.0` to a version way of struct - // and parse the kubernetes version from `v1.27.3` to a major minor way - // and create the release directory at the end. - clusterStackVersion, err := version.New(metadata.Versions.ClusterStack) - if err != nil { - return nil, "", fmt.Errorf("failed to parse cluster stack version: %w", err) - } - kubernetesVerion, err := config.ParseKubernetesVersion() - if err != nil { - return nil, "", fmt.Errorf("failed to parse kubernetes version: %w", err) - } - clusterStackReleaseDirName := fmt.Sprintf("%s-%s-%s-%s", config.Config.Provider.Type, config.Config.ClusterStackName, kubernetesVerion.String(), clusterStackVersion.String()) + return nil +} - if err := os.MkdirAll(filepath.Join(outputDirectory, clusterStackReleaseDirName), os.ModePerm); err != nil { - return nil, "", fmt.Errorf("failed to create release directory: %w", err) +func (c *CreateOptions) generateRelease() error { + if err := os.MkdirAll(c.ClusterStackReleaseDir, os.ModePerm); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) } - // Put the final metadata file into the output directory. - metaDataByte, err := yaml.Marshal(metadata) + // Write the current hash + hashJSONData, err := json.MarshalIndent(c.CurrentReleaseHash, "", " ") if err != nil { - return nil, "", fmt.Errorf("failed to marshal metadata file: %w", err) + return fmt.Errorf("failed to marshal hash json: %w", err) } - metadataFile, err := os.Create(filepath.Clean(filepath.Join(outputDirectory, clusterStackReleaseDirName, "metadata.yaml"))) + filePath := filepath.Join(c.ClusterStackReleaseDir, "hashes.json") + hashFile, err := os.Create(filepath.Clean(filePath)) if err != nil { - return nil, "", fmt.Errorf("failed to create metadata file: %w", err) + return fmt.Errorf("failed to create hash json: %w", err) } - defer metadataFile.Close() - - if _, err := metadataFile.Write(metaDataByte); err != nil { - return nil, "", fmt.Errorf("failed to write metadata file: %w", err) - } - - return metadata, clusterStackReleaseDirName, nil -} + defer hashFile.Close() -func handleStableMode(githubReleasePath string, latestReleaseHash, currentHash hash.ReleaseHash) (*csmctlclusterstack.MetaData, error) { - metadata, err := csmctlclusterstack.ParseMetaData(githubReleasePath) + _, err = hashFile.Write(hashJSONData) if err != nil { - return nil, fmt.Errorf("failed to parse metadata: %w", err) + return fmt.Errorf("failed to write current release hash: %w", err) } - metadata.Versions.Components.NodeImage = "" - metadata.Versions.ClusterStack, err = csmctlclusterstack.BumpVersion(metadata.Versions.ClusterStack) - if err != nil { - return nil, fmt.Errorf("failed to bump cluster stack version: %w", err) + // Build all the templated output and put it in a tmp directory + if err := template.GenerateOutputFromTemplate(c.ClusterStackPath, "./tmp/", c.Metadata); err != nil { + return fmt.Errorf("failed to generate tmp output: %w", err) } - fmt.Printf("Bumped ClusterStack Version: %s\n", metadata.Versions.ClusterStack) - if currentHash.ClusterAddonDir != latestReleaseHash.ClusterAddonDir || currentHash.ClusterAddonValues != latestReleaseHash.ClusterAddonValues { - metadata.Versions.Components.ClusterAddon, err = csmctlclusterstack.BumpVersion(metadata.Versions.Components.ClusterAddon) - if err != nil { - return nil, fmt.Errorf("failed to bump version: %w", err) - } - fmt.Printf("Bumped ClusterAddon Version: %s\n", metadata.Versions.Components.ClusterAddon) - } else { - fmt.Printf("ClusterAddon Version unchanged: %s\n", metadata.Versions.Components.ClusterAddon) + // Package Helm from the tmp directory to the release directory + if err := template.CreatePackage("./tmp/", c.ClusterStackReleaseDir); err != nil { + return fmt.Errorf("failed to create template package: %w", err) } - return metadata, nil -} - -func handleHashMode(metadata *csmctlclusterstack.MetaData) (*csmctlclusterstack.MetaData, error) { - gitCommitHash, err := git.GetLatestGitCommit("./") + // Copy the cluster-addon-values.yaml config to release if old way + clusterAddonData, err := os.ReadFile(filepath.Join(c.ClusterStackPath, "cluster-addon-values.yaml")) if err != nil { - return nil, fmt.Errorf("failed to get latest git commit: %w", err) + return fmt.Errorf("failed to read cluster-addon-values.yaml: %w", err) } - gitCommitHash = fmt.Sprintf("v0-sha.%s", gitCommitHash) - metadata.Versions.ClusterStack = gitCommitHash - metadata.Versions.Components.ClusterAddon = gitCommitHash - return metadata, nil -} - -func (c *CreateOptions) buildPackerAndGenerateRelease() error { - // Release directory name - releaseDirectory := filepath.Join(outputDirectory, c.clusterStackReleaseDir) - - // Write the current hash - hashJSONData, err := json.MarshalIndent(c.currentHash, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal json: %w", err) + if err := os.WriteFile(filepath.Join(c.ClusterStackReleaseDir, "cluster-addon-values.yaml"), clusterAddonData, os.ModePerm); err != nil { + return fmt.Errorf("failed to write cluster-addon-values.yaml: %w", err) } - filePath := filepath.Join(releaseDirectory, "hashes.json") - hashFile, err := os.Create(filepath.Clean(filePath)) + // Put the final metadata file into the output directory. + metaDataByte, err := yaml.Marshal(c.Metadata) if err != nil { - return fmt.Errorf("failed to create hash file: %w", err) + return fmt.Errorf("failed to marshal metadata yaml: %w", err) } - defer hashFile.Close() - _, err = hashFile.Write(hashJSONData) + metadataFile, err := os.Create(filepath.Join(c.ClusterStackReleaseDir, "metadata.yaml")) if err != nil { - return fmt.Errorf("failed to write hash file: %w", err) - } - - // Build all the templated output and put it in a tmp directory - if err := template.GenerateOutputFromTemplate(c.clusterStackPath, "./tmp/", c.metadata); err != nil { - return fmt.Errorf("failed to generate new temporary output template: %w", err) + return fmt.Errorf("failed to create metadata file: %w", err) } + defer metadataFile.Close() - // Package Helm from the tmp directory to the release directory - if err := template.CreatePackage("./tmp/", releaseDirectory); err != nil { - return fmt.Errorf("failed to create package: %w", err) + if _, err := metadataFile.Write(metaDataByte); err != nil { + return fmt.Errorf("failed to write metadata: %w", err) } return nil diff --git a/pkg/hash/hash.go b/pkg/hash/hash.go index 95c0f280..479bb9a7 100644 --- a/pkg/hash/hash.go +++ b/pkg/hash/hash.go @@ -37,8 +37,9 @@ const ( // ReleaseHash contains the information of release hash. type ReleaseHash struct { + ClusterStack string `json:"clusterStack"` ClusterAddonDir string `json:"clusterAddonDir"` - ClusterAddonValues string `json:"clusterAddonValues"` + ClusterAddonValues string `json:"clusterAddonValues,omitempty"` NodeImageDir string `json:"nodeImageDir,omitempty"` } @@ -50,6 +51,16 @@ func GetHash(path string) (ReleaseHash, error) { } releaseHash := ReleaseHash{} + + hash, err := dirhash.HashDir(path, "", dirhash.DefaultHash) + if err != nil { + return ReleaseHash{}, fmt.Errorf("failed to calculate cluster stack hash: %w", err) + } + hash = clean(hash) + fmt.Printf("path %q: cluster stack hash: %q\n", path, hash) + + releaseHash.ClusterStack = hash + for _, entry := range entries { entryPath := filepath.Join(path, entry.Name()) if entry.IsDir() && (entry.Name() == clusterAddonDirName || entry.Name() == nodeImageDirName) { @@ -57,7 +68,7 @@ func GetHash(path string) (ReleaseHash, error) { if err != nil { return ReleaseHash{}, fmt.Errorf("failed to hash dir: %w", err) } - hash = strings.TrimPrefix(hash, "h1:") + hash = clean(hash) switch entry.Name() { case clusterAddonDirName: @@ -72,13 +83,22 @@ func GetHash(path string) (ReleaseHash, error) { if _, err := io.Copy(fileHash, file); err != nil { return ReleaseHash{}, fmt.Errorf("failed to copy dir: %w", err) } - releaseHash.ClusterAddonValues = base64.StdEncoding.EncodeToString(fileHash.Sum(nil)) + releaseHash.ClusterAddonValues = clean(base64.StdEncoding.EncodeToString(fileHash.Sum(nil))) } } return releaseHash, nil } +func clean(hash string) string { + hash = strings.TrimPrefix(hash, "h1:") + hash = strings.ReplaceAll(hash, "/", "") + hash = strings.ReplaceAll(hash, "=", "") + hash = strings.ReplaceAll(hash, "+", "") + + return hash +} + // ValidateWithLatestReleaseHash compare current hash with latest release hash. func (r ReleaseHash) ValidateWithLatestReleaseHash(latestReleaseHash ReleaseHash) error { if r.ClusterAddonDir == latestReleaseHash.ClusterAddonDir && @@ -89,3 +109,7 @@ func (r ReleaseHash) ValidateWithLatestReleaseHash(latestReleaseHash ReleaseHash return nil } + +func (r ReleaseHash) GetClusterStackHash() string { + return r.ClusterStack[:7] +} diff --git a/pkg/template/template.go b/pkg/template/template.go index 0a6f53f0..bd106d0f 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -27,10 +27,10 @@ import ( ) // CustomWalkFunc is the type for the walk function. -type CustomWalkFunc func(src, dst, path string, info os.FileInfo, meta *csmctlclusterstack.MetaData) error +type CustomWalkFunc func(src, dst, path string, info os.FileInfo, meta csmctlclusterstack.MetaData) error // MyWalk is the custom walking function to walk in the cluster stacks. -func MyWalk(src, dst string, walkFn CustomWalkFunc, meta *csmctlclusterstack.MetaData) error { +func MyWalk(src, dst string, walkFn CustomWalkFunc, meta csmctlclusterstack.MetaData) error { if err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error { return walkFn(src, dst, path, info, meta) }); err != nil { @@ -40,7 +40,7 @@ func MyWalk(src, dst string, walkFn CustomWalkFunc, meta *csmctlclusterstack.Met return nil } -func visitFile(src, dst, path string, info os.FileInfo, meta *csmctlclusterstack.MetaData) error { +func visitFile(src, dst, path string, info os.FileInfo, meta csmctlclusterstack.MetaData) error { relativePath, err := filepath.Rel(src, path) if err != nil { return fmt.Errorf("failed to relate directory: %w", err) @@ -78,6 +78,6 @@ func visitFile(src, dst, path string, info os.FileInfo, meta *csmctlclusterstack } // GenerateOutputFromTemplate is used to generate the template with replaced values. -func GenerateOutputFromTemplate(src, dst string, meta *csmctlclusterstack.MetaData) error { +func GenerateOutputFromTemplate(src, dst string, meta csmctlclusterstack.MetaData) error { return MyWalk(src, dst, visitFile, meta) }