Skip to content

Commit

Permalink
Merge pull request #145 from nicholasSUSE/create-release-cmd
Browse files Browse the repository at this point in the history
Create release command
  • Loading branch information
nicholasSUSE authored Aug 5, 2024
2 parents 3525298 + cb000ca commit 3352c7e
Show file tree
Hide file tree
Showing 8 changed files with 510 additions and 49 deletions.
66 changes: 66 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const (
defaultBranchVersionEnvironmentVariable = "BRANCH_VERSION"
// defaultForkEnvironmentVariable is the default environment variable that indicates the fork URL
defaultForkEnvironmentVariable = "FORK"
// defaultChartVersionEnvironmentVariable is the default environment variable that indicates the version to release
defaultChartVersionEnvironmentVariable = ""
)

var (
Expand Down Expand Up @@ -76,6 +78,8 @@ var (
DebugMode = false
// ForkURL represents the fork URL configured as a remote in your local git repository
ForkURL = ""
// ChartVersion of the chart to release
ChartVersion = ""
)

func main() {
Expand Down Expand Up @@ -171,6 +175,18 @@ func main() {
Destination: &ForkURL,
EnvVar: defaultForkEnvironmentVariable,
}
chartVersionFlag := cli.StringFlag{
Name: "version",
Usage: `Usage:
./bin/charts-build-scripts <command> --version="<chart_version>"
VERSION="<chart_version>" make <command>
Target version of chart to release.
`,
Required: true,
Destination: &ChartVersion,
EnvVar: defaultChartVersionEnvironmentVariable,
}
app.Commands = []cli.Command{
{
Name: "list",
Expand Down Expand Up @@ -319,6 +335,13 @@ func main() {
Action: autoForwardPort,
Flags: []cli.Flag{branchVersionFlag, chartFlag, forkFlag},
},
{
Name: "release",
Usage: `Execute the release script to release a chart to the production branch.
`,
Action: release,
Flags: []cli.Flag{branchVersionFlag, chartFlag, chartVersionFlag, forkFlag},
},
}

if err := app.Run(os.Args); err != nil {
Expand Down Expand Up @@ -688,3 +711,46 @@ func autoForwardPort(c *cli.Context) {
}

}

func release(c *cli.Context) {
if ForkURL == "" {
logrus.Fatal("FORK environment variable must be set to run release cmd")
}

if CurrentChart == "" {
logrus.Fatal("CHART environment variable must be set to run release cmd")
}

rootFs := filesystem.GetFilesystem(getRepoRoot())

dependencies, err := lifecycle.InitDependencies(rootFs, c.String("branch-version"), CurrentChart, false)
if err != nil {
logrus.Fatalf("encountered error while initializing dependencies: %v", err)
}

status, err := lifecycle.LoadState(rootFs)
if err != nil {
logrus.Fatalf("could not load state; please run lifecycle-status before this command: %v", err)
}

release, err := auto.InitRelease(dependencies, status, ChartVersion, CurrentChart, ForkURL)
if err != nil {
logrus.Fatalf("failed to initialize release: %v", err)
}

if err := release.PullAsset(); err != nil {
logrus.Fatalf("failed to execute release: %v", err)
}

// Unzip Assets: ASSET=<chart>/<chart>-<version.tgz make unzip
CurrentAsset = release.Chart + "/" + release.AssetTgz
unzipAssets(c)

// update release.yaml
if err := release.UpdateReleaseYaml(); err != nil {
logrus.Fatalf("failed to update release.yaml: %v", err)
}

// make index
createOrUpdateIndex(c)
}
171 changes: 171 additions & 0 deletions pkg/auto/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package auto

import (
"errors"
"fmt"
"io"
"os"

"github.com/rancher/charts-build-scripts/pkg/filesystem"
"github.com/rancher/charts-build-scripts/pkg/git"
"github.com/rancher/charts-build-scripts/pkg/lifecycle"
"github.com/rancher/charts-build-scripts/pkg/path"
"gopkg.in/yaml.v3"
)

// Release holds necessary metadata to release a chart version
type Release struct {
git *git.Git
VR *lifecycle.VersionRules
AssetTgz string
AssetPath string
ChartVersion string
Chart string
ReleaseYamlPath string
ForkRemoteURL string
}

// InitRelease will create the Release struct with access to the necessary dependencies.
func InitRelease(d *lifecycle.Dependencies, s *lifecycle.Status, v, c, f string) (*Release, error) {
r := &Release{
git: d.Git,
VR: d.VR,
ChartVersion: v,
Chart: c,
ForkRemoteURL: f,
}

var ok bool
var assetVersions []lifecycle.Asset

assetVersions, ok = s.AssetsToBeReleased[r.Chart]
if !ok {
assetVersions, ok = s.AssetsToBeForwardPorted[r.Chart]
if !ok {
return nil, errors.New("no asset version to release for chart:" + r.Chart)
}
}

var assetVersion string
for _, version := range assetVersions {
if version.Version == r.ChartVersion {
assetVersion = version.Version
break
}
}
if assetVersion == "" {
return nil, errors.New("no asset version to release for chart:" + r.Chart + " version:" + r.ChartVersion)
}

r.AssetPath, r.AssetTgz = mountAssetVersionPath(r.Chart, assetVersion)

// Check again if the asset was already released in the local repository
if err := checkAssetReleased(r.AssetPath); err != nil {
return nil, fmt.Errorf("failed to check for chart:%s ; err: %w", r.Chart, err)
}

// Check if we have a release.yaml file in the expected path
if exist, err := filesystem.PathExists(d.RootFs, path.RepositoryReleaseYaml); err != nil || !exist {
return nil, errors.New("release.yaml not found")
}

r.ReleaseYamlPath = filesystem.GetAbsPath(d.RootFs, path.RepositoryReleaseYaml)

return r, nil
}

// PullAsset will execute the release porting for a chart in the repository
func (r *Release) PullAsset() error {
if err := r.git.FetchBranch(r.VR.DevBranch); err != nil {
return err
}

if err := r.git.CheckFileExists(r.AssetPath, r.VR.DevBranch); err != nil {
return fmt.Errorf("asset version not found in dev branch: %w", err)
}

if err := r.git.CheckoutFile(r.VR.DevBranch, r.AssetPath); err != nil {
return err
}

return r.git.ResetHEAD()
}

func checkAssetReleased(chartVersion string) error {
if _, err := os.Stat(chartVersion); err != nil {
return err
}

return nil
}

// mountAssetVersionPath returns the asset path and asset tgz name for a given chart and version.
// example: assets/longhorn/longhorn-100.0.0+up0.0.0.tgz
func mountAssetVersionPath(chart, version string) (string, string) {
assetTgz := chart + "-" + version + ".tgz"
assetPath := "assets/" + chart + "/" + assetTgz
return assetPath, assetTgz
}

func (r *Release) readReleaseYaml() (map[string][]string, error) {
var releaseVersions = make(map[string][]string, 0)

file, err := os.Open(r.ReleaseYamlPath)
if err != nil {
return nil, err
}
defer file.Close()

decoder := yaml.NewDecoder(file)
if err := decoder.Decode(&releaseVersions); err != nil {
if err == io.EOF {
// Handle EOF error gracefully
return releaseVersions, nil
}
return nil, err
}

return releaseVersions, nil
}

// UpdateReleaseYaml reads and parse the release.yaml file to a struct, appends the new version and writes it back to the file.
func (r *Release) UpdateReleaseYaml() error {
releaseVersions, err := r.readReleaseYaml()
if err != nil {
return err
}

// Append new version and remove duplicates if any
releaseVersions[r.Chart] = append(releaseVersions[r.Chart], r.ChartVersion)
releaseVersions[r.Chart] = removeDuplicates(releaseVersions[r.Chart])

// Since we opened and read the file before we can truncate it.
outputFile, err := os.Create(r.ReleaseYamlPath)
if err != nil {
return err
}
defer outputFile.Close()

encoder := yaml.NewEncoder(outputFile)
encoder.SetIndent(2)
if err := encoder.Encode(releaseVersions); err != nil {
return err
}

return nil
}

// removeDuplicates takes a slice of strings and returns a new slice with duplicates removed.
func removeDuplicates(slice []string) []string {
seen := make(map[string]struct{}) // map to keep track of seen strings
var result []string // slice to hold the results

for _, val := range slice {
if _, ok := seen[val]; !ok {
seen[val] = struct{}{} // mark string as seen
result = append(result, val) // append to result if not seen before
}
}

return result
}
Loading

0 comments on commit 3352c7e

Please sign in to comment.