Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,17 @@ jobs:
strategy:
fail-fast: false
matrix:
# list whatever Terraform versions here you would like to support
terraform:
- "1.0.*"
- "1.1.*"
- "1.2.*"
- "1.3.*"
- "1.4.*"
- "1.5.*"
Comment thread
ethanndickson marked this conversation as resolved.
- "1.6.*"
- "1.7.*"
- "1.8.*"
- "1.9.*"
- "1.10.*"
- "1.11.*"
- "1.12.*"
- "1.13.*"
- "1.14.*"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

Expand Down Expand Up @@ -118,7 +117,7 @@ jobs:

- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
terraform_version: "1.9.*"
terraform_version: "1.11.*"
terraform_wrapper: false

- name: Get dependencies
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The provider currently supports resources and data sources for:

## Requirements

- [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.0
- [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.5
- [Go](https://golang.org/doc/install) >= 1.21
- [Coder](https://github.com/coder/coder) >= 2.10.1

Expand Down
115 changes: 102 additions & 13 deletions integration/integration.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package integration

import (
"bytes"
"context"
"fmt"
"io"
Expand All @@ -13,7 +14,9 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/go-connections/nat"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -67,7 +70,6 @@ func StartCoder(ctx context.Context, t *testing.T, name string, options ...func(
option(&opts)
}

// Env vars override user-selected options.
if v, ok := os.LookupEnv("CODER_IMAGE"); ok {
opts.image = v
}
Expand All @@ -83,28 +85,40 @@ func StartCoder(ctx context.Context, t *testing.T, name string, options ...func(
t.Skip("Skipping tests that require a license.")
}

ref := opts.image + ":" + opts.version
t.Logf("using coder image %s:%s", opts.image, opts.version)

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
require.NoError(t, err, "init docker client")

p := randomPort(t)
t.Logf("random port is %d", p)
// Stand up a temporary Coder instance
puller, err := cli.ImagePull(ctx, opts.image+":"+opts.version, image.PullOptions{})
require.NoError(t, err, "pull coder image")
defer func() {
if err := puller.Close(); err != nil {
t.Logf("error closing image puller: %v", err)

// Give Coder an external PostgreSQL on a per-test network instead of its
// embedded one, which downloads a binary from Maven at startup (a flaky,
// rate-limited fetch that reds CI lanes).
netName := "terraform-provider-coderd-" + name + "-net"
net, err := cli.NetworkCreate(ctx, netName, network.CreateOptions{})
require.NoError(t, err, "create test network")
t.Cleanup(func() {
// t.Context() is canceled before t.Cleanup callbacks run, so use a
// fresh context or the removal is a no-op and the network leaks.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := cli.NetworkRemove(ctx, net.ID); err != nil {
Comment thread
ethanndickson marked this conversation as resolved.
t.Logf("error removing network %s: %v", net.ID, err)
}
}()
_, err = io.Copy(os.Stderr, puller)
require.NoError(t, err, "pull coder image")
})
startPostgres(ctx, t, cli, netName)

// Stand up a temporary Coder instance.
pullImage(ctx, t, cli, ref)

env := []string{
"CODER_HTTP_ADDRESS=0.0.0.0:3000", // Listen on all interfaces inside the container.
"CODER_ACCESS_URL=http://localhost:3000", // Avoid creating try.coder.app URLs.
"CODER_TELEMETRY_ENABLE=false", // Avoid creating noise.
"CODER_PG_CONNECTION_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable",
}
if !opts.enableRateLimits {
env = append(env, "CODER_DANGEROUS_DISABLE_RATE_LIMITS=true")
Expand All @@ -114,25 +128,36 @@ func StartCoder(ctx context.Context, t *testing.T, name string, options ...func(
}

ctr, err := cli.ContainerCreate(ctx, &container.Config{
Image: opts.image + ":" + opts.version,
Image: ref,
Env: env,
Labels: map[string]string{},
ExposedPorts: map[nat.Port]struct{}{nat.Port("3000/tcp"): {}},
}, &container.HostConfig{
PortBindings: map[nat.Port][]nat.PortBinding{
nat.Port("3000/tcp"): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", p)}},
},
}, nil, nil, "terraform-provider-coderd-"+name)
}, &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{netName: {}},
}, nil, "terraform-provider-coderd-"+name)
require.NoError(t, err, "create test deployment")

t.Logf("created container %s\n", ctr.ID)
t.Cleanup(func() { // Make sure we clean up after ourselves.
// TODO: also have this execute if you Ctrl+C!
t.Logf("stopping container %s\n", ctr.ID)
// t.Context() is canceled before t.Cleanup callbacks run, so use a
// fresh context or the removal is a no-op and the container leaks.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
Force: true,
})
})
t.Cleanup(func() {
if t.Failed() {
dumpContainerLogs(t, cli, ctr.ID)
}
})

err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{})
require.NoError(t, err, "start container")
Expand All @@ -149,7 +174,7 @@ func StartCoder(ctx context.Context, t *testing.T, name string, options ...func(
coderURL, err := url.Parse(fmt.Sprintf("http://localhost:%d", p))
require.NoError(t, err, "parse coder URL")
client := codersdk.New(coderURL)
// Wait for container to come up
// Wait for the container to come up.
require.Eventually(t, func() bool {
_, err := client.BuildInfo(ctx)
if err != nil {
Expand Down Expand Up @@ -178,6 +203,70 @@ func StartCoder(ctx context.Context, t *testing.T, name string, options ...func(
return client
}

// pullImage pulls a Docker image, streaming progress to stderr.
func pullImage(ctx context.Context, t *testing.T, cli *client.Client, ref string) {
t.Helper()
reader, err := cli.ImagePull(ctx, ref, image.PullOptions{})
require.NoError(t, err, "pull image %s", ref)
defer func() {
if err := reader.Close(); err != nil {
t.Logf("error closing image puller: %v", err)
}
}()
_, err = io.Copy(os.Stderr, reader)
require.NoError(t, err, "read image pull output for %s", ref)
}

// startPostgres runs a throwaway PostgreSQL that Coder connects to instead of
// starting its embedded one. It's reachable at hostname "postgres" on netName.
// Coder retries its database connection on startup, so we don't wait for it.
func startPostgres(ctx context.Context, t *testing.T, cli *client.Client, netName string) {
t.Helper()
const ref = "us-docker.pkg.dev/coder-v2-images-public/public/postgres:17"
pullImage(ctx, t, cli, ref)
ctr, err := cli.ContainerCreate(ctx, &container.Config{
Image: ref,
Env: []string{"POSTGRES_PASSWORD=postgres"},
}, &container.HostConfig{}, &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
netName: {Aliases: []string{"postgres"}},
},
}, nil, "")
require.NoError(t, err, "create postgres container")
t.Cleanup(func() {
// t.Context() is canceled before t.Cleanup callbacks run, so use a
// fresh context or the removal is a no-op and the container leaks.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{Force: true})
})
require.NoError(t, cli.ContainerStart(ctx, ctr.ID, container.StartOptions{}), "start postgres container")
}

func dumpContainerLogs(t *testing.T, cli *client.Client, containerID string) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
logs, err := cli.ContainerLogs(ctx, containerID, container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
})
if err != nil {
t.Logf("failed to fetch logs for container %s: %v", containerID, err)
return
}
defer func() {
if err := logs.Close(); err != nil {
t.Logf("error closing container log reader: %v", err)
}
}()
var buf bytes.Buffer
if _, err := stdcopy.StdCopy(&buf, &buf, logs); err != nil {
t.Logf("failed to read logs for container %s: %v", containerID, err)
return
}
t.Logf("=== coder container %s logs ===\n%s=== end coder container logs ===", containerID, buf.String())
}

// randomPort is a helper function to find a free random port.
// Note that the OS may reallocate the port very quickly, so
// this is not _guaranteed_.
Expand Down
11 changes: 2 additions & 9 deletions internal/provider/template_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
cp "github.com/otiai10/copy"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -1142,14 +1141,8 @@ resource "coderd_template" "test" {
cfg2 := fmt.Sprintf(cfg, client.URL.String(), client.SessionToken(), "yes", exTemplateOne)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IsUnitTest: true,
// Terraform 1.0 panics while formatting plans that contain marked nested
// values in this scenario ("value is marked, so must be unmarked first").
// The provider regression is still covered on Terraform 1.1+.
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_1_0),
},
PreCheck: func() { testAccPreCheck(t) },
IsUnitTest: true,
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
ExternalProviders: map[string]resource.ExternalProvider{
"random": {
Expand Down