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

✨ Support new Cluster stack convention #104

Merged
merged 1 commit into from
May 23, 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
22 changes: 11 additions & 11 deletions pkg/clusterstack/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,45 @@ type CsctlConfig struct {
}

// GetCsctlConfig returns CsctlConfig.
func GetCsctlConfig(path string) (CsctlConfig, error) {
func GetCsctlConfig(path string) (*CsctlConfig, error) {
configPath := filepath.Join(path, "csctl.yaml")
configFileData, err := os.ReadFile(filepath.Clean(configPath))
if err != nil {
return CsctlConfig{}, fmt.Errorf("failed to read csctl config: %w", err)
return nil, fmt.Errorf("failed to read csctl config: %w", err)
}

cs := CsctlConfig{}
cs := &CsctlConfig{}
if err := yaml.Unmarshal(configFileData, &cs); err != nil {
return CsctlConfig{}, fmt.Errorf("failed to unmarshal csctl yaml: %w", err)
return nil, fmt.Errorf("failed to unmarshal csctl yaml: %w", err)
}

if cs.Config.Provider.Type == "" {
return CsctlConfig{}, fmt.Errorf("provider type must not be empty")
return nil, fmt.Errorf("provider type must not be empty")
}

if len(cs.Config.Provider.Type) > 253 {
return CsctlConfig{}, fmt.Errorf("provider name must not be greater than 253")
return nil, 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 CsctlConfig{}, fmt.Errorf("failed to provider name match regex: %w", err)
return nil, fmt.Errorf("failed to provider name match regex: %w", err)
}
if !match {
return CsctlConfig{}, fmt.Errorf("invalid provider type: %q", cs.Config.Provider.Type)
return nil, fmt.Errorf("invalid provider type: %q", cs.Config.Provider.Type)
}

if cs.Config.ClusterStackName == "" {
return CsctlConfig{}, fmt.Errorf("cluster stack name must not be empty")
return nil, 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 CsctlConfig{}, fmt.Errorf("failed to kubernetes match regex: %w", err)
return nil, fmt.Errorf("failed to kubernetes match regex: %w", err)
}
if !matched {
return CsctlConfig{}, fmt.Errorf("invalid kubernetes version: %q", cs.Config.KubernetesVersion)
return nil, fmt.Errorf("invalid kubernetes version: %q", cs.Config.KubernetesVersion)
}

return cs, nil
Expand Down
8 changes: 4 additions & 4 deletions pkg/clusterstack/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ type MetaData struct {
}

// ParseMetaData parse the metadata file.
func ParseMetaData(path string) (MetaData, error) {
func ParseMetaData(path string) (*MetaData, error) {
metadataPath := filepath.Join(path, "metadata.yaml")
fileInfo, err := os.ReadFile(filepath.Clean(metadataPath))
if err != nil {
return MetaData{}, fmt.Errorf("failed to read metadata file: %w", err)
return nil, fmt.Errorf("failed to read metadata file: %w", err)
}

metaData := MetaData{}
metaData := &MetaData{}

if err := yaml.Unmarshal(fileInfo, &metaData); err != nil {
return MetaData{}, fmt.Errorf("failed to unmarshal metadata yaml: %w", err)
return nil, fmt.Errorf("failed to unmarshal metadata yaml: %w", err)
}

return metaData, nil
Expand Down
16 changes: 8 additions & 8 deletions pkg/clusterstack/mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ import (
)

// HandleStableMode returns metadata for the stable mode.
func HandleStableMode(gitHubReleasePath string, currentReleaseHash, latestReleaseHash hash.ReleaseHash) (MetaData, error) {
func HandleStableMode(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)
return nil, 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)
return nil, 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)
return nil, fmt.Errorf("failed to bump cluster addon: %w", err)
}
fmt.Printf("Bumped ClusterAddon Version: %s\n", metadata.Versions.Components.ClusterAddon)
} else {
Expand All @@ -49,7 +49,7 @@ func HandleStableMode(gitHubReleasePath string, currentReleaseHash, latestReleas
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)
return nil, fmt.Errorf("failed to bump node image: %w", err)
}
fmt.Printf("Bumped NodeImage Version: %s\n", metadata.Versions.Components.NodeImage)
} else {
Expand All @@ -64,15 +64,15 @@ func HandleStableMode(gitHubReleasePath string, currentReleaseHash, latestReleas
}

// HandleHashMode returns metadata of Hash mode.
func HandleHashMode(kubernetesVersion string) (MetaData, error) {
func HandleHashMode(kubernetesVersion string) (*MetaData, error) {
commitHash, err := git.GetLatestGitCommit("./")
if err != nil {
return MetaData{}, fmt.Errorf("failed to get latest commit hash: %w", err)
return nil, fmt.Errorf("failed to get latest commit hash: %w", err)
}

commitHash = fmt.Sprintf("v0-sha.%s", commitHash)

return MetaData{
return &MetaData{
APIVersion: "metadata.clusterstack.x-k8s.io/v1alpha1",
Versions: Versions{
Kubernetes: kubernetesVersion,
Expand Down
63 changes: 43 additions & 20 deletions pkg/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ var (

// CreateOptions contains config for creating a release.
type CreateOptions struct {
ClusterStackPath string
ClusterStackReleaseDir string
Config clusterstack.CsctlConfig
Metadata clusterstack.MetaData
CurrentReleaseHash hash.ReleaseHash
LatestReleaseHash hash.ReleaseHash
NodeImageRegistry string
newClusterStackConvention bool
ClusterStackPath string
ClusterStackReleaseDir string
Config *clusterstack.CsctlConfig
Metadata *clusterstack.MetaData
CurrentReleaseHash hash.ReleaseHash
LatestReleaseHash hash.ReleaseHash
NodeImageRegistry string
}

// createCmd represents the create command.
Expand Down Expand Up @@ -95,7 +96,17 @@ func GetCreateOptions(ctx context.Context, clusterStackPath string) (*CreateOpti
createOption.ClusterStackPath = clusterStackPath
createOption.Config = config

_, _, err = providerplugin.GetProviderExecutable(&config)
if _, err := os.Stat(filepath.Join(clusterStackPath, "clusteraddon.yaml")); err != nil {
// old if clusteraddon.yaml is not present.
if !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to find clusteraddon.yaml: %w", err)
}
} else {
// new if clusteraddon.yaml is present.
createOption.newClusterStackConvention = true
}

_, _, err = providerplugin.GetProviderExecutable(config)
if err != nil {
return createOption, fmt.Errorf("providerplugin.GetProviderExecutable(&config) failed: %w", err)
}
Expand All @@ -121,7 +132,7 @@ func GetCreateOptions(ctx context.Context, clusterStackPath string) (*CreateOpti
// update the metadata kubernetes version with the csctl.yaml config
createOption.Metadata.Versions.Kubernetes = config.Config.KubernetesVersion

latestRepoRelease, err := github.GetLatestReleaseFromRemoteRepository(ctx, mode, &config, gc)
latestRepoRelease, err := github.GetLatestReleaseFromRemoteRepository(ctx, mode, config, gc)
if err != nil {
return nil, fmt.Errorf("failed to get latest release form remote repository: %w", err)
}
Expand All @@ -144,7 +155,7 @@ func GetCreateOptions(ctx context.Context, clusterStackPath string) (*CreateOpti
}
}

releaseDirName, err := clusterstack.GetClusterStackReleaseDirectoryName(&createOption.Metadata, &createOption.Config)
releaseDirName, err := clusterstack.GetClusterStackReleaseDirectoryName(createOption.Metadata, createOption.Config)
if err != nil {
return nil, fmt.Errorf("failed to get cluster stack release directory name: %w", err)
}
Expand Down Expand Up @@ -222,7 +233,7 @@ func (c *CreateOptions) generateRelease() error {
}

// Build all the templated output and put it in a tmp directory
if err := template.GenerateOutputFromTemplate(c.ClusterStackPath, "./.tmp/", &c.Metadata); err != nil {
if err := template.GenerateOutputFromTemplate(c.ClusterStackPath, "./.tmp/", c.Metadata); err != nil {
return fmt.Errorf("failed to generate tmp output: %w", err)
}

Expand All @@ -238,18 +249,30 @@ func (c *CreateOptions) generateRelease() error {
}

// Package Helm from the tmp directory to the release directory
if err := template.CreatePackage("./.tmp/", c.ClusterStackReleaseDir); err != nil {
if err := template.CreatePackage("./.tmp/", c.ClusterStackReleaseDir, c.newClusterStackConvention, c.Config, c.Metadata); err != nil {
return fmt.Errorf("failed to create template package: %w", err)
}

// 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 fmt.Errorf("failed to read cluster-addon-values.yaml: %w", err)
}
if c.newClusterStackConvention {
// Copy the clusteraddon.yaml config to release if new way
clusterAddonData, err := os.ReadFile(filepath.Join(c.ClusterStackPath, "clusteraddon.yaml"))
if err != nil {
return fmt.Errorf("failed to read clusteraddon.yaml: %w", err)
}

if err := os.WriteFile(filepath.Join(c.ClusterStackReleaseDir, "clusteraddon.yaml"), clusterAddonData, os.FileMode(0o644)); err != nil {
return fmt.Errorf("failed to write clusteraddon.yaml: %w", err)
}
} else {
// 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 fmt.Errorf("failed to read cluster-addon-values.yaml: %w", err)
}

if err := os.WriteFile(filepath.Join(c.ClusterStackReleaseDir, "cluster-addon-values.yaml"), clusterAddonData, os.FileMode(0o644)); err != nil {
return fmt.Errorf("failed to write cluster-addon-values.yaml: %w", err)
if err := os.WriteFile(filepath.Join(c.ClusterStackReleaseDir, "cluster-addon-values.yaml"), clusterAddonData, os.FileMode(0o644)); err != nil {
return fmt.Errorf("failed to write cluster-addon-values.yaml: %w", err)
}
}

// Put the final metadata file into the output directory.
Expand All @@ -268,7 +291,7 @@ func (c *CreateOptions) generateRelease() error {
return fmt.Errorf("failed to write metadata: %w", err)
}

err = providerplugin.CreateNodeImages(&c.Config,
err = providerplugin.CreateNodeImages(c.Config,
c.ClusterStackPath,
c.ClusterStackReleaseDir,
c.NodeImageRegistry)
Expand Down
87 changes: 84 additions & 3 deletions pkg/template/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,40 @@ limitations under the License.
package template

import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"

"github.com/SovereignCloudStack/csctl/pkg/clusterstack"
"helm.sh/helm/v3/pkg/action"
)

// CreatePackage creates the package for release.
func CreatePackage(src, dst string) error {
func CreatePackage(src, dst string, newType bool, config *clusterstack.CsctlConfig, metadata *clusterstack.MetaData) error {
fmt.Printf("path now: %q\n", filepath.Join(src, "cluster-class"))
if err := createHelmPackage(filepath.Join(src, "cluster-class"), dst); err != nil {
return fmt.Errorf("failed to create package for ClusterClass: %w", err)
}

if err := createHelmPackage(filepath.Join(src, "cluster-addon"), dst); err != nil {
return fmt.Errorf("failed to create package for ClusterAddon: %w", err)
kubernetesVerion, err := config.ParseKubernetesVersion()
if err != nil {
return fmt.Errorf("failed to parse kubernetes version: %w", err)
}

if newType {
clusterAddonDst := filepath.Join(dst, fmt.Sprintf("%s-%s-%s-cluster-addon-%s.tgz", config.Config.Provider.Type, config.Config.ClusterStackName, kubernetesVerion.String(), metadata.Versions.Components.ClusterAddon))
if err := createTarPackage(filepath.Join(src, "cluster-addon"), clusterAddonDst); err != nil {
return fmt.Errorf("failed to create package for ClusterAddon: %w", err)
}
} else {
fmt.Printf("path now: %q\n", filepath.Join(src, "cluster-addon"))
if err := createHelmPackage(filepath.Join(src, "cluster-addon"), dst); err != nil {
return fmt.Errorf("failed to create helm package for ClusterAddon: %w", err)
}
}

return nil
Expand All @@ -47,3 +67,64 @@ func createHelmPackage(src, dst string) error {

return nil
}

func createTarPackage(src, dst string) error {
outFile, err := os.Create(filepath.Clean(dst))
if err != nil {
return fmt.Errorf("failed to create tar output destination directory: %w", err)
}
defer outFile.Close()

gw := gzip.NewWriter(outFile)
defer gw.Close()

tw := tar.NewWriter(gw)
defer tw.Close()

if err := filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

// Ignore the root folder itself
if path == src {
return nil
}

relPath, err := filepath.Rel(src, path)
if err != nil {
return fmt.Errorf("failed to calculate relative path of source: %q and destination: %q directory: %w", src, dst, err)
}

// Use filepath.ToSlash to convert path separators to '/'
relPath = filepath.ToSlash(relPath)

header, err := tar.FileInfoHeader(info, relPath)
if err != nil {
return fmt.Errorf("failed to get the tar info header: %w", err)
}
header.Name = relPath

if err := tw.WriteHeader(header); err != nil {
return fmt.Errorf("failed to set the write header: %w", err)
}

if !info.IsDir() {
file, err := os.Open(filepath.Clean(path))
if err != nil {
return fmt.Errorf("failed to open path: %w", err)
}
defer file.Close()

if _, err := io.Copy(tw, file); err != nil {
return fmt.Errorf("failed to copy file: %w", err)
}
}

return nil
}); err != nil {
return fmt.Errorf("failed to walk on the source directory: %w", err)
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v2
type: application
description: CNI chart for kind
name: CNI
version: '0'
maintainers:
- name: Syself
email: info@syself.com
url: https://github.com/syself
Loading
Loading