Skip to content

Commit

Permalink
Ensure license files are tracked during installation so they can be r…
Browse files Browse the repository at this point in the history
…emoved (#206)
  • Loading branch information
radeksimko authored May 20, 2024
1 parent 5a74938 commit 237ac6f
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 36 deletions.
37 changes: 37 additions & 0 deletions build/git_revision.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"log"
"os"
"path/filepath"
"time"

"github.com/go-git/go-git/v5"
Expand Down Expand Up @@ -169,11 +170,47 @@ func (gr *GitRevision) Build(ctx context.Context) (string, error) {
gr.pathsToRemove = append(gr.pathsToRemove, installDir)
}

// copy license file on best effort basis
dstLicensePath := filepath.Join(installDir, "LICENSE.txt")
srcLicensePath := filepath.Join(repoDir, "LICENSE.txt")
altSrcLicensePath := filepath.Join(repoDir, "LICENSE")
if _, err := os.Stat(srcLicensePath); err == nil {
err = gr.copyLicenseFile(srcLicensePath, dstLicensePath)
if err != nil {
return "", err
}
} else if _, err := os.Stat(altSrcLicensePath); err == nil {
err = gr.copyLicenseFile(altSrcLicensePath, dstLicensePath)
if err != nil {
return "", err
}
}

gr.log().Printf("building %s (timeout: %s)", gr.Product.Name, buildTimeout)
defer gr.log().Printf("building of %s finished", gr.Product.Name)
return bi.Build.Build(buildCtx, repoDir, installDir, gr.Product.BinaryName())
}

func (gr *GitRevision) copyLicenseFile(srcPath, dstPath string) error {
src, err := os.Open(srcPath)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(dstPath)
if err != nil {
return err
}
defer dst.Close()
n, err := io.Copy(dst, src)
if err != nil {
return err
}
gr.log().Printf("license file copied from %q to %q (%d bytes)",
srcPath, dstPath, n)
return nil
}

