Skip to content

Commit

Permalink
feat: Update Coder Terraform Provider to v0.2.1 (#563)
Browse files Browse the repository at this point in the history
This update exposes the workspace name and owner, and changes
authentication methods to be explicit. Implicit authentication
added unnecessary complexity and introduced inconsistency.
  • Loading branch information
kylecarbs committed Mar 25, 2022
1 parent 27c24de commit a06821c
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 230 deletions.
54 changes: 34 additions & 20 deletions cli/workspaceagent.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package cli

import (
"context"
"net/url"
"os"
"time"

"github.com/spf13/cobra"
"golang.org/x/xerrors"
Expand All @@ -13,13 +15,13 @@ import (
"github.com/coder/coder/agent"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/peer"
"github.com/coder/retry"
)

func workspaceAgent() *cobra.Command {
return &cobra.Command{
Use: "agent",
// This command isn't useful for users, and seems
// more likely to confuse.
// This command isn't useful to manually execute.
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
coderURLRaw, exists := os.LookupEnv("CODER_URL")
Expand All @@ -30,30 +32,42 @@ func workspaceAgent() *cobra.Command {
if err != nil {
return xerrors.Errorf("parse %q: %w", coderURLRaw, err)
}
logger := slog.Make(sloghuman.Sink(cmd.OutOrStdout()))
client := codersdk.New(coderURL)
sessionToken, exists := os.LookupEnv("CODER_TOKEN")
auth, exists := os.LookupEnv("CODER_AUTH")
if !exists {
// probe, err := cloud.New()
// if err != nil {
// return xerrors.Errorf("probe cloud: %w", err)
// }
// if !probe.Detected {
// return xerrors.Errorf("no valid authentication method found; set \"CODER_TOKEN\"")
// }
// switch {
// case probe.GCP():
response, err := client.AuthWorkspaceGoogleInstanceIdentity(cmd.Context(), "", nil)
auth = "token"
}
switch auth {
case "token":
sessionToken, exists := os.LookupEnv("CODER_TOKEN")
if !exists {
return xerrors.Errorf("CODER_TOKEN must be set for token auth")
}
client.SessionToken = sessionToken
case "google-instance-identity":
ctx, cancelFunc := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancelFunc()
for retry.New(100*time.Millisecond, 5*time.Second).Wait(ctx) {
var response codersdk.WorkspaceAgentAuthenticateResponse
response, err = client.AuthWorkspaceGoogleInstanceIdentity(cmd.Context(), "", nil)
if err != nil {
logger.Warn(ctx, "authenticate workspace with Google Instance Identity", slog.Error(err))
continue
}
client.SessionToken = response.SessionToken
break
}
if err != nil {
return xerrors.Errorf("authenticate workspace with gcp: %w", err)
return xerrors.Errorf("agent failed to authenticate in time: %w", err)
}
sessionToken = response.SessionToken
// default:
// return xerrors.Errorf("%q authentication not supported; set \"CODER_TOKEN\" instead", probe.Name)
// }
case "aws-instance-identity":
return xerrors.Errorf("not implemented")
case "azure-instance-identity":
return xerrors.Errorf("not implemented")
}
client.SessionToken = sessionToken
closer := agent.New(client.ListenWorkspaceAgent, &peer.ConnOptions{
Logger: slog.Make(sloghuman.Sink(cmd.OutOrStdout())),
Logger: logger,
})
<-cmd.Context().Done()
return closer.Close()
Expand Down
10 changes: 8 additions & 2 deletions coderd/provisionerdaemons.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
if err != nil {
return nil, failJob(fmt.Sprintf("get project: %s", err))
}
owner, err := server.Database.GetUserByID(ctx, workspace.OwnerID)
if err != nil {
return nil, failJob(fmt.Sprintf("get owner: %s", err))
}

// Compute parameters for the workspace to consume.
parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{
Expand Down Expand Up @@ -224,6 +228,8 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
Metadata: &sdkproto.Provision_Metadata{
CoderUrl: server.AccessURL.String(),
WorkspaceTransition: transition,
WorkspaceName: workspace.Name,
WorkspaceOwner: owner.Username,
},
},
}
Expand Down Expand Up @@ -591,9 +597,9 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
}
if resource.AgentID.Valid {
var instanceID sql.NullString
if protoResource.Agent.GetGoogleInstanceIdentity() != nil {
if protoResource.Agent.GetInstanceId() != "" {
instanceID = sql.NullString{
String: protoResource.Agent.GetGoogleInstanceIdentity().InstanceId,
String: protoResource.Agent.GetInstanceId(),
Valid: true,
}
}
Expand Down
10 changes: 4 additions & 6 deletions coderd/workspaceresourceauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
Name: "somename",
Type: "someinstance",
Agent: &proto.Agent{
Auth: &proto.Agent_GoogleInstanceIdentity{
GoogleInstanceIdentity: &proto.GoogleInstanceIdentityAuth{},
Auth: &proto.Agent_InstanceId{
InstanceId: "",
},
},
}},
Expand All @@ -90,10 +90,8 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
Name: "somename",
Type: "someinstance",
Agent: &proto.Agent{
Auth: &proto.Agent_GoogleInstanceIdentity{
GoogleInstanceIdentity: &proto.GoogleInstanceIdentityAuth{
InstanceId: instanceID,
},
Auth: &proto.Agent_InstanceId{
InstanceId: instanceID,
},
},
}},
Expand Down
7 changes: 3 additions & 4 deletions examples/gcp-linux/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.2.1"
}
}
}
Expand Down Expand Up @@ -41,6 +42,7 @@ data "coder_workspace" "me" {
}

