Repository: replicatedhq/kots
Pull request #181 (2019-12-05 18:02:57)

Load version label from airgap metadata if it's available


Patch:
diff --git a/ffi/airgap.go b/ffi/airgap.go
index b33e2acea..3861ebf83 100644
--- a/ffi/airgap.go
+++ b/ffi/airgap.go
@@ -75,6 +75,7 @@ func PullFromAirgap(socket, licenseData, airgapDir, downstream, outputFile, regi
 			Downstreams:         []string{downstream},
 			LocalPath:           releaseDir,
 			LicenseFile:         licenseFile,
+			AirgapRoot:          airgapDir,
 			ExcludeKotsKinds:    true,
 			RootDir:             tmpRoot,
 			ExcludeAdminConsole: true,
diff --git a/pkg/pull/pull.go b/pkg/pull/pull.go
index 28d1efe6b..19dfc6d09 100644
--- a/pkg/pull/pull.go
+++ b/pkg/pull/pull.go
@@ -28,6 +28,7 @@ type PullOptions struct {
 	Downstreams         []string
 	LocalPath           string
 	LicenseFile         string
+	AirgapRoot          string
 	ConfigFile          string
 	UpdateCursor        string
 	ExcludeKotsKinds    bool
@@ -131,6 +132,13 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
 		}
 		fetchOptions.ConfigValues = config
 	}
+	if pullOptions.AirgapRoot != "" {
+		airgap, err := findAirgapMetaInDir(pullOptions.AirgapRoot)
+		if err != nil {
+			return "", errors.Wrap(err, "failed to parse license from file")
+		}
+		fetchOptions.Airgap = airgap
+	}
 
 	log.ActionWithSpinner("Pulling upstream")
 	u, err := upstream.FetchUpstream(upstreamURI, &fetchOptions)
@@ -391,6 +399,44 @@ func parseConfigValuesFromFile(filename string) (*kotsv1beta1.ConfigValues, erro
 	return config, nil
 }
 
