Skip to content
Merged
8 changes: 4 additions & 4 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -401,15 +401,15 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE

----------
Module: google.golang.org/api
Version: v0.266.0
Version: v0.267.0
License: BSD-3-Clause
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.266.0/LICENSE
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.267.0/LICENSE

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

----------
Module: google.golang.org/genproto/googleapis
Expand Down
3 changes: 1 addition & 2 deletions cli/cmd/download_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package cmd
import (
"fmt"
"log"
"strings"

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

fullFilename := strings.ReplaceAll(build.Version, "/", "-") + "-" + filename
fullFilename := build.BuildPackageFilename(filename)
out, err := c.FileWriter.OpenAppend(fullFilename)
if err != nil {
out, err = c.FileWriter.Create(fullFilename)
Expand Down
40 changes: 36 additions & 4 deletions cli/cmd/download_package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var _ = Describe("DownloadPackages", func() {
c cmd.DownloadPackageCmd
filename string
version string
hash string
build portal.Build
mockPortal *portal.MockPortal
mockFileWriter *util.MockFileIO
Expand All @@ -30,6 +31,7 @@ var _ = Describe("DownloadPackages", func() {
BeforeEach(func() {
filename = "installer.tar.gz"
version = "codesphere-1.42.0"
hash = "abc1234567"
mockPortal = portal.NewMockPortal(GinkgoT())
mockFileWriter = util.NewMockFileIO(GinkgoT())
})
Expand All @@ -44,6 +46,7 @@ var _ = Describe("DownloadPackages", func() {
}
build = portal.Build{
Version: version,
Hash: hash,
Artifacts: []portal.Artifact{
{Filename: filename},
{Filename: "otherFilename.tar.gz"},
Expand Down Expand Up @@ -169,35 +172,64 @@ var _ = Describe("DownloadPackages", func() {
It("Downloads the correct artifact to the correct output file", func() {
expectedBuildToDownload := portal.Build{
Version: version,
Hash: hash,
Artifacts: []portal.Artifact{
{Filename: filename},
},
}

fakeFile := os.NewFile(uintptr(0), filename)
mockFileWriter.EXPECT().OpenAppend(version+"-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().Open(version+"-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().OpenAppend(version+"-"+hash+"-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().Open(version+"-"+hash+"-"+filename).Return(fakeFile, nil)
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, 0, false).Return(nil)
mockPortal.EXPECT().VerifyBuildArtifactDownload(mock.Anything, expectedBuildToDownload).Return(nil)
err := c.DownloadBuild(mockPortal, build, filename)
Expect(err).NotTo(HaveOccurred())
})

It("Truncates long hash to 10 characters in filename", func() {
longHash := "abc1234567890defghij"
buildWithLongHash := portal.Build{
Version: version,
Hash: longHash,
Artifacts: []portal.Artifact{
{Filename: filename},
{Filename: "otherFilename.tar.gz"},
},
}
expectedBuildToDownload := portal.Build{
Version: version,
Hash: longHash,
Artifacts: []portal.Artifact{
{Filename: filename},
},
}

fakeFile := os.NewFile(uintptr(0), filename)
mockFileWriter.EXPECT().OpenAppend(version+"-abc1234567-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().Open(version+"-abc1234567-"+filename).Return(fakeFile, nil)
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, 0, false).Return(nil)
mockPortal.EXPECT().VerifyBuildArtifactDownload(mock.Anything, expectedBuildToDownload).Return(nil)
err := c.DownloadBuild(mockPortal, buildWithLongHash, filename)
Expect(err).NotTo(HaveOccurred())
})

Context("Version contains a slash", func() {
BeforeEach(func() {
version = "other/version/v1.42.0"
})
It("Downloads the correct artifact to the correct output file", func() {
expectedBuildToDownload := portal.Build{
Version: version,
Hash: hash,
Artifacts: []portal.Artifact{
{Filename: filename},
},
}

fakeFile := os.NewFile(uintptr(0), filename)
mockFileWriter.EXPECT().OpenAppend("other-version-v1.42.0-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().Open("other-version-v1.42.0-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().OpenAppend("other-version-v1.42.0-"+hash+"-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().Open("other-version-v1.42.0-"+hash+"-"+filename).Return(fakeFile, nil)
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, 0, false).Return(nil)
mockPortal.EXPECT().VerifyBuildArtifactDownload(mock.Anything, expectedBuildToDownload).Return(nil)
err := c.DownloadBuild(mockPortal, build, filename)
Expand Down
18 changes: 14 additions & 4 deletions internal/bootstrap/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1378,14 +1378,24 @@ func (b *GCPBootstrapper) InstallCodesphere() error {
skipStepsArg = " -s " + strings.Join(skipSteps, ",")
}

downloadCmd := "oms-cli download package -f " + packageFile + " " + b.Env.InstallVersion
err := b.Env.Jumpbox.RunSSHCommand("root", downloadCmd)
build, err := b.PortalClient.GetBuild(portal.CodesphereProduct, b.Env.InstallVersion, b.Env.InstallHash)
if err != nil {
return fmt.Errorf("failed to get build info: %w", err)
}

downloadCmd := "oms-cli download package -f " + packageFile
if b.Env.InstallHash != "" {
downloadCmd += " -H " + b.Env.InstallHash
}
downloadCmd += " " + b.Env.InstallVersion
err = b.Env.Jumpbox.RunSSHCommand("root", downloadCmd)
if err != nil {
return fmt.Errorf("failed to download Codesphere package from jumpbox: %w", err)
}

installCmd := fmt.Sprintf("oms-cli install codesphere -c /etc/codesphere/config.yaml -k %s/age_key.txt -p %s-%s%s",
b.Env.SecretsDir, b.Env.InstallVersion, packageFile, skipStepsArg)
fullPackageFilename := portal.BuildPackageFilenameFromParts(b.Env.InstallVersion, build.Hash, packageFile)
installCmd := fmt.Sprintf("oms-cli install codesphere -c /etc/codesphere/config.yaml -k %s/age_key.txt -p %s%s",
b.Env.SecretsDir, fullPackageFilename, skipStepsArg)
err = b.Env.Jumpbox.RunSSHCommand("root", installCmd)
if err != nil {
return fmt.Errorf("failed to install Codesphere from jumpbox: %w", err)
Expand Down
65 changes: 55 additions & 10 deletions internal/bootstrap/gcp/gcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,7 @@ var _ = Describe("GCP Bootstrapper", func() {
Describe("InstallCodesphere", func() {
BeforeEach(func() {
csEnv.InstallVersion = "v1.2.3"
csEnv.InstallHash = "abc1234567890"
})
Describe("Valid InstallCodesphere", func() {
Context("Direct GitHub access", func() {
Expand All @@ -1368,42 +1369,86 @@ var _ = Describe("GCP Bootstrapper", func() {

})
It("downloads and installs lite package", func() {
mockPortalClient.EXPECT().GetBuild(portal.CodesphereProduct, "v1.2.3", "abc1234567890").Return(portal.Build{
Version: "v1.2.3",
Hash: "abc1234567890",
}, nil)

// Expect download package
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer-lite.tar.gz v1.2.3").Return(nil)
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer-lite.tar.gz -H abc1234567890 v1.2.3").Return(nil)

// Expect install codesphere
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-lite.tar.gz -s load-container-images").Return(nil)
"oms-cli install codesphere -c /etc/codesphere/config.yaml -k /etc/codesphere/secrets/age_key.txt -p v1.2.3-abc1234567-installer-lite.tar.gz -s load-container-images").Return(nil)

err := bs.InstallCodesphere()
Expect(err).NotTo(HaveOccurred())
})
})

Context("without explicit hash", func() {
BeforeEach(func() {
csEnv.InstallHash = ""
})
It("downloads and installs codesphere", func() {
mockPortalClient.EXPECT().GetBuild(portal.CodesphereProduct, "v1.2.3", "").Return(portal.Build{
Version: "v1.2.3",
Hash: "def9876543210",
}, nil)

// Expect download package
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(nil)

// Expect install codesphere
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-def9876543-installer.tar.gz").Return(nil)

err := bs.InstallCodesphere()
Expect(err).NotTo(HaveOccurred())
})
})

It("downloads and installs codesphere", func() {
// Expect download package
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(nil)
It("downloads and installs codesphere with hash", func() {
mockPortalClient.EXPECT().GetBuild(portal.CodesphereProduct, "v1.2.3", "abc1234567890").Return(portal.Build{
Version: "v1.2.3",
Hash: "abc1234567890",
}, nil)

// Expect install codesphere
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)
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz -H abc1234567890 v1.2.3").Return(nil)
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-abc1234567-installer.tar.gz").Return(nil)

err := bs.InstallCodesphere()
Expect(err).NotTo(HaveOccurred())
})
})

Describe("Invalid cases", func() {
It("fails when GetBuild fails", func() {
mockPortalClient.EXPECT().GetBuild(portal.CodesphereProduct, "v1.2.3", "abc1234567890").Return(portal.Build{}, fmt.Errorf("portal error"))

err := bs.InstallCodesphere()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("failed to get build info"))
})

It("fails when download package fails", func() {
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(fmt.Errorf("download error"))
mockPortalClient.EXPECT().GetBuild(portal.CodesphereProduct, "v1.2.3", "abc1234567890").Return(portal.Build{
Version: "v1.2.3",
Hash: "abc1234567890",
}, nil)
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"))

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

It("fails when install codesphere fails", func() {
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz v1.2.3").Return(nil).Once()
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()
mockPortalClient.EXPECT().GetBuild(portal.CodesphereProduct, "v1.2.3", "abc1234567890").Return(portal.Build{
Version: "v1.2.3",
Hash: "abc1234567890",
}, nil)
nodeClient.EXPECT().RunCommand(mock.MatchedBy(jumpbboxMatcher), "root", "oms-cli download package -f installer.tar.gz -H abc1234567890 v1.2.3").Return(nil).Once()
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-abc1234567-installer.tar.gz").Return(fmt.Errorf("install error")).Once()

err := bs.InstallCodesphere()
Expect(err).To(HaveOccurred())
Expand Down
1 change: 1 addition & 0 deletions internal/installer/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (p *Package) Extract(force bool) error {
if err != nil {
return fmt.Errorf("failed to figure out if package %s is already extracted in %s: %w", p.Filename, workDir, err)
}

if alreadyExtracted && !force {
log.Println("Skipping extraction, package already unpacked. Use force option to overwrite.")
return nil
Expand Down
20 changes: 20 additions & 0 deletions internal/portal/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package portal

import (
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -42,3 +43,22 @@ func (b *Build) GetBuildForDownload(filename string) (Build, error) {

return Build{}, fmt.Errorf("artifact not found: %s", filename)
}

// BuildPackageFilename generates the standard package filename for a given build
// Format: {version}-{shortHash}-{filename}
// Hash is truncated to 10 characters, version slashes are replaced with dashes.
func (b *Build) BuildPackageFilename(filename string) string {
return BuildPackageFilenameFromParts(b.Version, b.Hash, filename)
}

// BuildPackageFilenameFromParts generates the standard package filename from individual parts
// Format: {version}-{shortHash}-{filename}
// Hash is truncated to 10 characters, version slashes are replaced with dashes.
func BuildPackageFilenameFromParts(version, hash, filename string) string {
shortHash := hash
if len(shortHash) > 10 {
shortHash = shortHash[:10]
}
sanitizedVersion := strings.ReplaceAll(version, "/", "-")
return sanitizedVersion + "-" + shortHash + "-" + filename
}
63 changes: 63 additions & 0 deletions internal/portal/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,66 @@ var _ = Describe("GetBuildForDownload", func() {
})

})

var _ = Describe("BuildPackageFilename", func() {
Describe("BuildPackageFilename method", func() {
It("generates filename with truncated hash", func() {
build := portal.Build{
Version: "v1.2.3",
Hash: "abc1234567890def",
}
filename := build.BuildPackageFilename("installer.tar.gz")
Expect(filename).To(Equal("v1.2.3-abc1234567-installer.tar.gz"))
})

It("handles short hash without truncation", func() {
build := portal.Build{
Version: "v1.2.3",
Hash: "abc123",
}
filename := build.BuildPackageFilename("installer.tar.gz")
Expect(filename).To(Equal("v1.2.3-abc123-installer.tar.gz"))
})

It("replaces slashes in version with dashes", func() {
build := portal.Build{
Version: "feature/my-branch",
Hash: "abc1234567890",
}
filename := build.BuildPackageFilename("installer-lite.tar.gz")
Expect(filename).To(Equal("feature-my-branch-abc1234567-installer-lite.tar.gz"))
})
})

Describe("BuildPackageFilenameFromParts function", func() {
It("generates filename from parts with truncated hash", func() {
filename := portal.BuildPackageFilenameFromParts("v1.2.3", "abc1234567890def", "installer.tar.gz")
Expect(filename).To(Equal("v1.2.3-abc1234567-installer.tar.gz"))
})

It("handles branch versions with slashes", func() {
filename := portal.BuildPackageFilenameFromParts("feature/test", "abc1234567890", "installer.tar.gz")
Expect(filename).To(Equal("feature-test-abc1234567-installer.tar.gz"))
})

It("handles exact 10 character hash", func() {
filename := portal.BuildPackageFilenameFromParts("v1.0.0", "1234567890", "installer.tar.gz")
Expect(filename).To(Equal("v1.0.0-1234567890-installer.tar.gz"))
})

It("handles empty hash", func() {
filename := portal.BuildPackageFilenameFromParts("v1.0.0", "", "installer.tar.gz")
Expect(filename).To(Equal("v1.0.0--installer.tar.gz"))
})

It("handles empty version", func() {
filename := portal.BuildPackageFilenameFromParts("", "abc1234567", "installer.tar.gz")
Expect(filename).To(Equal("-abc1234567-installer.tar.gz"))
})

It("handles multiple slashes in version", func() {
filename := portal.BuildPackageFilenameFromParts("feature/sub/branch/v1", "abc1234567", "installer.tar.gz")
Expect(filename).To(Equal("feature-sub-branch-v1-abc1234567-installer.tar.gz"))
})
})
})
8 changes: 4 additions & 4 deletions internal/tmpl/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -401,15 +401,15 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE

----------
Module: google.golang.org/api
Version: v0.266.0
Version: v0.267.0
License: BSD-3-Clause
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.266.0/LICENSE
License URL: https://github.com/googleapis/google-api-go-client/blob/v0.267.0/LICENSE

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

----------
Module: google.golang.org/genproto/googleapis
Expand Down