wip: commit progress on terraform serialization
This commit is contained in:
parent
fae52150cc
commit
83a82345de
167
cmd/readmevalidation/terraformblocks.go
Normal file
167
cmd/readmevalidation/terraformblocks.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type terraformLifecycleCondition struct {
|
||||||
|
// Any terraform expression that evaluates to a boolean
|
||||||
|
expression string
|
||||||
|
errorMessage *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type terraformLifecycle struct {
|
||||||
|
createBeforeDestroy bool
|
||||||
|
preventDestroy bool
|
||||||
|
ignoreChanges []string
|
||||||
|
replaceTriggeredBy []string
|
||||||
|
preCondition *terraformLifecycleCondition
|
||||||
|
postCondition *terraformLifecycleCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
type terraformForEachKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
terraformForEachKindMap terraformForEachKind = "map"
|
||||||
|
terraformForEachKindSet terraformForEachKind = "set"
|
||||||
|
)
|
||||||
|
|
||||||
|
type terraformForEach struct {
|
||||||
|
kind terraformForEachKind
|
||||||
|
// If the kind is "map", all values should be guaranteed to be a definite
|
||||||
|
// string for each key. If it's "set", all values should be nil
|
||||||
|
values map[string]*string
|
||||||
|
}
|
||||||
|
|
||||||
|
type terraformVariableKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
terraformVariableKindString terraformVariableKind = "string"
|
||||||
|
terraformVariableKindNumber terraformVariableKind = "number"
|
||||||
|
terraformVariableKindBool terraformVariableKind = "bool"
|
||||||
|
terraformVariableKindList terraformVariableKind = "list"
|
||||||
|
terraformVariableKindMap terraformVariableKind = "map"
|
||||||
|
terraformVariableKindSet terraformVariableKind = "set"
|
||||||
|
// This value kind is incredibly rare. It corresponds to any variable with
|
||||||
|
// a null value but no defined type
|
||||||
|
terraformVariableKindUnknown terraformVariableKind = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
type terraformVariable struct {
|
||||||
|
kind terraformVariableKind
|
||||||
|
value any
|
||||||
|
}
|
||||||
|
|
||||||
|
// coderTerraformModule represents the values that can be parsed from a
|
||||||
|
// Terraform module that are relevant to a Coder deployment. Most of the fields
|
||||||
|
// are derived from the main Terraform spec, but some additional Coder-specific
|
||||||
|
// fields are appended, too, for convenience
|
||||||
|
type coderTerraformModule struct {
|
||||||
|
// Corresponds to the optional `lifecycle` metadata field available to all
|
||||||
|
// resource types
|
||||||
|
Lifecycle *terraformLifecycle `json:"lifecycle"`
|
||||||
|
// Corresponds to the optional `for_each` metadata field available to all
|
||||||
|
// resource types
|
||||||
|
ForEach *terraformForEach `json:"for_each"`
|
||||||
|
// Corresponds to the `source` field in a module block
|
||||||
|
ModuleSource string `json:"module_source"`
|
||||||
|
// Corresponds to the `version` field in Terraform module blocks. Note that
|
||||||
|
// while the Terraform spec marks this field as optional, Coder requires
|
||||||
|
// that one always be defined.
|
||||||
|
Version string `json:"version"`
|
||||||
|
// Corresponds to the optional `provider` field in a module block
|
||||||
|
Provider *string `json:"provider"`
|
||||||
|
// Corresponds to optional `depends_on` field for module blocks
|
||||||
|
DependsOn []string `json:"depends_on"`
|
||||||
|
// Corresponds to the `count` field for any Terraform resource type. It
|
||||||
|
// defines the number of resource instances to create when using Terraform
|
||||||
|
// Apply.
|
||||||
|
InstanceCount int `json:"instance_count"`
|
||||||
|
// Corresponds to `agent_id`` field in a module block. Terraform doesn't
|
||||||
|
// have any built-in concept of an agent_id, but it's needed to make a
|
||||||
|
// module work with a Coder deployment
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
|
// Captures all other arbitrary values defined for a Terraform module block.
|
||||||
|
// Note that while Terraform itself has you define all other fields at the
|
||||||
|
// same level as the well-known/official fields, they've been isolated into
|
||||||
|
// a map for the Go struct definition to improve type-safety
|
||||||
|
Values map[string]terraformVariable `json:"values"`
|
||||||
|
// The raw Terraform snippet used to derive the coderTerraformModule struct
|
||||||
|
SourceCode string `json:"source_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ json.Marshaler = &coderTerraformModule{}
|
||||||
|
|
||||||
|
func (ctmCopy coderTerraformModule) MarshalJSON() ([]byte, error) {
|
||||||
|
if ctmCopy.Lifecycle != nil {
|
||||||
|
lCopy := &terraformLifecycle{
|
||||||
|
createBeforeDestroy: ctmCopy.Lifecycle.createBeforeDestroy,
|
||||||
|
preventDestroy: ctmCopy.Lifecycle.preventDestroy,
|
||||||
|
ignoreChanges: ctmCopy.Lifecycle.ignoreChanges,
|
||||||
|
replaceTriggeredBy: ctmCopy.Lifecycle.replaceTriggeredBy,
|
||||||
|
preCondition: ctmCopy.Lifecycle.preCondition,
|
||||||
|
postCondition: ctmCopy.Lifecycle.postCondition,
|
||||||
|
}
|
||||||
|
// Make sure that both slices always get serialized as JSON null if
|
||||||
|
// they're empty. Serializing as an empty JSON array has no extra
|
||||||
|
// semantics
|
||||||
|
if len(lCopy.ignoreChanges) == 0 {
|
||||||
|
lCopy.ignoreChanges = nil
|
||||||
|
}
|
||||||
|
if len(lCopy.replaceTriggeredBy) == 0 {
|
||||||
|
lCopy.replaceTriggeredBy = nil
|
||||||
|
}
|
||||||
|
ctmCopy.Lifecycle = lCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctmCopy.ForEach != nil {
|
||||||
|
feCopy := &terraformForEach{
|
||||||
|
kind: ctmCopy.ForEach.kind,
|
||||||
|
values: ctmCopy.ForEach.values,
|
||||||
|
}
|
||||||
|
// Make sure that the values map is NEVER serialized as JSON null
|
||||||
|
if feCopy.values == nil {
|
||||||
|
feCopy.values = make(map[string]*string)
|
||||||
|
}
|
||||||
|
ctmCopy.ForEach = feCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ctmCopy.DependsOn) == 0 {
|
||||||
|
ctmCopy.DependsOn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctmCopy.Values == nil {
|
||||||
|
ctmCopy.Values = make(map[string]terraformVariable)
|
||||||
|
}
|
||||||
|
for k, tfValue := range ctmCopy.Values {
|
||||||
|
switch tfValue.kind {
|
||||||
|
case terraformVariableKindString, terraformVariableKindBool, terraformVariableKindNumber, terraformVariableKindUnknown:
|
||||||
|
continue
|
||||||
|
case terraformVariableKindSet, terraformVariableKindList:
|
||||||
|
recast, ok := tfValue.value.([]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unable to process terraform variable %q of kind %q as set/list", k, tfValue.kind)
|
||||||
|
}
|
||||||
|
if recast == nil {
|
||||||
|
ctmCopy.Values[k] = terraformVariable{
|
||||||
|
kind: tfValue.kind,
|
||||||
|
value: []any{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case terraformVariableKindMap:
|
||||||
|
recast, ok := tfValue.value.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unable to process terraform variable %q of kind %q as map", k, tfValue.kind)
|
||||||
|
}
|
||||||
|
if recast == nil {
|
||||||
|
ctmCopy.Values[k] = terraformVariable{
|
||||||
|
kind: tfValue.kind,
|
||||||
|
value: make(map[string]any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(ctmCopy)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user