+func findAirgapMetaInDir(root string) (*kotsv1beta1.Airgap, error) {
+	files, err := ioutil.ReadDir(root)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil, nil
+		}
+		return nil, errors.Wrap(err, "failed to read airgap directory content")
+	}
+
+	for _, file := range files {
+		if file.IsDir() {
+			continue
+		}
+
+		contents, err := ioutil.ReadFile(filepath.Join(root, file.Name()))
+		if err != nil {
+			// TODO: log
+			continue
+		}
+
+		decode := scheme.Codecs.UniversalDeserializer().Decode
+		decoded, gvk, err := decode(contents, nil, nil)
+		if err != nil {
+			// TODO: log
+			continue
+		}
+
+		if gvk.Group != "kots.io" || gvk.Version != "v1beta1" || gvk.Kind != "Airgap" {
+			continue
+		}
+
+		airgap := decoded.(*kotsv1beta1.Airgap)
+		return airgap, nil
+	}
+
+	return nil, nil
+}
+
 func imagesDirFromOptions(upstream *upstream.Upstream, pullOptions PullOptions) string {
 	if pullOptions.RewriteImageOptions.ImageFiles != "" {
 		return pullOptions.RewriteImageOptions.ImageFiles
diff --git a/pkg/upstream/fetch.go b/pkg/upstream/fetch.go
index 965f1fa68..63b8d5c30 100644
--- a/pkg/upstream/fetch.go
+++ b/pkg/upstream/fetch.go
@@ -17,6 +17,7 @@ type FetchOptions struct {
 	LocalPath           string
 	License             *kotsv1beta1.License
 	ConfigValues        *kotsv1beta1.ConfigValues
+	Airgap              *kotsv1beta1.Airgap
 	CurrentCursor       string
 	CurrentVersionLabel string
 }
@@ -43,7 +44,7 @@ func downloadUpstream(upstreamURI string, fetchOptions *FetchOptions) (*Upstream
 		return downloadHelm(u, fetchOptions.HelmRepoURI)
 	}
 	if u.Scheme == "replicated" {
-		return downloadReplicated(u, fetchOptions.LocalPath, fetchOptions.RootDir, fetchOptions.UseAppDir, fetchOptions.License, fetchOptions.ConfigValues, fetchOptions.CurrentCursor, fetchOptions.CurrentVersionLabel)
+		return downloadReplicated(u, fetchOptions.LocalPath, fetchOptions.RootDir, fetchOptions.UseAppDir, fetchOptions.License, fetchOptions.ConfigValues, fetchOptions.CurrentCursor, pickVersionLabel(fetchOptions))
 	}
 	if u.Scheme == "git" {
 		return downloadGit(upstreamURI)
@@ -54,3 +55,10 @@ func downloadUpstream(upstreamURI string, fetchOptions *FetchOptions) (*Upstream
 
 	return nil, errors.Errorf("unknown protocol scheme %q", u.Scheme)
 }
+
+func pickVersionLabel(fetchOptions *FetchOptions) string {
+	if fetchOptions.Airgap != nil && fetchOptions.Airgap.Spec.VersionLabel != "" {
+		return fetchOptions.Airgap.Spec.VersionLabel
+	}
+	return fetchOptions.CurrentVersionLabel
+}


Test patch:
diff --git a/pkg/upstream/fetch_test.go b/pkg/upstream/fetch_test.go
new file mode 100644
index 000000000..39d053299
--- /dev/null
+++ b/pkg/upstream/fetch_test.go
@@ -0,0 +1,84 @@
+package upstream
+
+import (
+	"encoding/base64"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func Test_downloadUpstream(t *testing.T) {
+	req := require.New(t)
+
+	srcDir, err := ioutil.TempDir("", "downloadUpstream")
+	req.NoError(err)
+	defer os.RemoveAll(srcDir)
+
+	workDir, err := ioutil.TempDir("", "workDir")
+	req.NoError(err)
+	defer os.RemoveAll(workDir)
+
+	releaseFiles := map[string]string{
+		// empty tar.gz file, base64 encoded
+		"app.tar.gz": `H4sIAG5/6V0AA+3PSwrCMBQF0CwlK9AkNWY9glQKouBv/RZbnOkss3Mmd3J53LfZhu5SSq3W+Mn9kqns
+llzFPNRWSmt1qDHlUnIOsfafFsLz/jjc5inH6XU9n6bLr95cG8c/d9Y/vgkAAAAAAAAAAAAdvQGTLU7p
+ACgAAA==`,
+	}
+
+	for name, data := range releaseFiles {
+		content, err := base64.StdEncoding.DecodeString(data)
+		req.NoError(err)
+		err = ioutil.WriteFile(filepath.Join(srcDir, name), content, 0644)
+		req.NoError(err)
+	}
+
+	tests := []struct {
+		airgapVersionLabel  string
+		currentVersionLabel string
+		expectedLabel       string
+	}{
+		{
+			airgapVersionLabel:  "10.9.8",
+			currentVersionLabel: "1.2.0",
+			expectedLabel:       "10.9.8",
+		},
+		{
+			airgapVersionLabel:  "",
+			currentVersionLabel: "1.2.0",
+			expectedLabel:       "1.2.0",
+		},
+	}
+
+	for _, test := range tests {
+		fetchOptions := &FetchOptions{
+			RootDir:             workDir,
+			LocalPath:           srcDir,
+			CurrentVersionLabel: test.currentVersionLabel,
+			License: &kotsv1beta1.License{
+				Spec: kotsv1beta1.LicenseSpec{
+					Endpoint: "http://localhost",
+					AppSlug:  "app-slug",
+				},
+			},
+			Airgap: &kotsv1beta1.Airgap{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "kots.io/v1beta1",
+					Kind:       "Airgap",
+				},
+				Spec: kotsv1beta1.AirgapSpec{
+					VersionLabel: test.airgapVersionLabel,
+				},
+			},
+		}
+
+		u, err := FetchUpstream("replicated://app-slug", fetchOptions)
+		req.NoError(err)
+		assert.Equal(t, test.expectedLabel, u.VersionLabel)
+	}
+}