func (gr *GitRevision) Remove(ctx context.Context) error {
if gr.pathsToRemove != nil {
for _, path := range gr.pathsToRemove {
Expand Down
17 changes: 16 additions & 1 deletion build/git_revision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package build
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/hashicorp/go-version"
Expand All @@ -32,7 +34,15 @@ func TestGitRevision_terraform(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { gr.Remove(ctx) })

licensePath := filepath.Join(filepath.Dir(execPath), "LICENSE.txt")
t.Cleanup(func() {
gr.Remove(ctx)
// check if license was deleted
if _, err := os.Stat(licensePath); !os.IsNotExist(err) {
t.Fatalf("license file not deleted at %q: %s", licensePath, err)
}
})

v, err := product.Terraform.GetVersion(ctx, execPath)
if err != nil {
Expand All @@ -47,6 +57,11 @@ func TestGitRevision_terraform(t *testing.T) {
t.Fatalf("versions don't match (expected: %s, installed: %s)",
latestConstraint, v)
}

// check if license was copied
if _, err := os.Stat(licensePath); err != nil {
t.Fatalf("expected license file not found at %q: %s", licensePath, err)
}
}

func TestGitRevision_consul(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions checkpoint/latest_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) {
if lv.ArmoredPublicKey != "" {
d.ArmoredPublicKey = lv.ArmoredPublicKey
}
zipFilePath, err := d.DownloadAndUnpack(ctx, pv, dstDir, "")
if zipFilePath != "" {
lv.pathsToRemove = append(lv.pathsToRemove, zipFilePath)
up, err := d.DownloadAndUnpack(ctx, pv, dstDir, "")
if up != nil {
lv.pathsToRemove = append(lv.pathsToRemove, up.PathsToRemove...)
}
if err != nil {
return "", err
Expand Down
17 changes: 16 additions & 1 deletion checkpoint/latest_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package checkpoint
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/hashicorp/go-version"
Expand Down Expand Up @@ -34,7 +36,15 @@ func TestLatestVersion(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { lv.Remove(ctx) })

licensePath := filepath.Join(filepath.Dir(execPath), "LICENSE.txt")
t.Cleanup(func() {
lv.Remove(ctx)
// check if license was deleted
if _, err := os.Stat(licensePath); !os.IsNotExist(err) {
t.Fatalf("license file not deleted at %q: %s", licensePath, err)
}
})

v, err := product.Terraform.GetVersion(ctx, execPath)
if err != nil {
Expand All @@ -49,6 +59,11 @@ func TestLatestVersion(t *testing.T) {
t.Fatalf("versions don't match (expected: %s, installed: %s)",
latestConstraint, v)
}

// check if license was copied
if _, err := os.Stat(licensePath); err != nil {
t.Fatalf("expected license file not found at %q: %s", licensePath, err)
}
}

func TestLatestVersionValidate(t *testing.T) {
Expand Down
60 changes: 37 additions & 23 deletions internal/releasesjson/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,18 @@ type Downloader struct {
BaseURL string
}

func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion, binDir string, licenseDir string) (zipFilePath string, err error) {
type UnpackedProduct struct {
PathsToRemove []string
}

func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion, binDir string, licenseDir string) (up *UnpackedProduct, err error) {
if len(pv.Builds) == 0 {
return "", fmt.Errorf("no builds found for %s %s", pv.Name, pv.Version)
return nil, fmt.Errorf("no builds found for %s %s", pv.Name, pv.Version)
}

pb, ok := pv.Builds.FilterBuild(runtime.GOOS, runtime.GOARCH, "zip")
if !ok {
return "", fmt.Errorf("no ZIP archive found for %s %s %s/%s",
return nil, fmt.Errorf("no ZIP archive found for %s %s %s/%s",
pv.Name, pv.Version, runtime.GOOS, runtime.GOARCH)
}

Expand All @@ -49,12 +53,12 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
}
verifiedChecksums, err := v.DownloadAndVerifyChecksums(ctx)
if err != nil {
return "", err
return nil, err
}
var ok bool
verifiedChecksum, ok = verifiedChecksums[pb.Filename]
if !ok {
return "", fmt.Errorf("no checksum found for %q", pb.Filename)
return nil, fmt.Errorf("no checksum found for %q", pb.Filename)
}
}

Expand All @@ -67,12 +71,12 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
// are still pointing to the mock server if one is set.
baseURL, err := url.Parse(d.BaseURL)
if err != nil {
return "", err
return nil, err
}

u, err := url.Parse(archiveURL)
if err != nil {
return "", err
return nil, err
}
u.Scheme = baseURL.Scheme
u.Host = baseURL.Host
Expand All @@ -83,15 +87,15 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,

req, err := http.NewRequestWithContext(ctx, http.MethodGet, archiveURL, nil)
if err != nil {
return "", fmt.Errorf("failed to create request for %q: %w", archiveURL, err)
return nil, fmt.Errorf("failed to create request for %q: %w", archiveURL, err)
}
resp, err := client.Do(req)
if err != nil {
return "", err
return nil, err
}

if resp.StatusCode != 200 {
return "", fmt.Errorf("failed to download ZIP archive from %q: %s", archiveURL, resp.Status)
return nil, fmt.Errorf("failed to download ZIP archive from %q: %s", archiveURL, resp.Status)
}

defer resp.Body.Close()
Expand All @@ -100,19 +104,22 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,

contentType := resp.Header.Get("content-type")
if !contentTypeIsZip(contentType) {
return "", fmt.Errorf("unexpected content-type: %s (expected any of %q)",
return nil, fmt.Errorf("unexpected content-type: %s (expected any of %q)",
contentType, zipMimeTypes)
}

expectedSize := resp.ContentLength

pkgFile, err := os.CreateTemp("", pb.Filename)
if err != nil {
return "", err
return nil, err
}
defer pkgFile.Close()
pkgFilePath, err := filepath.Abs(pkgFile.Name())

up = &UnpackedProduct{}
up.PathsToRemove = append(up.PathsToRemove, pkgFilePath)

d.Logger.Printf("copying %q (%d bytes) to %s", pb.Filename, expectedSize, pkgFile.Name())

var bytesCopied int64
Expand All @@ -123,35 +130,35 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,

bytesCopied, err = io.Copy(h, r)
if err != nil {
return "", err
return nil, err
}

calculatedSum := h.Sum(nil)
if !bytes.Equal(calculatedSum, verifiedChecksum) {
return pkgFilePath, fmt.Errorf(
return up, fmt.Errorf(
"checksum mismatch (expected: %x, got: %x)",
verifiedChecksum, calculatedSum,
)
}
} else {
bytesCopied, err = io.Copy(pkgFile, pkgReader)
if err != nil {
return pkgFilePath, err
return up, err
}
}

d.Logger.Printf("copied %d bytes to %s", bytesCopied, pkgFile.Name())

if expectedSize != 0 && bytesCopied != int64(expectedSize) {
return pkgFilePath, fmt.Errorf(
return up, fmt.Errorf(
"unexpected size (downloaded: %d, expected: %d)",
bytesCopied, expectedSize,
)
}

r, err := zip.OpenReader(pkgFile.Name())
if err != nil {
return pkgFilePath, err
return up, err
}
defer r.Close()

Expand All @@ -163,7 +170,7 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
}
srcFile, err := f.Open()
if err != nil {
return pkgFilePath, err
return up, err
}

// Determine the appropriate destination file path
Expand All @@ -174,20 +181,25 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,

d.Logger.Printf("unpacking %s to %s", f.Name, dstDir)
dstPath := filepath.Join(dstDir, f.Name)

if isLicenseFile(f.Name) {
up.PathsToRemove = append(up.PathsToRemove, dstPath)
}

dstFile, err := os.Create(dstPath)
if err != nil {
return pkgFilePath, err
return up, err
}

_, err = io.Copy(dstFile, srcFile)
if err != nil {
return pkgFilePath, err
return up, err
}
srcFile.Close()
dstFile.Close()
}

return pkgFilePath, nil
return up, nil
}

// The production release site uses consistent single mime type
Expand All @@ -207,11 +219,13 @@ func contentTypeIsZip(contentType string) bool {
return false
}

// Enterprise products have a few additional license files
// that need to be extracted to a separate directory
// Product archives may have a few license files
// which may be extracted to a separate directory
// and may need to be tracked for later cleanup.
var licenseFiles = []string{
"EULA.txt",
"TermsOfEvaluation.txt",
"LICENSE.txt",
}

func isLicenseFile(filename string) bool {
Expand Down
6 changes: 3 additions & 3 deletions releases/exact_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ func (ev *ExactVersion) Install(ctx context.Context) (string, error) {
if ev.Enterprise != nil {
licenseDir = ev.Enterprise.LicenseDir
}
zipFilePath, err := d.DownloadAndUnpack(ctx, pv, dstDir, licenseDir)
if zipFilePath != "" {
ev.pathsToRemove = append(ev.pathsToRemove, zipFilePath)
up, err := d.DownloadAndUnpack(ctx, pv, dstDir, licenseDir)
if up != nil {
ev.pathsToRemove = append(ev.pathsToRemove, up.PathsToRemove...)
}
if err != nil {
return "", err
Expand Down
6 changes: 3 additions & 3 deletions releases/latest_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) {
if lv.Enterprise != nil {
licenseDir = lv.Enterprise.LicenseDir
}
zipFilePath, err := d.DownloadAndUnpack(ctx, versionToInstall, dstDir, licenseDir)
if zipFilePath != "" {
lv.pathsToRemove = append(lv.pathsToRemove, zipFilePath)
up, err := d.DownloadAndUnpack(ctx, versionToInstall, dstDir, licenseDir)
if up != nil {
lv.pathsToRemove = append(lv.pathsToRemove, up.PathsToRemove...)
}
if err != nil {
return "", err
Expand Down
16 changes: 14 additions & 2 deletions releases/releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestLatestVersion_prereleases(t *testing.T) {
func TestExactVersion(t *testing.T) {
testutil.EndToEndTest(t)

versionToInstall := version.Must(version.NewVersion("1.1.0"))
versionToInstall := version.Must(version.NewVersion("1.8.2"))
ev := &ExactVersion{
Product: product.Terraform,
Version: versionToInstall,
Expand All @@ -139,7 +139,14 @@ func TestExactVersion(t *testing.T) {
t.Fatal(err)
}

t.Cleanup(func() { ev.Remove(ctx) })
licensePath := filepath.Join(filepath.Dir(execPath), "LICENSE.txt")
t.Cleanup(func() {
ev.Remove(ctx)
// check if license was deleted
if _, err := os.Stat(licensePath); !os.IsNotExist(err) {
t.Fatalf("license file not deleted at %q: %s", licensePath, err)
}
})

t.Logf("exec path of installed: %s", execPath)

Expand All @@ -152,6 +159,11 @@ func TestExactVersion(t *testing.T) {
t.Fatalf("versions don't match (expected: %s, installed: %s)",
versionToInstall, v)
}

// check if license was copied
if _, err := os.Stat(licensePath); err != nil {
t.Fatalf("expected license file not found at %q: %s", licensePath, err)
}
}

func BenchmarkExactVersion(b *testing.B) {
Expand Down

0 comments on commit 237ac6f

Please sign in to comment.