Skip to content

Commit 670ef72

Browse files
feat: add package checksum tracking and re-extraction logic (#193)
Add the ability to download/install new builds of the same branch without cleaning up the oms-workdir first. [Clickup](https://app.clickup.com/t/869buu10w) --------- Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com>
1 parent a1dfcc5 commit 670ef72

File tree

9 files changed

+162
-27
lines changed

9 files changed

+162
-27
lines changed

NOTICE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,15 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE
401401

402402
----------
403403
Module: google.golang.org/api
404-
Version: v0.266.0
404+
Version: v0.267.0
405405
License: BSD-3-Clause
406-
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.266.0/LICENSE
406+
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.267.0/LICENSE
407407

408408
----------
409409
Module: google.golang.org/api/internal/third_party/uritemplates
410-
Version: v0.266.0
410+
Version: v0.267.0
411411
License: BSD-3-Clause
412-
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.266.0/internal/third_party/uritemplates/LICENSE
412+
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.267.0/internal/third_party/uritemplates/LICENSE
413413

414414
----------
415415
Module: google.golang.org/genproto/googleapis

cli/cmd/download_package.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package cmd
66
import (
77
"fmt"
88
"log"
9-
"strings"
109

1110
"github.com/codesphere-cloud/cs-go/pkg/io"
1211
"github.com/spf13/cobra"
@@ -100,7 +99,7 @@ func (c *DownloadPackageCmd) DownloadBuild(p portal.Portal, build portal.Build,
10099
return fmt.Errorf("failed to find artifact in package: %w", err)
101100
}
102101

103-
fullFilename := strings.ReplaceAll(build.Version, "/", "-") + "-" + filename
102+
fullFilename := build.BuildPackageFilename(filename)
104103
out, err := c.FileWriter.OpenAppend(fullFilename)
105104
if err != nil {
106105
out, err = c.FileWriter.Create(fullFilename)

cli/cmd/download_package_test.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var _ = Describe("DownloadPackages", func() {
2222
c cmd.DownloadPackageCmd
2323
filename string
2424
version string
25+
hash string
2526
build portal.Build
2627
mockPortal *portal.MockPortal
2728
mockFileWriter *util.MockFileIO
@@ -30,6 +31,7 @@ var _ = Describe("DownloadPackages", func() {
3031
BeforeEach(func() {
3132
filename = "installer.tar.gz"
3233
version = "codesphere-1.42.0"
34+
hash = "abc1234567"
3335
mockPortal = portal.NewMockPortal(GinkgoT())
3436
mockFileWriter = util.NewMockFileIO(GinkgoT())
3537
})
@@ -44,6 +46,7 @@ var _ = Describe("DownloadPackages", func() {
4446
}
4547
build = portal.Build{
4648
Version: version,
49+
Hash: hash,
4750
Artifacts: []portal.Artifact{
4851
{Filename: filename},
4952
{Filename: "otherFilename.tar.gz"},
@@ -169,35 +172,64 @@ var _ = Describe("DownloadPackages", func() {
169172
It("Downloads the correct artifact to the correct output file", func() {
170173
expectedBuildToDownload := portal.Build{
171174
Version: version,
175+
Hash: hash,
172176
Artifacts: []portal.Artifact{
173177
{Filename: filename},
174178
},
175179
}
176180

177181
fakeFile := os.NewFile(uintptr(0), filename)
178-
mockFileWriter.EXPECT().OpenAppend(version+"-"+filename).Return(fakeFile, nil)
179-
mockFileWriter.EXPECT().Open(version+"-"+filename).Return(fakeFile, nil)
182+
mockFileWriter.EXPECT().OpenAppend(version+"-"+hash+"-"+filename).Return(fakeFile, nil)
183+
mockFileWriter.EXPECT().Open(version+"-"+hash+"-"+filename).Return(fakeFile, nil)
180184
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, 0, false).Return(nil)
181185
mockPortal.EXPECT().VerifyBuildArtifactDownload(mock.Anything, expectedBuildToDownload).Return(nil)
182186
err := c.DownloadBuild(mockPortal, build, filename)
183187
Expect(err).NotTo(HaveOccurred())
184188
})
185189

190+
It("Uses long hash in filename", func() {
191+
longHash := "abc1234567890defghij"
192+
buildWithLongHash := portal.Build{
193+
Version: version,
194+
Hash: longHash,
195+
Artifacts: []portal.Artifact{
196+
{Filename: filename},
197+
{Filename: "otherFilename.tar.gz"},
198+
},
199+
}
200+
expectedBuildToDownload := portal.Build{
201+
Version: version,
202+
Hash: longHash,
203+
Artifacts: []portal.Artifact{
204+
{Filename: filename},
205+
},
206+
}
207+
208+
fakeFile := os.NewFile(uintptr(0), filename)
209+
mockFileWriter.EXPECT().OpenAppend(version+"-"+longHash+"-"+filename).Return(fakeFile, nil)
210+
mockFileWriter.EXPECT().Open(version+"-"+longHash+"-"+filename).Return(fakeFile, nil)
211+
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, 0, false).Return(nil)
212+
mockPortal.EXPECT().VerifyBuildArtifactDownload(mock.Anything, expectedBuildToDownload).Return(nil)
213+
err := c.DownloadBuild(mockPortal, buildWithLongHash, filename)
214+
Expect(err).NotTo(HaveOccurred())
215+
})
216+
186217
Context("Version contains a slash", func() {
187218
BeforeEach(func() {
188219
version = "other/version/v1.42.0"
189220
})
190221
It("Downloads the correct artifact to the correct output file", func() {
191222
expectedBuildToDownload := portal.Build{
192223
Version: version,
224+
Hash: hash,
193225
Artifacts: []portal.Artifact{
194226
{Filename: filename},
195227
},
196228
}
197229

198230
fakeFile := os.NewFile(uintptr(0), filename)
199-
mockFileWriter.EXPECT().OpenAppend("other-version-v1.42.0-"+filename).Return(fakeFile, nil)
200-
mockFileWriter.EXPECT().Open("other-version-v1.42.0-"+filename).Return(fakeFile, nil)
231+
mockFileWriter.EXPECT().OpenAppend("other-version-v1.42.0-"+hash+"-"+filename).Return(fakeFile, nil)
232+
mockFileWriter.EXPECT().Open("other-version-v1.42.0-"+hash+"-"+filename).Return(fakeFile, nil)
201233
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, 0, false).Return(nil)
202234
mockPortal.EXPECT().VerifyBuildArtifactDownload(mock.Anything, expectedBuildToDownload).Return(nil)
203235
err := c.DownloadBuild(mockPortal, build, filename)

internal/bootstrap/gcp/gcp.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ func (b *GCPBootstrapper) ValidateInput() error {
306306
return fmt.Errorf("failed to get codesphere package: %w", err)
307307
}
308308

309+
if b.Env.InstallHash == "" {
310+
b.Env.InstallHash = build.Hash
311+
}
312+
309313
requiredFilename := "installer.tar.gz"
310314
if b.Env.RegistryType == RegistryTypeGitHub {
311315
requiredFilename = "installer-lite.tar.gz"
@@ -1408,14 +1412,19 @@ func (b *GCPBootstrapper) InstallCodesphere() error {
14081412
skipStepsArg = " -s " + strings.Join(skipSteps, ",")
14091413
}
14101414

1411-
downloadCmd := "oms-cli download package -f " + packageFile + " " + b.Env.InstallVersion
1415+
downloadCmd := "oms-cli download package -f " + packageFile
1416+
if b.Env.InstallHash != "" {
1417+
downloadCmd += " -H " + b.Env.InstallHash
1418+
}
1419+
downloadCmd += " " + b.Env.InstallVersion
14121420
err := b.Env.Jumpbox.RunSSHCommand("root", downloadCmd)
14131421
if err != nil {
14141422
return fmt.Errorf("failed to download Codesphere package from jumpbox: %w", err)
14151423
}
14161424

1417-
installCmd := fmt.Sprintf("oms-cli install codesphere -c /etc/codesphere/config.yaml -k %s/age_key.txt -p %s-%s%s",
1418-
b.Env.SecretsDir, b.Env.InstallVersion, packageFile, skipStepsArg)
1425+
fullPackageFilename := portal.BuildPackageFilenameFromParts(b.Env.InstallVersion, b.Env.InstallHash, packageFile)
1426+
installCmd := fmt.Sprintf("oms-cli install codesphere -c /etc/codesphere/config.yaml -k %s/age_key.txt -p %s%s",
1427+
b.Env.SecretsDir, fullPackageFilename, skipStepsArg)
14191428
err = b.Env.Jumpbox.RunSSHCommand("root", installCmd)
14201429
if err != nil {
14211430
return fmt.Errorf("failed to install Codesphere from jumpbox: %w", err)

internal/bootstrap/gcp/gcp_test.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,7 @@ var _ = Describe("GCP Bootstrapper", func() {
13851385
Describe("InstallCodesphere", func() {
13861386
BeforeEach(func() {
13871387
csEnv.InstallVersion = "v1.2.3"
1388+
csEnv.InstallHash = "abc1234567890"
13881389
})
13891390
Describe("Valid InstallCodesphere", func() {
13901391
Context("Direct GitHub access", func() {
@@ -1396,23 +1397,37 @@ var _ = Describe("GCP Bootstrapper", func() {
13961397
})
13971398
It("downloads and installs lite package", func() {
13981399
// Expect download package
1399-
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer-lite.tar.gz v1.2.3").Return(nil)
1400+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer-lite.tar.gz -H abc1234567890 v1.2.3").Return(nil)
14001401

14011402
// Expect install codesphere
14021403
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root",
1403-
"oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-installer-lite.tar.gz -s load-container-images").Return(nil)
1404+
"oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-abc1234567890-installer-lite.tar.gz -s load-container-images").Return(nil)
14041405

14051406
err := bs.InstallCodesphere()
14061407
Expect(err).NotTo(HaveOccurred())
14071408
})
14081409
})
14091410

1410-
It("downloads and installs codesphere", func() {
1411-
// Expect download package
1412-
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(nil)
1411+
Context("without explicit hash", func() {
1412+
BeforeEach(func() {
1413+
// Simulate that ValidateInput has populated the hash
1414+
csEnv.InstallHash = "def9876543210"
1415+
})
1416+
It("downloads and installs codesphere", func() {
1417+
// Expect download package
1418+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz -H def9876543210 v1.2.3").Return(nil)
1419+
1420+
// Expect install codesphere
1421+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-def9876543210-installer.tar.gz").Return(nil)
1422+
1423+
err := bs.InstallCodesphere()
1424+
Expect(err).NotTo(HaveOccurred())
1425+
})
1426+
})
14131427

1414-
// Expect install codesphere
1415-
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-installer.tar.gz").Return(nil)
1428+
It("downloads and installs codesphere with hash", func() {
1429+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz -H abc1234567890 v1.2.3").Return(nil)
1430+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-abc1234567890-installer.tar.gz").Return(nil)
14161431

14171432
err := bs.InstallCodesphere()
14181433
Expect(err).NotTo(HaveOccurred())
@@ -1421,16 +1436,16 @@ var _ = Describe("GCP Bootstrapper", func() {
14211436

14221437
Describe("Invalid cases", func() {
14231438
It("fails when download package fails", func() {
1424-
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(fmt.Errorf("download error"))
1439+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz -H abc1234567890 v1.2.3").Return(fmt.Errorf("download error"))
14251440

14261441
err := bs.InstallCodesphere()
14271442
Expect(err).To(HaveOccurred())
14281443
Expect(err.Error()).To(ContainSubstring("failed to download Codesphere package from jumpbox"))
14291444
})
14301445

14311446
It("fails when install codesphere fails", func() {
1432-
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(nil).Once()
1433-
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-installer.tar.gz").Return(fmt.Errorf("install error")).Once()
1447+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz -H abc1234567890 v1.2.3").Return(nil).Once()
1448+
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-abc1234567890-installer.tar.gz").Return(fmt.Errorf("install error")).Once()
14341449

14351450
err := bs.InstallCodesphere()
14361451
Expect(err).To(HaveOccurred())

internal/installer/package.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func (p *Package) Extract(force bool) error {
8686
if err != nil {
8787
return fmt.Errorf("failed to figure out if package %s is already extracted in %s: %w", p.Filename, workDir, err)
8888
}
89+
8990
if alreadyExtracted && !force {
9091
log.Println("Skipping extraction, package already unpacked. Use force option to overwrite.")
9192
return nil

internal/portal/package.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package portal
55

66
import (
77
"fmt"
8+
"strings"
89
"time"
910
)
1011

@@ -42,3 +43,18 @@ func (b *Build) GetBuildForDownload(filename string) (Build, error) {
4243

4344
return Build{}, fmt.Errorf("artifact not found: %s", filename)
4445
}
46+
47+
// BuildPackageFilename generates the standard package filename for a given build
48+
// Format: {version}-{hash}-{filename}
49+
// Version slashes are replaced with dashes.
50+
func (b *Build) BuildPackageFilename(filename string) string {
51+
return BuildPackageFilenameFromParts(b.Version, b.Hash, filename)
52+
}
53+
54+
// BuildPackageFilenameFromParts generates the standard package filename from individual parts
55+
// Format: {version}-{hash}-{filename}
56+
// Version slashes are replaced with dashes.
57+
func BuildPackageFilenameFromParts(version, hash, filename string) string {
58+
sanitizedVersion := strings.ReplaceAll(version, "/", "-")
59+
return sanitizedVersion + "-" + hash + "-" + filename
60+
}

internal/portal/package_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,66 @@ var _ = Describe("GetBuildForDownload", func() {
3333
})
3434

3535
})
36+
37+
var _ = Describe("BuildPackageFilename", func() {
38+
Describe("BuildPackageFilename method", func() {
39+
It("generates filename with long hash", func() {
40+
build := portal.Build{
41+
Version: "v1.2.3",
42+
Hash: "abc1234567890def",
43+
}
44+
filename := build.BuildPackageFilename("installer.tar.gz")
45+
Expect(filename).To(Equal("v1.2.3-abc1234567890def-installer.tar.gz"))
46+
})
47+
48+
It("handles short hash", func() {
49+
build := portal.Build{
50+
Version: "v1.2.3",
51+
Hash: "abc123",
52+
}
53+
filename := build.BuildPackageFilename("installer.tar.gz")
54+
Expect(filename).To(Equal("v1.2.3-abc123-installer.tar.gz"))
55+
})
56+
57+
It("replaces slashes in version with dashes", func() {
58+
build := portal.Build{
59+
Version: "feature/my-branch",
60+
Hash: "abc1234567890",
61+
}
62+
filename := build.BuildPackageFilename("installer-lite.tar.gz")
63+
Expect(filename).To(Equal("feature-my-branch-abc1234567890-installer-lite.tar.gz"))
64+
})
65+
})
66+
67+
Describe("BuildPackageFilenameFromParts function", func() {
68+
It("generates filename from parts with long hash", func() {
69+
filename := portal.BuildPackageFilenameFromParts("v1.2.3", "abc1234567890def", "installer.tar.gz")
70+
Expect(filename).To(Equal("v1.2.3-abc1234567890def-installer.tar.gz"))
71+
})
72+
73+
It("handles branch versions with slashes", func() {
74+
filename := portal.BuildPackageFilenameFromParts("feature/test", "abc1234567890", "installer.tar.gz")
75+
Expect(filename).To(Equal("feature-test-abc1234567890-installer.tar.gz"))
76+
})
77+
78+
It("handles exact 10 character hash", func() {
79+
filename := portal.BuildPackageFilenameFromParts("v1.0.0", "1234567890", "installer.tar.gz")
80+
Expect(filename).To(Equal("v1.0.0-1234567890-installer.tar.gz"))
81+
})
82+
83+
It("handles empty hash", func() {
84+
filename := portal.BuildPackageFilenameFromParts("v1.0.0", "", "installer.tar.gz")
85+
Expect(filename).To(Equal("v1.0.0--installer.tar.gz"))
86+
})
87+
88+
It("handles empty version", func() {
89+
filename := portal.BuildPackageFilenameFromParts("", "abc1234567", "installer.tar.gz")
90+
Expect(filename).To(Equal("-abc1234567-installer.tar.gz"))
91+
})
92+
93+
It("handles multiple slashes in version", func() {
94+
filename := portal.BuildPackageFilenameFromParts("feature/sub/branch/v1", "abc1234567", "installer.tar.gz")
95+
Expect(filename).To(Equal("feature-sub-branch-v1-abc1234567-installer.tar.gz"))
96+
})
97+
})
98+
})

internal/tmpl/NOTICE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,15 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE
401401

402402
----------
403403
Module: google.golang.org/api
404-
Version: v0.266.0
404+
Version: v0.267.0
405405
License: BSD-3-Clause
406-
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.266.0/LICENSE
406+
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.267.0/LICENSE
407407

408408
----------
409409
Module: google.golang.org/api/internal/third_party/uritemplates
410-
Version: v0.266.0
410+
Version: v0.267.0
411411
License: BSD-3-Clause
412-
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.266.0/internal/third_party/uritemplates/LICENSE
412+
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.267.0/internal/third_party/uritemplates/LICENSE
413413

414414
----------
415415
Module: google.golang.org/genproto/googleapis

0 commit comments

Comments
 (0)