data "coder_agent_script" "dev" {
auth = "google-instance-identity"
arch = "amd64"
os = "linux"
}
Expand Down Expand Up @@ -87,8 +89,5 @@ resource "google_compute_instance" "dev" {

resource "coder_agent" "dev" {
count = length(google_compute_instance.dev)
auth {
type = "google-instance-identity"
instance_id = google_compute_instance.dev[0].instance_id
}
instance_id = google_compute_instance.dev[0].instance_id
}
72 changes: 23 additions & 49 deletions provisioner/terraform/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"

"github.com/hashicorp/terraform-exec/tfexec"
Expand Down Expand Up @@ -102,6 +101,8 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
env = append(env,
"CODER_URL="+start.Metadata.CoderUrl,
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),
"CODER_WORKSPACE_NAME="+start.Metadata.WorkspaceName,
"CODER_WORKSPACE_OWNER="+start.Metadata.WorkspaceOwner,
)
for key, value := range provisionersdk.AgentScriptEnv() {
env = append(env, key+"="+value)
Expand Down Expand Up @@ -285,38 +286,24 @@ func parseTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, planfi
if envRaw, has := resource.Expressions["env"]; has {
env, ok := envRaw.ConstantValue.(map[string]string)
if !ok {
return nil, xerrors.Errorf("unexpected type %q for env map", reflect.TypeOf(envRaw.ConstantValue).String())
return nil, xerrors.Errorf("unexpected type %T for env map", envRaw.ConstantValue)
}
agent.Env = env
}
if startupScriptRaw, has := resource.Expressions["startup_script"]; has {
startupScript, ok := startupScriptRaw.ConstantValue.(string)
if !ok {
return nil, xerrors.Errorf("unexpected type %q for startup script", reflect.TypeOf(startupScriptRaw.ConstantValue).String())
return nil, xerrors.Errorf("unexpected type %T for startup script", startupScriptRaw.ConstantValue)
}
agent.StartupScript = startupScript
}
if auth, has := resource.Expressions["auth"]; has {
if len(auth.ExpressionData.NestedBlocks) > 0 {
block := auth.ExpressionData.NestedBlocks[0]
authType, has := block["type"]
if has {
authTypeValue, valid := authType.ConstantValue.(string)
if !valid {
return nil, xerrors.Errorf("unexpected type %q for auth type", reflect.TypeOf(authType.ConstantValue))
}
switch authTypeValue {
case "google-instance-identity":
instanceID, _ := block["instance_id"].ConstantValue.(string)
agent.Auth = &proto.Agent_GoogleInstanceIdentity{
GoogleInstanceIdentity: &proto.GoogleInstanceIdentityAuth{
InstanceId: instanceID,
},
}
default:
return nil, xerrors.Errorf("unknown auth type: %q", authTypeValue)
}
}
if instanceIDRaw, has := resource.Expressions["instance_id"]; has {
instanceID, ok := instanceIDRaw.ConstantValue.(string)
if !ok {
return nil, xerrors.Errorf("unexpected type %T for instance_id", instanceIDRaw.ConstantValue)
}
agent.Auth = &proto.Agent_InstanceId{
InstanceId: instanceID,
}
}

Expand Down Expand Up @@ -379,12 +366,9 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
resources := make([]*proto.Resource, 0)
if state.Values != nil {
type agentAttributes struct {
ID string `mapstructure:"id"`
Token string `mapstructure:"token"`
Auth []struct {
Type string `mapstructure:"type"`
InstanceID string `mapstructure:"instance_id"`
} `mapstructure:"auth"`
ID string `mapstructure:"id"`
Token string `mapstructure:"token"`
InstanceID string `mapstructure:"instance_id"`
Env map[string]string `mapstructure:"env"`
StartupScript string `mapstructure:"startup_script"`
}
Expand All @@ -409,17 +393,9 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
Token: attrs.Token,
},
}
if len(attrs.Auth) > 0 {
auth := attrs.Auth[0]
switch auth.Type {
case "google-instance-identity":
agent.Auth = &proto.Agent_GoogleInstanceIdentity{
GoogleInstanceIdentity: &proto.GoogleInstanceIdentityAuth{
InstanceId: auth.InstanceID,
},
}
default:
return nil, xerrors.Errorf("unknown auth type: %q", auth.Type)
if attrs.InstanceID != "" {
agent.Auth = &proto.Agent_InstanceId{
InstanceId: attrs.InstanceID,
}
}
resourceKey := strings.Join([]string{resource.Type, resource.Name}, ".")
Expand Down Expand Up @@ -453,14 +429,12 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
}
}

if agent != nil {
if agent.GetGoogleInstanceIdentity() != nil {
// Make sure the instance has an instance ID!
_, exists := resource.AttributeValues["instance_id"]
if !exists {
// This was a mistake!
agent = nil
}
if agent != nil && agent.GetInstanceId() != "" {
// Make sure the instance has an instance ID!
_, exists := resource.AttributeValues["instance_id"]
if !exists {
// This was a mistake!
agent = nil
}
}

Expand Down
18 changes: 5 additions & 13 deletions provisioner/terraform/provision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.1.0"
version = "0.2.1"
}
}
}
Expand Down Expand Up @@ -195,10 +195,7 @@ provider "coder" {
depends_on = [
null_resource.A
]
auth {
type = "google-instance-identity"
instance_id = "an-instance"
}
instance_id = "an-instance"
}
resource "null_resource" "A" {}`,
},
Expand Down Expand Up @@ -261,10 +258,7 @@ provider "coder" {
"main.tf": provider + `
resource "coder_agent" "A" {
count = length(null_resource.A)
auth {
type = "google-instance-identity"
instance_id = "an-instance"
}
instance_id = "an-instance"
}
resource "null_resource" "A" {
count = 1
Expand All @@ -287,10 +281,8 @@ provider "coder" {
Name: "A",
Type: "null_resource",
Agent: &proto.Agent{
Auth: &proto.Agent_GoogleInstanceIdentity{
GoogleInstanceIdentity: &proto.GoogleInstanceIdentityAuth{
InstanceId: "an-instance",
},
Auth: &proto.Agent_InstanceId{
InstanceId: "an-instance",
},
},
}},
Expand Down
7 changes: 5 additions & 2 deletions provisionersdk/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var (
"amd64": `
$ProgressPreference = "SilentlyContinue"
Invoke-WebRequest -Uri ${ACCESS_URL}bin/coder-windows-amd64.exe -OutFile $env:TEMP\coder.exe
$env:CODER_AUTH = "${AUTH_TYPE}"
$env:CODER_URL = "${ACCESS_URL}"
Start-Process -FilePath $env:TEMP\coder.exe -ArgumentList "workspaces","agent" -PassThru
`,
Expand All @@ -19,9 +20,10 @@ Start-Process -FilePath $env:TEMP\coder.exe -ArgumentList "workspaces","agent" -
"amd64": `
#!/usr/bin/env sh
set -eu pipefail
BINARY_LOCATION=$(mktemp -d)/coder
export BINARY_LOCATION=$(mktemp -d)/coder
curl -fsSL ${ACCESS_URL}bin/coder-linux-amd64 -o $BINARY_LOCATION
chmod +x $BINARY_LOCATION
export CODER_AUTH="${AUTH_TYPE}"
export CODER_URL="${ACCESS_URL}"
exec $BINARY_LOCATION workspaces agent
`,
Expand All @@ -30,9 +32,10 @@ exec $BINARY_LOCATION workspaces agent
"amd64": `
#!/usr/bin/env sh
set -eu pipefail
BINARY_LOCATION=$(mktemp -d)/coder
export BINARY_LOCATION=$(mktemp -d)/coder
curl -fsSL ${ACCESS_URL}bin/coder-darwin-amd64 -o $BINARY_LOCATION
chmod +x $BINARY_LOCATION
export CODER_AUTH="${AUTH_TYPE}"
export CODER_URL="${ACCESS_URL}"
exec $BINARY_LOCATION workspaces agent
`,
Expand Down
1 change: 1 addition & 0 deletions provisionersdk/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func TestAgentScript(t *testing.T) {
return
}
script = strings.ReplaceAll(script, "${ACCESS_URL}", srvURL.String()+"/")
script = strings.ReplaceAll(script, "${AUTH_TYPE}", "token")
output, err := exec.Command("sh", "-c", script).CombinedOutput()
t.Log(string(output))
require.NoError(t, err)
Expand Down

0 comments on commit a06821c

Please sign in to comment.