Skip to content
Merged
8 changes: 8 additions & 0 deletions cli/cmd/download_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,13 @@ func (c *DownloadPackageCmd) DownloadBuild(p portal.Portal, build portal.Build,
return fmt.Errorf("failed to verify artifact: %w", err)
}

if download.Artifacts[0].Md5Sum != "" {
checksumFilename := fullFilename + ".md5"
err = c.FileWriter.WriteFile(checksumFilename, []byte(download.Artifacts[0].Md5Sum), 0644)
if err != nil {
log.Printf("Warning: failed to save checksum file: %v", err)
}
}

return nil
}
26 changes: 26 additions & 0 deletions cli/cmd/download_package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,32 @@ var _ = Describe("DownloadPackages", func() {
Expect(err).NotTo(HaveOccurred())
})

It("Saves MD5 checksum to sidecar file when available", func() {
md5Sum := "d41d8cd98f00b204e9800998ecf8427e"
buildWithMd5 := portal.Build{
Version: version,
Artifacts: []portal.Artifact{
{Filename: filename, Md5Sum: md5Sum},
{Filename: "otherFilename.tar.gz"},
},
}
expectedBuildToDownload := portal.Build{
Version: version,
Artifacts: []portal.Artifact{
{Filename: filename, Md5Sum: md5Sum},
},
}

fakeFile := os.NewFile(uintptr(0), filename)
mockFileWriter.EXPECT().OpenAppend(version+"-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().Open(version+"-"+filename).Return(fakeFile, nil)
mockFileWriter.EXPECT().WriteFile(version+"-"+filename+".md5", []byte(md5Sum), os.FileMode(0644)).Return(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, buildWithMd5, filename)
Expect(err).NotTo(HaveOccurred())
})

Context("Version contains a slash", func() {
BeforeEach(func() {
version = "other/version/v1.42.0"
Expand Down
61 changes: 60 additions & 1 deletion internal/installer/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

const depsDir = "deps"
const depsTar = "deps.tar.gz"
const checksumMarkerFile = ".oms-package-checksum"

type PackageManager interface {
FileIO() util.FileIO
Expand Down Expand Up @@ -73,8 +74,52 @@ func (p *Package) alreadyExtracted(dir string) (bool, error) {
return isDir, nil
}

func (p *Package) getPackageChecksum() string {
data, err := p.fileIO.ReadFile(p.Filename + ".md5")
if err != nil {
return ""
}
return strings.TrimSpace(string(data))
}

func (p *Package) getExtractedChecksum(workDir string) string {
data, err := p.fileIO.ReadFile(path.Join(workDir, checksumMarkerFile))
if err != nil {
return ""
}
return strings.TrimSpace(string(data))
}

func (p *Package) saveExtractedChecksum(workDir, checksum string) error {
if checksum == "" {
return nil
}
return p.fileIO.WriteFile(path.Join(workDir, checksumMarkerFile), []byte(checksum), 0644)
}

func (p *Package) packageChanged(workDir string) bool {
packageChecksum := p.getPackageChecksum()
if packageChecksum == "" {
return false
}

extractedChecksum := p.getExtractedChecksum(workDir)
if extractedChecksum == "" {
log.Println("No checksum marker found in extracted directory, will re-extract to ensure consistency.")
return true
}

if packageChecksum != extractedChecksum {
log.Printf("Package checksum changed (was: %s, now: %s), will re-extract.", extractedChecksum, packageChecksum)
return true
}

return false
}

// Extract extracts the package tar.gz file into its working directory.
// If force is true, it will overwrite existing files.
// If the package checksum has changed since last extraction, it will also re-extract.
func (p *Package) Extract(force bool) error {
workDir := p.GetWorkDir()
err := os.MkdirAll(p.OmsWorkdir, 0755)
Expand All @@ -86,11 +131,19 @@ 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 {

// Check if the package has changed since last extraction
needsReExtraction := p.packageChanged(workDir)

if alreadyExtracted && !force && !needsReExtraction {
log.Println("Skipping extraction, package already unpacked. Use force option to overwrite.")
return nil
}

if needsReExtraction && !force {
log.Println("Package has changed, re-extracting...")
}

err = util.ExtractTarGz(p.fileIO, p.Filename, workDir)
if err != nil {
return fmt.Errorf("failed to extract package %s to %s: %w", p.Filename, workDir, err)
Expand All @@ -105,6 +158,12 @@ func (p *Package) Extract(force bool) error {
}
}

packageChecksum := p.getPackageChecksum()
err = p.saveExtractedChecksum(workDir, packageChecksum)
if err != nil {
log.Printf("Warning: failed to save checksum marker: %v", err)
}

return nil
}

Expand Down
97 changes: 97 additions & 0 deletions internal/installer/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,103 @@ var _ = Describe("Package", func() {
Expect(err.Error()).To(ContainSubstring("failed to ensure workdir exists"))
})
})

Context("when package checksum changes", func() {
var checksumFile string

BeforeEach(func() {
checksumFile = pkg.Filename + ".md5"
err := os.WriteFile(checksumFile, []byte("original-checksum-123"), 0644)
Expect(err).ToNot(HaveOccurred())

err = pkg.Extract(false)
Expect(err).ToNot(HaveOccurred())
})

It("re-extracts when checksum changes", func() {
workDir := pkg.GetWorkDir()
markerFile := filepath.Join(workDir, ".oms-package-checksum")
Expect(markerFile).To(BeAnExistingFile())
content, err := os.ReadFile(markerFile)
Expect(err).ToNot(HaveOccurred())
Expect(string(content)).To(Equal("original-checksum-123"))

testFile := filepath.Join(workDir, "test-file.txt")
err = os.WriteFile(testFile, []byte("modified content"), 0644)
Expect(err).ToNot(HaveOccurred())

err = os.WriteFile(checksumFile, []byte("new-checksum-456"), 0644)
Expect(err).ToNot(HaveOccurred())

err = pkg.Extract(false)
Expect(err).ToNot(HaveOccurred())

actualContent, err := os.ReadFile(testFile)
Expect(err).ToNot(HaveOccurred())
Expect(string(actualContent)).To(Equal("test content"))

content, err = os.ReadFile(markerFile)
Expect(err).ToNot(HaveOccurred())
Expect(string(content)).To(Equal("new-checksum-456"))
})

It("skips extraction when checksum is the same", func() {
workDir := pkg.GetWorkDir()
markerFile := filepath.Join(workDir, ".oms-package-checksum")
Expect(markerFile).To(BeAnExistingFile())

testFile := filepath.Join(workDir, "test-file.txt")
err := os.WriteFile(testFile, []byte("modified content"), 0644)
Expect(err).ToNot(HaveOccurred())

err = pkg.Extract(false)
Expect(err).ToNot(HaveOccurred())

actualContent, err := os.ReadFile(testFile)
Expect(err).ToNot(HaveOccurred())
Expect(string(actualContent)).To(Equal("modified content"))
})

It("re-extracts when marker file is missing", func() {
workDir := pkg.GetWorkDir()
markerFile := filepath.Join(workDir, ".oms-package-checksum")

err := os.Remove(markerFile)
Expect(err).ToNot(HaveOccurred())

testFile := filepath.Join(workDir, "test-file.txt")
err = os.WriteFile(testFile, []byte("modified content"), 0644)
Expect(err).ToNot(HaveOccurred())

err = pkg.Extract(false)
Expect(err).ToNot(HaveOccurred())

actualContent, err := os.ReadFile(testFile)
Expect(err).ToNot(HaveOccurred())
Expect(string(actualContent)).To(Equal("test content"))
})
})

Context("when no checksum sidecar file exists", func() {
BeforeEach(func() {
err := pkg.Extract(false)
Expect(err).ToNot(HaveOccurred())
})

It("skips extraction without checksum (backward compatibility)", func() {
workDir := pkg.GetWorkDir()
testFile := filepath.Join(workDir, "test-file.txt")
err := os.WriteFile(testFile, []byte("modified content"), 0644)
Expect(err).ToNot(HaveOccurred())

err = pkg.Extract(false)
Expect(err).ToNot(HaveOccurred())

actualContent, err := os.ReadFile(testFile)
Expect(err).ToNot(HaveOccurred())
Expect(string(actualContent)).To(Equal("modified content"))
})
})
})
})

Expand Down