diff --git a/build/git_revision.go b/build/git_revision.go index ae36e5d..0ce7ba4 100644 --- a/build/git_revision.go +++ b/build/git_revision.go @@ -27,11 +27,21 @@ var ( discardLogger = log.New(io.Discard, "", 0) ) +const ( + // dstLicenseFileName is the name of the license file to be copied to the destination directory + dstLicenseFileName = "LICENSE.txt" +) + // GitRevision installs a particular git revision by cloning // the repository and building it per product BuildInstructions type GitRevision struct { - Product product.Product - InstallDir string + Product product.Product + InstallDir string + + // LicenseDir represents directory path where to install license files. + // If empty, license files will placed in the same directory as the binary. + LicenseDir string + Ref string CloneTimeout time.Duration BuildTimeout time.Duration @@ -169,21 +179,17 @@ func (gr *GitRevision) Build(ctx context.Context) (string, error) { installDir = tmpDir gr.pathsToRemove = append(gr.pathsToRemove, installDir) } + gr.log().Printf("install dir is %q", 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 - } + // default to installDir if LicenseDir is not set + licenseDir := gr.LicenseDir + if licenseDir == "" { + licenseDir = installDir + } + gr.log().Printf("attempting to copy license file to %q", licenseDir) + if err := gr.copyLicenseIfExists(repoDir, licenseDir); err != nil { + return "", err } gr.log().Printf("building %s (timeout: %s)", gr.Product.Name, buildTimeout) @@ -191,23 +197,44 @@ func (gr *GitRevision) Build(ctx context.Context) (string, error) { return bi.Build.Build(buildCtx, repoDir, installDir, gr.Product.BinaryName()) } +func (gr *GitRevision) copyLicenseIfExists(repoDir string, dstDir string) error { + licenseFiles := []string{"LICENSE.txt", "LICENSE"} + + for _, file := range licenseFiles { + srcPath := filepath.Join(repoDir, file) + if _, err := os.Stat(srcPath); err == nil { + gr.log().Printf("found license file at %q", srcPath) + dstPath := filepath.Join(dstDir, dstLicenseFileName) + if err := gr.copyLicenseFile(srcPath, dstPath); err != nil { + return fmt.Errorf("failed to copy license file from %q to %q: %w", srcPath, dstPath, err) + } + } + } + + return nil +} + func (gr *GitRevision) copyLicenseFile(srcPath, dstPath string) error { + gr.log().Printf("copying license file from %q to %q", srcPath, dstPath) src, err := os.Open(srcPath) if err != nil { - return err + return fmt.Errorf("failed to open license file at %q: %w", srcPath, err) } defer src.Close() + dst, err := os.Create(dstPath) if err != nil { - return err + return fmt.Errorf("failed to create license file at %q: %w", dstPath, err) } defer dst.Close() n, err := io.Copy(dst, src) if err != nil { - return err + return fmt.Errorf("failed to copy license file from %q to %q: %w", srcPath, dstPath, err) } gr.log().Printf("license file copied from %q to %q (%d bytes)", srcPath, dstPath, n) + // Add the license file to the list of paths to remove after being successfully copied + gr.pathsToRemove = append(gr.pathsToRemove, dstPath) return nil } @@ -216,7 +243,7 @@ func (gr *GitRevision) Remove(ctx context.Context) error { for _, path := range gr.pathsToRemove { err := os.RemoveAll(path) if err != nil { - return err + return fmt.Errorf("failed to remove %q: %w", path, err) } } } diff --git a/build/git_revision_test.go b/build/git_revision_test.go index 400e424..98d99b3 100644 --- a/build/git_revision_test.go +++ b/build/git_revision_test.go @@ -25,7 +25,12 @@ var ( func TestGitRevision_terraform(t *testing.T) { testutil.EndToEndTest(t) - gr := &GitRevision{Product: product.Terraform} + tempDir := t.TempDir() + + gr := &GitRevision{ + Product: product.Terraform, + LicenseDir: tempDir, + } gr.SetLogger(testutil.TestLogger()) ctx := context.Background() @@ -35,7 +40,7 @@ func TestGitRevision_terraform(t *testing.T) { t.Fatal(err) } - licensePath := filepath.Join(filepath.Dir(execPath), "LICENSE.txt") + licensePath := filepath.Join(tempDir, dstLicenseFileName) t.Cleanup(func() { gr.Remove(ctx) // check if license was deleted diff --git a/checkpoint/latest_version.go b/checkpoint/latest_version.go index 7a8aa3d..a382cb1 100644 --- a/checkpoint/latest_version.go +++ b/checkpoint/latest_version.go @@ -35,6 +35,10 @@ type LatestVersion struct { SkipChecksumVerification bool InstallDir string + // LicenseDir represents directory path where to install license files. + // If empty, license files will placed in the same directory as the binary. + LicenseDir string + // ArmoredPublicKey is a public PGP key in ASCII/armor format to use // instead of built-in pubkey to verify signature of downloaded checksums ArmoredPublicKey string @@ -126,7 +130,9 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) { if lv.ArmoredPublicKey != "" { d.ArmoredPublicKey = lv.ArmoredPublicKey } - up, err := d.DownloadAndUnpack(ctx, pv, dstDir, "") + + licenseDir := lv.LicenseDir + up, err := d.DownloadAndUnpack(ctx, pv, dstDir, licenseDir) if up != nil { lv.pathsToRemove = append(lv.pathsToRemove, up.PathsToRemove...) } diff --git a/installer_examples_test.go b/installer_examples_test.go index c3069c4..41f630f 100644 --- a/installer_examples_test.go +++ b/installer_examples_test.go @@ -142,11 +142,10 @@ func ExampleInstaller_enterpriseVersion() { execPath, err := i.Install(ctx, []src.Installable{ &releases.ExactVersion{ - Product: product.Vault, - Version: v1_9, - Enterprise: &releases.EnterpriseOptions{ // specify that we want the enterprise version - LicenseDir: licenseDir, // where license files should be placed (required for enterprise versions) - }, + Product: product.Vault, + Version: v1_9, + LicenseDir: licenseDir, // required for enterprise versions + Enterprise: &releases.EnterpriseOptions{}, // specify that we want the enterprise version }, }) if err != nil { diff --git a/installer_test.go b/installer_test.go index d29b776..436bac2 100644 --- a/installer_test.go +++ b/installer_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/hashicorp/go-version" - "github.com/hashicorp/hc-install" + install "github.com/hashicorp/hc-install" "github.com/hashicorp/hc-install/fs" "github.com/hashicorp/hc-install/internal/testutil" "github.com/hashicorp/hc-install/product" @@ -119,9 +119,8 @@ func TestInstaller_Install_enterprise(t *testing.T) { Product: product.Vault, Version: version.Must(version.NewVersion("1.9.8")), InstallDir: tmpBinaryDir, - Enterprise: &releases.EnterpriseOptions{ - LicenseDir: tmpLicenseDir, - }, + LicenseDir: tmpLicenseDir, + Enterprise: &releases.EnterpriseOptions{}, }, }) if err != nil { diff --git a/internal/releasesjson/downloader.go b/internal/releasesjson/downloader.go index a1139b5..b50fea0 100644 --- a/internal/releasesjson/downloader.go +++ b/internal/releasesjson/downloader.go @@ -175,6 +175,7 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion, // Determine the appropriate destination file path dstDir := binDir + // for license files, use binDir if licenseDir is not set if isLicenseFile(f.Name) && licenseDir != "" { dstDir = licenseDir } diff --git a/releases/enterprise.go b/releases/enterprise.go index 179d40d..cfef088 100644 --- a/releases/enterprise.go +++ b/releases/enterprise.go @@ -6,9 +6,6 @@ package releases import "fmt" type EnterpriseOptions struct { - // LicenseDir represents directory path where to install license files (required) - LicenseDir string - // Meta represents optional version metadata (e.g. hsm, fips1402) Meta string } @@ -25,12 +22,12 @@ func enterpriseVersionMetadata(eo *EnterpriseOptions) string { return metadata } -func validateEnterpriseOptions(eo *EnterpriseOptions) error { +func validateEnterpriseOptions(eo *EnterpriseOptions, licenseDir string) error { if eo == nil { return nil } - if eo.LicenseDir == "" { + if licenseDir == "" { return fmt.Errorf("LicenseDir must be provided when requesting enterprise versions") } diff --git a/releases/exact_version.go b/releases/exact_version.go index 179f0b4..597e9ae 100644 --- a/releases/exact_version.go +++ b/releases/exact_version.go @@ -27,6 +27,10 @@ type ExactVersion struct { InstallDir string Timeout time.Duration + // LicenseDir represents directory path where to install license files + // (required for enterprise versions, optional for Community editions). + LicenseDir string + // Enterprise indicates installation of enterprise version (leave nil for Community editions) Enterprise *EnterpriseOptions @@ -72,7 +76,7 @@ func (ev *ExactVersion) Validate() error { return fmt.Errorf("unknown version") } - if err := validateEnterpriseOptions(ev.Enterprise); err != nil { + if err := validateEnterpriseOptions(ev.Enterprise, ev.LicenseDir); err != nil { return err } @@ -131,10 +135,7 @@ func (ev *ExactVersion) Install(ctx context.Context) (string, error) { d.BaseURL = ev.ApiBaseURL } - licenseDir := "" - if ev.Enterprise != nil { - licenseDir = ev.Enterprise.LicenseDir - } + licenseDir := ev.LicenseDir up, err := d.DownloadAndUnpack(ctx, pv, dstDir, licenseDir) if up != nil { ev.pathsToRemove = append(ev.pathsToRemove, up.PathsToRemove...) diff --git a/releases/latest_version.go b/releases/latest_version.go index c4888f4..ee70782 100644 --- a/releases/latest_version.go +++ b/releases/latest_version.go @@ -27,6 +27,10 @@ type LatestVersion struct { Timeout time.Duration IncludePrereleases bool + // LicenseDir represents directory path where to install license files + // (required for enterprise versions, optional for Community editions). + LicenseDir string + // Enterprise indicates installation of enterprise version (leave nil for Community editions) Enterprise *EnterpriseOptions @@ -68,7 +72,7 @@ func (lv *LatestVersion) Validate() error { return fmt.Errorf("invalid binary name: %q", lv.Product.BinaryName()) } - if err := validateEnterpriseOptions(lv.Enterprise); err != nil { + if err := validateEnterpriseOptions(lv.Enterprise, lv.LicenseDir); err != nil { return err } @@ -131,10 +135,7 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) { if lv.ApiBaseURL != "" { d.BaseURL = lv.ApiBaseURL } - licenseDir := "" - if lv.Enterprise != nil { - licenseDir = lv.Enterprise.LicenseDir - } + licenseDir := lv.LicenseDir up, err := d.DownloadAndUnpack(ctx, versionToInstall, dstDir, licenseDir) if up != nil { lv.pathsToRemove = append(lv.pathsToRemove, up.PathsToRemove...) diff --git a/releases/versions.go b/releases/versions.go index 49b1af7..a431609 100644 --- a/releases/versions.go +++ b/releases/versions.go @@ -30,8 +30,9 @@ type Versions struct { } type InstallationOptions struct { - Timeout time.Duration - Dir string + Timeout time.Duration + Dir string + LicenseDir string SkipChecksumVerification bool @@ -46,7 +47,7 @@ func (v *Versions) List(ctx context.Context) ([]src.Source, error) { return nil, fmt.Errorf("invalid product name: %q", v.Product.Name) } - if err := validateEnterpriseOptions(v.Enterprise); err != nil { + if err := validateEnterpriseOptions(v.Enterprise, v.Install.LicenseDir); err != nil { return nil, err } @@ -85,6 +86,7 @@ func (v *Versions) List(ctx context.Context) ([]src.Source, error) { Version: pv.Version, InstallDir: v.Install.Dir, Timeout: v.Install.Timeout, + LicenseDir: v.Install.LicenseDir, ArmoredPublicKey: v.Install.ArmoredPublicKey, SkipChecksumVerification: v.Install.SkipChecksumVerification, @@ -92,8 +94,7 @@ func (v *Versions) List(ctx context.Context) ([]src.Source, error) { if v.Enterprise != nil { ev.Enterprise = &EnterpriseOptions{ - Meta: v.Enterprise.Meta, - LicenseDir: v.Enterprise.LicenseDir, + Meta: v.Enterprise.Meta, } } diff --git a/releases/versions_test.go b/releases/versions_test.go index 5ba23f9..a4aa5c1 100644 --- a/releases/versions_test.go +++ b/releases/versions_test.go @@ -61,10 +61,12 @@ func TestVersions_List_enterprise(t *testing.T) { versions := &Versions{ Product: product.Vault, Constraints: cons, - Enterprise: &EnterpriseOptions{ - Meta: "hsm", + Install: InstallationOptions{ LicenseDir: "/some/path", }, + Enterprise: &EnterpriseOptions{ + Meta: "hsm", + }, } ctx := context.Background()