mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Use plan to detect resource agent association (#381)
* ci: Update DataDog GitHub branch to fallback to GITHUB_REF This was detecting branches, but not our "main" branch before. Hopefully this fixes it! * Add basic Terraform Provider * Rename post files to upload * Add tests for resources * Skip instance identity test * Add tests for ensuring agent get's passed through properly * Fix linting errors * Add echo path * Fix agent authentication * fix: Convert all jobs to use a common resource and agent type This enables a consistent API for project import and provisioned resources. * Add "coder_workspace" data source * feat: Remove magical parameters from being injected This is a much cleaner abstraction. Explicitly declaring the user parameters for each provisioner makes for significantly simpler testing. * feat: Add graceful exits to provisionerd Terraform (or other provisioners) may need to cleanup state, or cancel actions before exit. This adds the ability to gracefully exit provisionerd. * Fix cancel error check * feat: Add destroy to workspace provision job This enables the full flow of create/update/delete. * fix: Use plan to detect resource agent association Before this used the configuration object which detected all resources regardless of count.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-exec/tfexec"
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -75,10 +76,13 @@ func (t *terraform) Provision(request *proto.Provision_Request, stream proto.DRP
|
||||
}
|
||||
|
||||
func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, request *proto.Provision_Request, stream proto.DRPCProvisioner_ProvisionStream) error {
|
||||
env := map[string]string{
|
||||
"CODER_URL": request.Metadata.CoderUrl,
|
||||
"CODER_WORKSPACE_TRANSITION": strings.ToLower(request.Metadata.WorkspaceTransition.String()),
|
||||
env := map[string]string{}
|
||||
for _, envEntry := range os.Environ() {
|
||||
parts := strings.SplitN(envEntry, "=", 2)
|
||||
env[parts[0]] = parts[1]
|
||||
}
|
||||
env["CODER_URL"] = request.Metadata.CoderUrl
|
||||
env["CODER_WORKSPACE_TRANSITION"] = strings.ToLower(request.Metadata.WorkspaceTransition.String())
|
||||
planfilePath := filepath.Join(request.Directory, "terraform.tfplan")
|
||||
options := []tfexec.PlanOption{tfexec.JSON(true), tfexec.Out(planfilePath)}
|
||||
for _, param := range request.ParameterValues {
|
||||
@@ -159,9 +163,31 @@ func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terr
|
||||
_ = reader.Close()
|
||||
<-closeChan
|
||||
|
||||
// Maps resource dependencies to expression references.
|
||||
// This is *required* for a plan, because "DependsOn"
|
||||
// does not propagate.
|
||||
resourceDependencies := map[string][]string{}
|
||||
for _, resource := range plan.Config.RootModule.Resources {
|
||||
if resource.Expressions == nil {
|
||||
resource.Expressions = map[string]*tfjson.Expression{}
|
||||
}
|
||||
// Count expression is separated for logical reasons,
|
||||
// but it's simpler syntactically for us to combine here.
|
||||
if resource.CountExpression != nil {
|
||||
resource.Expressions["count"] = resource.CountExpression
|
||||
}
|
||||
for _, expression := range resource.Expressions {
|
||||
dependencies, exists := resourceDependencies[resource.Address]
|
||||
if !exists {
|
||||
dependencies = []string{}
|
||||
}
|
||||
dependencies = append(dependencies, expression.References...)
|
||||
resourceDependencies[resource.Address] = dependencies
|
||||
}
|
||||
}
|
||||
|
||||
resources := make([]*proto.Resource, 0)
|
||||
agents := map[string]*proto.Agent{}
|
||||
agentDepends := map[string][]string{}
|
||||
|
||||
// Store all agents inside the maps!
|
||||
for _, resource := range plan.Config.RootModule.Resources {
|
||||
@@ -196,9 +222,10 @@ func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terr
|
||||
}
|
||||
switch authTypeValue {
|
||||
case "google-instance-identity":
|
||||
instanceID, _ := block["instance_id"].ConstantValue.(string)
|
||||
agent.Auth = &proto.Agent_GoogleInstanceIdentity{
|
||||
GoogleInstanceIdentity: &proto.GoogleInstanceIdentityAuth{
|
||||
InstanceId: block["instance_id"].ConstantValue.(string),
|
||||
InstanceId: instanceID,
|
||||
},
|
||||
}
|
||||
default:
|
||||
@@ -208,31 +235,33 @@ func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terr
|
||||
}
|
||||
}
|
||||
|
||||
resourceKey := strings.Join([]string{resource.Type, resource.Name}, ".")
|
||||
agents[resourceKey] = agent
|
||||
agentDepends[resourceKey] = resource.DependsOn
|
||||
agents[resource.Address] = agent
|
||||
}
|
||||
|
||||
for _, resource := range plan.Config.RootModule.Resources {
|
||||
for _, resource := range plan.PlannedValues.RootModule.Resources {
|
||||
if resource.Type == "coder_agent" {
|
||||
continue
|
||||
}
|
||||
// The resource address on planned values can include the indexed
|
||||
// value like "[0]", but the config doesn't have these, and we don't
|
||||
// care which index the resource is.
|
||||
resourceAddress := fmt.Sprintf("%s.%s", resource.Type, resource.Name)
|
||||
var agent *proto.Agent
|
||||
// Associate resources that depend on an agent.
|
||||
for _, dep := range resource.DependsOn {
|
||||
for _, dependency := range resourceDependencies[resourceAddress] {
|
||||
var has bool
|
||||
agent, has = agents[dep]
|
||||
agent, has = agents[dependency]
|
||||
if has {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Associate resources where the agent depends on it.
|
||||
for agentKey, dependsOn := range agentDepends {
|
||||
for _, depend := range dependsOn {
|
||||
if depend != strings.Join([]string{resource.Type, resource.Name}, ".") {
|
||||
for agentAddress := range agents {
|
||||
for _, depend := range resourceDependencies[agentAddress] {
|
||||
if depend != resourceAddress {
|
||||
continue
|
||||
}
|
||||
agent = agents[agentKey]
|
||||
agent = agents[agentAddress]
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -254,10 +283,11 @@ func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terr
|
||||
}
|
||||
|
||||
func (t *terraform) runTerraformApply(ctx context.Context, terraform *tfexec.Terraform, request *proto.Provision_Request, stream proto.DRPCProvisioner_ProvisionStream, statefilePath string) error {
|
||||
env := []string{
|
||||
"CODER_URL=" + request.Metadata.CoderUrl,
|
||||
"CODER_WORKSPACE_TRANSITION=" + strings.ToLower(request.Metadata.WorkspaceTransition.String()),
|
||||
}
|
||||
env := os.Environ()
|
||||
env = append(env,
|
||||
"CODER_URL="+request.Metadata.CoderUrl,
|
||||
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(request.Metadata.WorkspaceTransition.String()),
|
||||
)
|
||||
vars := []string{}
|
||||
for _, param := range request.ParameterValues {
|
||||
switch param.DestinationScheme {
|
||||
|
||||
@@ -235,11 +235,10 @@ provider "coder" {
|
||||
Files: map[string]string{
|
||||
"main.tf": provider + `
|
||||
resource "coder_agent" "A" {
|
||||
count = 1
|
||||
}
|
||||
resource "null_resource" "A" {
|
||||
depends_on = [
|
||||
coder_agent.A
|
||||
]
|
||||
count = length(coder_agent.A)
|
||||
}`,
|
||||
},
|
||||
Request: &proto.Provision_Request{
|
||||
@@ -266,15 +265,15 @@ provider "coder" {
|
||||
Files: map[string]string{
|
||||
"main.tf": provider + `
|
||||
resource "coder_agent" "A" {
|
||||
depends_on = [
|
||||
null_resource.A
|
||||
]
|
||||
count = length(null_resource.A)
|
||||
auth {
|
||||
type = "google-instance-identity"
|
||||
instance_id = "an-instance"
|
||||
}
|
||||
}
|
||||
resource "null_resource" "A" {}`,
|
||||
resource "null_resource" "A" {
|
||||
count = 1
|
||||
}`,
|
||||
},
|
||||
Request: &proto.Provision_Request{
|
||||
DryRun: true,
|
||||
|
||||
Reference in New Issue
Block a user