Skip to content

Commit 60401e6

Browse files
authored
ci: run integration test against mainline and stable versions (#248)
- Adds `scripts/coderversion` to fetch stable and mainline versions - Adds `minVersion` constraint to integration tests - Runs integration tests against both stable and mainline versions - Adds separate integration test for fields introduced in v2.12.0
1 parent e6ba29a commit 60401e6

File tree

7 files changed

+228
-16
lines changed

7 files changed

+228
-16
lines changed

.github/workflows/test.yml

+11-3
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,21 @@ jobs:
3838
run: |
3939
go build -v .
4040
41-
- name: Run integration test
41+
- name: Run integration test (mainline)
4242
timeout-minutes: 10
4343
env:
4444
CODER_IMAGE: "ghcr.io/coder/coder"
45-
CODER_VERSION: "latest"
4645
run: |
47-
go test -v ./integration
46+
source <(go run ./scripts/coderversion)
47+
CODER_VERSION="${CODER_MAINLINE_VERSION}" go test -v ./integration
48+
49+
- name: Run integration test (stable)
50+
timeout-minutes: 10
51+
env:
52+
CODER_IMAGE: "ghcr.io/coder/coder"
53+
run: |
54+
source <(go run ./scripts/coderversion)
55+
CODER_VERSION="${CODER_STABLE_VERSION}" go test -v ./integration
4856
4957
# run acceptance tests in a matrix with Terraform core versions
5058
test:

go.mod

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ require (
99
github.com/google/uuid v1.6.0
1010
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
1111
github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0
12+
github.com/masterminds/semver v1.5.0
1213
github.com/mitchellh/mapstructure v1.5.0
1314
github.com/robfig/cron/v3 v3.0.1
1415
github.com/stretchr/testify v1.9.0
16+
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
17+
golang.org/x/mod v0.18.0
1518
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
1619
)
1720

1821
require (
22+
github.com/Masterminds/semver v1.5.0 // indirect
1923
github.com/Microsoft/go-winio v0.5.2 // indirect
2024
github.com/agext/levenshtein v1.2.3 // indirect
2125
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
@@ -76,8 +80,6 @@ require (
7680
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
7781
go.opentelemetry.io/otel/trace v1.27.0 // indirect
7882
golang.org/x/crypto v0.23.0 // indirect
79-
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
80-
golang.org/x/mod v0.18.0 // indirect
8183
golang.org/x/net v0.25.0 // indirect
8284
golang.org/x/sys v0.20.0 // indirect
8385
golang.org/x/text v0.15.0 // indirect

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
22
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
33
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
4+
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
5+
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
46
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
57
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
68
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
@@ -147,6 +149,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
147149
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
148150
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
149151
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
152+
github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U=
153+
github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg=
150154
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
151155
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
152156
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=

integration/integration_test.go

+35-2
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,19 @@ func TestIntegration(t *testing.T) {
5757
require.NoError(t, err, "invalid value specified for timeout")
5858
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMins)*time.Minute)
5959
t.Cleanup(cancel)
60+
ctrID := setup(ctx, t, t.Name(), coderImg, coderVersion)
6061

6162
for _, tt := range []struct {
6263
// Name of the folder under `integration/` containing a test template
6364
name string
65+
// Minimum coder version for which to run this test
66+
minVersion string
6467
// map of string to regex to be passed to assertOutput()
6568
expectedOutput map[string]string
6669
}{
6770
{
68-
name: "test-data-source",
71+
name: "test-data-source",
72+
minVersion: "v0.0.0",
6973
expectedOutput: map[string]string{
7074
"provisioner.arch": runtime.GOARCH,
7175
"provisioner.id": `[a-zA-Z0-9-]+`,
@@ -86,6 +90,31 @@ func TestIntegration(t *testing.T) {
8690
"workspace.template_name": `test-data-source`,
8791
"workspace.template_version": `.+`,
8892
"workspace.transition": `start`,
93+
},
94+
},
95+
{
96+
name: "workspace-owner",
97+
minVersion: "v2.12.0",
98+
expectedOutput: map[string]string{
99+
"provisioner.arch": runtime.GOARCH,
100+
"provisioner.id": `[a-zA-Z0-9-]+`,
101+
"provisioner.os": runtime.GOOS,
102+
"workspace.access_port": `\d+`,
103+
"workspace.access_url": `https?://\D+:\d+`,
104+
"workspace.id": `[a-zA-z0-9-]+`,
105+
"workspace.name": ``,
106+
"workspace.owner": `testing`,
107+
"workspace.owner_email": `testing@coder\.com`,
108+
"workspace.owner_groups": `\[\]`,
109+
"workspace.owner_id": `[a-zA-Z0-9]+`,
110+
"workspace.owner_name": `default`,
111+
"workspace.owner_oidc_access_token": `^$`, // TODO: need a test OIDC integration
112+
"workspace.owner_session_token": `[a-zA-Z0-9-]+`,
113+
"workspace.start_count": `1`,
114+
"workspace.template_id": `[a-zA-Z0-9-]+`,
115+
"workspace.template_name": `workspace-owner`,
116+
"workspace.template_version": `.+`,
117+
"workspace.transition": `start`,
89118
"workspace_owner.email": `testing@coder\.com`,
90119
"workspace_owner.full_name": `default`,
91120
"workspace_owner.groups": `\[\]`,
@@ -98,9 +127,13 @@ func TestIntegration(t *testing.T) {
98127
},
99128
},
100129
} {
130+
tt := tt
101131
t.Run(tt.name, func(t *testing.T) {
132+
t.Parallel()
133+
if coderVersion != "latest" && semver.Compare(coderVersion, tt.minVersion) < 0 {
134+
t.Skipf("skipping due to CODER_VERSION %q < minVersion %q", coderVersion, tt.minVersion)
135+
}
102136
// Given: we have an existing Coder deployment running locally
103-
ctrID := setup(ctx, t, tt.name, coderImg, coderVersion)
104137
// Import named template
105138

106139
// NOTE: Template create command was deprecated after this version

integration/test-data-source/main.tf

-9
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,6 @@ locals {
3838
"workspace.template_name" : data.coder_workspace.me.template_name,
3939
"workspace.template_version" : data.coder_workspace.me.template_version,
4040
"workspace.transition" : data.coder_workspace.me.transition,
41-
"workspace_owner.email" : data.coder_workspace_owner.me.email,
42-
"workspace_owner.full_name" : data.coder_workspace_owner.me.full_name,
43-
"workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups),
44-
"workspace_owner.id" : data.coder_workspace_owner.me.id,
45-
"workspace_owner.name" : data.coder_workspace_owner.me.name,
46-
"workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token,
47-
"workspace_owner.session_token" : data.coder_workspace_owner.me.session_token,
48-
"workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key,
49-
"workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key,
5041
}
5142
}
5243

integration/workspace-owner/main.tf

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
}
6+
local = {
7+
source = "hashicorp/local"
8+
}
9+
}
10+
}
11+
12+
// TODO: test coder_external_auth and coder_git_auth
13+
// data coder_external_auth "me" {}
14+
// data coder_git_auth "me" {}
15+
data "coder_provisioner" "me" {}
16+
data "coder_workspace" "me" {}
17+
data "coder_workspace_owner" "me" {}
18+
19+
locals {
20+
# NOTE: these must all be strings in the output
21+
output = {
22+
"provisioner.arch" : data.coder_provisioner.me.arch,
23+
"provisioner.id" : data.coder_provisioner.me.id,
24+
"provisioner.os" : data.coder_provisioner.me.os,
25+
"workspace.access_port" : tostring(data.coder_workspace.me.access_port),
26+
"workspace.access_url" : data.coder_workspace.me.access_url,
27+
"workspace.id" : data.coder_workspace.me.id,
28+
"workspace.name" : data.coder_workspace.me.name,
29+
"workspace.owner" : data.coder_workspace.me.owner,
30+
"workspace.owner_email" : data.coder_workspace.me.owner_email,
31+
"workspace.owner_groups" : jsonencode(data.coder_workspace.me.owner_groups),
32+
"workspace.owner_id" : data.coder_workspace.me.owner_id,
33+
"workspace.owner_name" : data.coder_workspace.me.owner_name,
34+
"workspace.owner_oidc_access_token" : data.coder_workspace.me.owner_oidc_access_token,
35+
"workspace.owner_session_token" : data.coder_workspace.me.owner_session_token,
36+
"workspace.start_count" : tostring(data.coder_workspace.me.start_count),
37+
"workspace.template_id" : data.coder_workspace.me.template_id,
38+
"workspace.template_name" : data.coder_workspace.me.template_name,
39+
"workspace.template_version" : data.coder_workspace.me.template_version,
40+
"workspace.transition" : data.coder_workspace.me.transition,
41+
"workspace_owner.email" : data.coder_workspace_owner.me.email,
42+
"workspace_owner.full_name" : data.coder_workspace_owner.me.full_name,
43+
"workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups),
44+
"workspace_owner.id" : data.coder_workspace_owner.me.id,
45+
"workspace_owner.name" : data.coder_workspace_owner.me.name,
46+
"workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token,
47+
"workspace_owner.session_token" : data.coder_workspace_owner.me.session_token,
48+
"workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key,
49+
"workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key,
50+
}
51+
}
52+
53+
variable "output_path" {
54+
type = string
55+
}
56+
57+
resource "local_file" "output" {
58+
filename = var.output_path
59+
content = jsonencode(local.output)
60+
}
61+
62+
output "output" {
63+
value = local.output
64+
sensitive = true
65+
}

scripts/coderversion/main.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
9+
"github.com/masterminds/semver"
10+
)
11+
12+
func main() {
13+
releases := fetchReleases()
14+
15+
mainlineVer := semver.MustParse("v0.0.0")
16+
for _, rel := range releases {
17+
if rel == "" {
18+
debug("ignoring untagged version %s\n", rel)
19+
continue
20+
}
21+
22+
ver, err := semver.NewVersion(rel)
23+
if err != nil {
24+
debug("skipping invalid version %s\n", rel)
25+
}
26+
27+
if ver.Compare(mainlineVer) > 0 {
28+
mainlineVer = ver
29+
continue
30+
}
31+
}
32+
33+
mainline := fmt.Sprintf("v%d.%d.%d", mainlineVer.Major(), mainlineVer.Minor(), mainlineVer.Patch())
34+
_, _ = fmt.Fprintf(os.Stdout, "CODER_MAINLINE_VERSION=%q\n", mainline)
35+
36+
expectedStableMinor := mainlineVer.Minor() - 1
37+
if expectedStableMinor < 0 {
38+
expectedStableMinor = 0
39+
}
40+
debug("expected stable minor: %d\n", expectedStableMinor)
41+
stableVer := semver.MustParse("v0.0.0")
42+
for _, rel := range releases {
43+
debug("check version %s\n", rel)
44+
if rel == "" {
45+
debug("ignoring untagged version %s\n", rel)
46+
continue
47+
}
48+
49+
ver, err := semver.NewVersion(rel)
50+
if err != nil {
51+
debug("skipping invalid version %s\n", rel)
52+
}
53+
54+
if ver.Minor() != expectedStableMinor {
55+
debug("skipping version %s\n", rel)
56+
continue
57+
}
58+
59+
if ver.Compare(stableVer) > 0 {
60+
stableVer = ver
61+
continue
62+
}
63+
}
64+
65+
stable := fmt.Sprintf("v%d.%d.%d", stableVer.Major(), stableVer.Minor(), stableVer.Patch())
66+
_, _ = fmt.Fprintf(os.Stdout, "CODER_STABLE_VERSION=%q\n", stable)
67+
}
68+
69+
type release struct {
70+
TagName string `json:"tag_name"`
71+
}
72+
73+
const releasesURL = "https://api.github.com/repos/coder/coder/releases"
74+
75+
// fetchReleases fetches the releases of coder/coder
76+
// this is done directly via JSON API to avoid pulling in the entire
77+
// github client
78+
func fetchReleases() []string {
79+
resp, err := http.Get(releasesURL)
80+
if err != nil {
81+
fatal("get releases: %w", err)
82+
}
83+
defer resp.Body.Close()
84+
85+
var releases []release
86+
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
87+
fatal("parse releases: %w", err)
88+
}
89+
90+
var ss []string
91+
for _, rel := range releases {
92+
if rel.TagName != "" {
93+
ss = append(ss, rel.TagName)
94+
95+
}
96+
}
97+
return ss
98+
}
99+
100+
func debug(format string, args ...any) {
101+
if _, ok := os.LookupEnv("VERBOSE"); ok {
102+
_, _ = fmt.Fprintf(os.Stderr, format, args...)
103+
}
104+
}
105+
106+
func fatal(format string, args ...any) {
107+
_, _ = fmt.Fprintf(os.Stderr, format, args...)
108+
os.Exit(1)
109+
}

0 commit comments

Comments
 (0)