Skip to content

Commit 3352c7e

Browse files
authored
Merge pull request #145 from nicholasSUSE/create-release-cmd
Create release command
2 parents 3525298 + cb000ca commit 3352c7e

File tree

8 files changed

+510
-49
lines changed

8 files changed

+510
-49
lines changed

main.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const (
4646
defaultBranchVersionEnvironmentVariable = "BRANCH_VERSION"
4747
// defaultForkEnvironmentVariable is the default environment variable that indicates the fork URL
4848
defaultForkEnvironmentVariable = "FORK"
49+
// defaultChartVersionEnvironmentVariable is the default environment variable that indicates the version to release
50+
defaultChartVersionEnvironmentVariable = ""
4951
)
5052

5153
var (
@@ -76,6 +78,8 @@ var (
7678
DebugMode = false
7779
// ForkURL represents the fork URL configured as a remote in your local git repository
7880
ForkURL = ""
81+
// ChartVersion of the chart to release
82+
ChartVersion = ""
7983
)
8084

8185
func main() {
@@ -171,6 +175,18 @@ func main() {
171175
Destination: &ForkURL,
172176
EnvVar: defaultForkEnvironmentVariable,
173177
}
178+
chartVersionFlag := cli.StringFlag{
179+
Name: "version",
180+
Usage: `Usage:
181+
./bin/charts-build-scripts <command> --version="<chart_version>"
182+
VERSION="<chart_version>" make <command>
183+
184+
Target version of chart to release.
185+
`,
186+
Required: true,
187+
Destination: &ChartVersion,
188+
EnvVar: defaultChartVersionEnvironmentVariable,
189+
}
174190
app.Commands = []cli.Command{
175191
{
176192
Name: "list",
@@ -319,6 +335,13 @@ func main() {
319335
Action: autoForwardPort,
320336
Flags: []cli.Flag{branchVersionFlag, chartFlag, forkFlag},
321337
},
338+
{
339+
Name: "release",
340+
Usage: `Execute the release script to release a chart to the production branch.
341+
`,
342+
Action: release,
343+
Flags: []cli.Flag{branchVersionFlag, chartFlag, chartVersionFlag, forkFlag},
344+
},
322345
}
323346

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

690713
}
714+
715+
func release(c *cli.Context) {
716+
if ForkURL == "" {
717+
logrus.Fatal("FORK environment variable must be set to run release cmd")
718+
}
719+
720+
if CurrentChart == "" {
721+
logrus.Fatal("CHART environment variable must be set to run release cmd")
722+
}
723+
724+
rootFs := filesystem.GetFilesystem(getRepoRoot())
725+
726+
dependencies, err := lifecycle.InitDependencies(rootFs, c.String("branch-version"), CurrentChart, false)
727+
if err != nil {
728+
logrus.Fatalf("encountered error while initializing dependencies: %v", err)
729+
}
730+
731+
status, err := lifecycle.LoadState(rootFs)
732+
if err != nil {
733+
logrus.Fatalf("could not load state; please run lifecycle-status before this command: %v", err)
734+
}
735+
736+
release, err := auto.InitRelease(dependencies, status, ChartVersion, CurrentChart, ForkURL)
737+
if err != nil {
738+
logrus.Fatalf("failed to initialize release: %v", err)
739+
}
740+
741+
if err := release.PullAsset(); err != nil {
742+
logrus.Fatalf("failed to execute release: %v", err)
743+
}
744+
745+
// Unzip Assets: ASSET=<chart>/<chart>-<version.tgz make unzip
746+
CurrentAsset = release.Chart + "/" + release.AssetTgz
747+
unzipAssets(c)
748+
749+
// update release.yaml
750+
if err := release.UpdateReleaseYaml(); err != nil {
751+
logrus.Fatalf("failed to update release.yaml: %v", err)
752+
}
753+
754+
// make index
755+
createOrUpdateIndex(c)
756+
}

pkg/auto/release.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package auto
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
9+
"github.com/rancher/charts-build-scripts/pkg/filesystem"
10+
"github.com/rancher/charts-build-scripts/pkg/git"
11+
"github.com/rancher/charts-build-scripts/pkg/lifecycle"
12+
"github.com/rancher/charts-build-scripts/pkg/path"
13+
"gopkg.in/yaml.v3"
14+
)
15+
16+
// Release holds necessary metadata to release a chart version
17+
type Release struct {
18+
git *git.Git
19+
VR *lifecycle.VersionRules
20+
AssetTgz string
21+
AssetPath string
22+
ChartVersion string
23+
Chart string
24+
ReleaseYamlPath string
25+
ForkRemoteURL string
26+
}
27+
28+
// InitRelease will create the Release struct with access to the necessary dependencies.
29+
func InitRelease(d *lifecycle.Dependencies, s *lifecycle.Status, v, c, f string) (*Release, error) {
30+
r := &Release{
31+
git: d.Git,
32+
VR: d.VR,
33+
ChartVersion: v,
34+
Chart: c,
35+
ForkRemoteURL: f,
36+
}
37+
38+
var ok bool
39+
var assetVersions []lifecycle.Asset
40+
41+
assetVersions, ok = s.AssetsToBeReleased[r.Chart]
42+
if !ok {
43+
assetVersions, ok = s.AssetsToBeForwardPorted[r.Chart]
44+
if !ok {
45+
return nil, errors.New("no asset version to release for chart:" + r.Chart)
46+
}
47+
}
48+
49+
var assetVersion string
50+
for _, version := range assetVersions {
51+
if version.Version == r.ChartVersion {
52+
assetVersion = version.Version
53+
break
54+
}
55+
}
56+
if assetVersion == "" {
57+
return nil, errors.New("no asset version to release for chart:" + r.Chart + " version:" + r.ChartVersion)
58+
}
59+
60+
r.AssetPath, r.AssetTgz = mountAssetVersionPath(r.Chart, assetVersion)
61+
62+
// Check again if the asset was already released in the local repository
63+
if err := checkAssetReleased(r.AssetPath); err != nil {
64+
return nil, fmt.Errorf("failed to check for chart:%s ; err: %w", r.Chart, err)
65+
}
66+
67+
// Check if we have a release.yaml file in the expected path
68+
if exist, err := filesystem.PathExists(d.RootFs, path.RepositoryReleaseYaml); err != nil || !exist {
69+
return nil, errors.New("release.yaml not found")
70+
}
71+
72+
r.ReleaseYamlPath = filesystem.GetAbsPath(d.RootFs, path.RepositoryReleaseYaml)
73+
74+
return r, nil
75+
}
76+
77+
// PullAsset will execute the release porting for a chart in the repository
78+
func (r *Release) PullAsset() error {
79+
if err := r.git.FetchBranch(r.VR.DevBranch); err != nil {
80+
return err
81+
}
82+
83+
if err := r.git.CheckFileExists(r.AssetPath, r.VR.DevBranch); err != nil {
84+
return fmt.Errorf("asset version not found in dev branch: %w", err)
85+
}
86+
87+
if err := r.git.CheckoutFile(r.VR.DevBranch, r.AssetPath); err != nil {
88+
return err
89+
}
90+
91+
return r.git.ResetHEAD()
92+
}
93+
94+
func checkAssetReleased(chartVersion string) error {
95+
if _, err := os.Stat(chartVersion); err != nil {
96+
return err
97+
}
98+
99+
return nil
100+
}
101+
102+
// mountAssetVersionPath returns the asset path and asset tgz name for a given chart and version.
103+
// example: assets/longhorn/longhorn-100.0.0+up0.0.0.tgz
104+
func mountAssetVersionPath(chart, version string) (string, string) {
105+
assetTgz := chart + "-" + version + ".tgz"
106+
assetPath := "assets/" + chart + "/" + assetTgz
107+
return assetPath, assetTgz
108+
}
109+
110+
func (r *Release) readReleaseYaml() (map[string][]string, error) {
111+
var releaseVersions = make(map[string][]string, 0)
112+
113+
file, err := os.Open(r.ReleaseYamlPath)
114+
if err != nil {
115+
return nil, err
116+
}
117+
defer file.Close()
118+
119+
decoder := yaml.NewDecoder(file)
120+
if err := decoder.Decode(&releaseVersions); err != nil {
121+
if err == io.EOF {
122+
// Handle EOF error gracefully
123+
return releaseVersions, nil
124+
}
125+
return nil, err
126+
}
127+
128+
return releaseVersions, nil
129+
}
130+
131+
// UpdateReleaseYaml reads and parse the release.yaml file to a struct, appends the new version and writes it back to the file.
132+
func (r *Release) UpdateReleaseYaml() error {
133+
releaseVersions, err := r.readReleaseYaml()
134+
if err != nil {
135+
return err
136+
}
137+
138+
// Append new version and remove duplicates if any
139+
releaseVersions[r.Chart] = append(releaseVersions[r.Chart], r.ChartVersion)
140+
releaseVersions[r.Chart] = removeDuplicates(releaseVersions[r.Chart])
141+
142+
// Since we opened and read the file before we can truncate it.
143+
outputFile, err := os.Create(r.ReleaseYamlPath)
144+
if err != nil {
145+
return err
146+
}
147+
defer outputFile.Close()
148+
149+
encoder := yaml.NewEncoder(outputFile)
150+
encoder.SetIndent(2)
151+
if err := encoder.Encode(releaseVersions); err != nil {
152+
return err
153+
}
154+
155+
return nil
156+
}
157+
158+
// removeDuplicates takes a slice of strings and returns a new slice with duplicates removed.
159+
func removeDuplicates(slice []string) []string {
160+
seen := make(map[string]struct{}) // map to keep track of seen strings
161+
var result []string // slice to hold the results
162+
163+
for _, val := range slice {
164+
if _, ok := seen[val]; !ok {
165+
seen[val] = struct{}{} // mark string as seen
166+
result = append(result, val) // append to result if not seen before
167+
}
168+
}
169+
170+
return result
171+
}

0 commit comments

Comments
 (0)