From 27bf167ab3030b78c32ff78fffa153b25a914e64 Mon Sep 17 00:00:00 2001
From: Cian Johnston <cian@coder.com>
Date: Thu, 27 Jun 2024 10:29:54 +0100
Subject: [PATCH 1/2] chore: improve integration test compatibility with older
 Coder versions

---
 go.mod                          |  2 +
 go.sum                          |  4 ++
 integration/integration_test.go | 67 +++++++++++++++++++++++----------
 3 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/go.mod b/go.mod
index 625aae19..b5e26e56 100644
--- a/go.mod
+++ b/go.mod
@@ -76,6 +76,8 @@ require (
 	go.opentelemetry.io/otel/sdk v1.27.0 // indirect
 	go.opentelemetry.io/otel/trace v1.27.0 // indirect
 	golang.org/x/crypto v0.23.0 // indirect
+	golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
+	golang.org/x/mod v0.18.0 // indirect
 	golang.org/x/net v0.25.0 // indirect
 	golang.org/x/sys v0.20.0 // indirect
 	golang.org/x/text v0.15.0 // indirect
diff --git a/go.sum b/go.sum
index d07d5ff3..047612ff 100644
--- a/go.sum
+++ b/go.sum
@@ -252,9 +252,13 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
+golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
diff --git a/integration/integration_test.go b/integration/integration_test.go
index cf58b99e..e23d053b 100644
--- a/integration/integration_test.go
+++ b/integration/integration_test.go
@@ -21,6 +21,8 @@ import (
 	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/exp/slices"
+	"golang.org/x/mod/semver"
 )
 
 // TestIntegration performs an integration test against an ephemeral Coder deployment.
@@ -37,6 +39,16 @@ func TestIntegration(t *testing.T) {
 		t.Skip("Skipping integration tests during tf acceptance tests")
 	}
 
+	coderImg := os.Getenv("CODER_IMAGE")
+	if coderImg == "" {
+		coderImg = "ghcr.io/coder/coder"
+	}
+
+	coderVersion := os.Getenv("CODER_VERSION")
+	if coderVersion == "" {
+		coderVersion = "latest"
+	}
+
 	timeoutStr := os.Getenv("TIMEOUT_MINS")
 	if timeoutStr == "" {
 		timeoutStr = "10"
@@ -88,9 +100,17 @@ func TestIntegration(t *testing.T) {
 	} {
 		t.Run(tt.name, func(t *testing.T) {
 			// Given: we have an existing Coder deployment running locally
-			ctrID := setup(ctx, t, tt.name)
+			ctrID := setup(ctx, t, tt.name, coderImg, coderVersion)
 			// Import named template
-			_, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates push %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, tt.name, tt.name, tt.name))
+
+			// NOTE: Template create command was deprecated after this version
+			// ref: https://github.com/coder/coder/pull/11390
+			templateCreateCmd := "push"
+			if semver.Compare(coderVersion, "v2.7.0") < 1 {
+				t.Logf("using now-deprecated templates create command for older coder version")
+				templateCreateCmd = "create"
+			}
+			_, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates %s %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, templateCreateCmd, tt.name, tt.name, tt.name))
 			require.Equal(t, 0, rc)
 			// Create a workspace
 			_, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s --yes`, tt.name, tt.name))
@@ -105,7 +125,7 @@ func TestIntegration(t *testing.T) {
 	}
 }
 
-func setup(ctx context.Context, t *testing.T, name string) string {
+func setup(ctx context.Context, t *testing.T, name, coderImg, coderVersion string) string {
 	var (
 		// For this test to work, we pass in a custom terraformrc to use
 		// the locally built version of the provider.
@@ -118,16 +138,6 @@ func setup(ctx context.Context, t *testing.T, name string) string {
 		localURL = "http://localhost:3000"
 	)
 
-	coderImg := os.Getenv("CODER_IMAGE")
-	if coderImg == "" {
-		coderImg = "ghcr.io/coder/coder"
-	}
-
-	coderVersion := os.Getenv("CODER_VERSION")
-	if coderVersion == "" {
-		coderVersion = "latest"
-	}
-
 	t.Logf("using coder image %s:%s", coderImg, coderVersion)
 
 	// Ensure the binary is built
@@ -151,11 +161,7 @@ func setup(ctx context.Context, t *testing.T, name string) string {
 
 	// Ensure the image is available locally.
 	refStr := coderImg + ":" + coderVersion
-	t.Logf("ensuring image %q", refStr)
-	resp, err := cli.ImagePull(ctx, refStr, image.PullOptions{})
-	require.NoError(t, err)
-	_, err = io.ReadAll(resp)
-	require.NoError(t, err)
+	ensureImage(ctx, t, cli, refStr)
 
 	// Stand up a temporary Coder instance
 	ctr, err := cli.ContainerCreate(ctx, &container.Config{
@@ -213,6 +219,29 @@ func setup(ctx context.Context, t *testing.T, name string) string {
 	return ctr.ID
 }
 
+func ensureImage(ctx context.Context, t *testing.T, cli *client.Client, ref string) {
+	t.Helper()
+
+	t.Logf("ensuring image %q", ref)
+	images, err := cli.ImageList(ctx, image.ListOptions{})
+	require.NoError(t, err, "list images")
+	var found bool
+	for _, img := range images {
+		if slices.Contains(img.RepoTags, ref) {
+			t.Logf("image %q found locally, not pulling", ref)
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Logf("image %s not found locally, attempting to pull", ref)
+		resp, err := cli.ImagePull(ctx, ref, image.PullOptions{})
+		require.NoError(t, err)
+		_, err = io.ReadAll(resp)
+		require.NoError(t, err)
+	}
+}
+
 // execContainer executes the given command in the given container and returns
 // the output and the exit code of the command.
 func execContainer(ctx context.Context, t *testing.T, containerID, command string) (string, int) {
@@ -249,7 +278,7 @@ func assertOutput(t *testing.T, expected, actual map[string]string) {
 
 	for expectedKey, expectedValExpr := range expected {
 		actualVal := actual[expectedKey]
-		assert.Regexp(t, expectedValExpr, actualVal)
+		assert.Regexp(t, expectedValExpr, actualVal, "output key %q does not have expected value", expectedKey)
 	}
 	for actualKey := range actual {
 		_, ok := expected[actualKey]

From a6e2e7139a27df38175b49a53be000f50924c50b Mon Sep 17 00:00:00 2001
From: Cian Johnston <cian@coder.com>
Date: Thu, 27 Jun 2024 10:33:30 +0100
Subject: [PATCH 2/2] ensureImage: early return instead of break

---
 integration/integration_test.go | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/integration/integration_test.go b/integration/integration_test.go
index e23d053b..49b36456 100644
--- a/integration/integration_test.go
+++ b/integration/integration_test.go
@@ -225,21 +225,17 @@ func ensureImage(ctx context.Context, t *testing.T, cli *client.Client, ref stri
 	t.Logf("ensuring image %q", ref)
 	images, err := cli.ImageList(ctx, image.ListOptions{})
 	require.NoError(t, err, "list images")
-	var found bool
 	for _, img := range images {
 		if slices.Contains(img.RepoTags, ref) {
 			t.Logf("image %q found locally, not pulling", ref)
-			found = true
-			break
+			return
 		}
 	}
-	if !found {
-		t.Logf("image %s not found locally, attempting to pull", ref)
-		resp, err := cli.ImagePull(ctx, ref, image.PullOptions{})
-		require.NoError(t, err)
-		_, err = io.ReadAll(resp)
-		require.NoError(t, err)
-	}
+	t.Logf("image %s not found locally, attempting to pull", ref)
+	resp, err := cli.ImagePull(ctx, ref, image.PullOptions{})
+	require.NoError(t, err)
+	_, err = io.ReadAll(resp)
+	require.NoError(t, err)
 }
 
 // execContainer executes the given command in the given container and returns