mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: Update generated schema for provisionerd (#121)
This commit is contained in:
@ -1,10 +1,13 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-exec/tfexec"
|
||||
"golang.org/x/xerrors"
|
||||
@ -13,32 +16,52 @@ import (
|
||||
)
|
||||
|
||||
// Provision executes `terraform apply`.
|
||||
func (t *terraform) Provision(ctx context.Context, request *proto.Provision_Request) (*proto.Provision_Response, error) {
|
||||
func (t *terraform) Provision(request *proto.Provision_Request, stream proto.DRPCProvisioner_ProvisionStream) error {
|
||||
ctx := stream.Context()
|
||||
statefilePath := filepath.Join(request.Directory, "terraform.tfstate")
|
||||
err := os.WriteFile(statefilePath, request.State, 0600)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("write statefile %q: %w", statefilePath, err)
|
||||
if len(request.State) > 0 {
|
||||
err := os.WriteFile(statefilePath, request.State, 0600)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("write statefile %q: %w", statefilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
terraform, err := tfexec.NewTerraform(request.Directory, t.binaryPath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create new terraform executor: %w", err)
|
||||
return xerrors.Errorf("create new terraform executor: %w", err)
|
||||
}
|
||||
version, _, err := terraform.Version(ctx, false)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get terraform version: %w", err)
|
||||
return xerrors.Errorf("get terraform version: %w", err)
|
||||
}
|
||||
if !version.GreaterThanOrEqual(minimumTerraformVersion) {
|
||||
return nil, xerrors.Errorf("terraform version %q is too old. required >= %q", version.String(), minimumTerraformVersion.String())
|
||||
return xerrors.Errorf("terraform version %q is too old. required >= %q", version.String(), minimumTerraformVersion.String())
|
||||
}
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
_ = stream.Send(&proto.Provision_Response{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: scanner.Text(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
terraform.SetStdout(writer)
|
||||
err = terraform.Init(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("initialize terraform: %w", err)
|
||||
return xerrors.Errorf("initialize terraform: %w", err)
|
||||
}
|
||||
|
||||
env := map[string]string{}
|
||||
options := make([]tfexec.ApplyOption, 0)
|
||||
options := []tfexec.ApplyOption{tfexec.JSON(true)}
|
||||
for _, param := range request.ParameterValues {
|
||||
switch param.DestinationScheme {
|
||||
case proto.ParameterDestination_ENVIRONMENT_VARIABLE:
|
||||
@ -46,26 +69,73 @@ func (t *terraform) Provision(ctx context.Context, request *proto.Provision_Requ
|
||||
case proto.ParameterDestination_PROVISIONER_VARIABLE:
|
||||
options = append(options, tfexec.Var(fmt.Sprintf("%s=%s", param.Name, param.Value)))
|
||||
default:
|
||||
return nil, xerrors.Errorf("unsupported parameter type %q for %q", param.DestinationScheme, param.Name)
|
||||
return xerrors.Errorf("unsupported parameter type %q for %q", param.DestinationScheme, param.Name)
|
||||
}
|
||||
}
|
||||
err = terraform.SetEnv(env)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("apply environment variables: %w", err)
|
||||
return xerrors.Errorf("apply environment variables: %w", err)
|
||||
}
|
||||
|
||||
reader, writer = io.Pipe()
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
go func() {
|
||||
decoder := json.NewDecoder(reader)
|
||||
for {
|
||||
var log terraformProvisionLog
|
||||
err := decoder.Decode(&log)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logLevel, err := convertTerraformLogLevel(log.Level)
|
||||
if err != nil {
|
||||
// Not a big deal, but we should handle this at some point!
|
||||
continue
|
||||
}
|
||||
_ = stream.Send(&proto.Provision_Response{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: logLevel,
|
||||
Output: log.Message,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if log.Diagnostic == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the diagnostic is provided, let's provide a bit more info!
|
||||
logLevel, err = convertTerraformLogLevel(log.Diagnostic.Severity)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_ = stream.Send(&proto.Provision_Response{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: logLevel,
|
||||
Output: log.Diagnostic.Detail,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
terraform.SetStdout(writer)
|
||||
err = terraform.Apply(ctx, options...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("apply terraform: %w", err)
|
||||
return xerrors.Errorf("apply terraform: %w", err)
|
||||
}
|
||||
|
||||
statefileContent, err := os.ReadFile(statefilePath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("read file %q: %w", statefilePath, err)
|
||||
return xerrors.Errorf("read file %q: %w", statefilePath, err)
|
||||
}
|
||||
state, err := terraform.ShowStateFile(ctx, statefilePath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("show state file %q: %w", statefilePath, err)
|
||||
return xerrors.Errorf("show state file %q: %w", statefilePath, err)
|
||||
}
|
||||
resources := make([]*proto.Resource, 0)
|
||||
if state.Values != nil {
|
||||
@ -77,8 +147,42 @@ func (t *terraform) Provision(ctx context.Context, request *proto.Provision_Requ
|
||||
}
|
||||
}
|
||||
|
||||
return &proto.Provision_Response{
|
||||
Resources: resources,
|
||||
State: statefileContent,
|
||||
}, nil
|
||||
return stream.Send(&proto.Provision_Response{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
State: statefileContent,
|
||||
Resources: resources,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type terraformProvisionLog struct {
|
||||
Level string `json:"@level"`
|
||||
Message string `json:"@message"`
|
||||
|
||||
Diagnostic *terraformProvisionLogDiagnostic `json:"diagnostic"`
|
||||
}
|
||||
|
||||
type terraformProvisionLogDiagnostic struct {
|
||||
Severity string `json:"severity"`
|
||||
Summary string `json:"summary"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
func convertTerraformLogLevel(logLevel string) (proto.LogLevel, error) {
|
||||
switch strings.ToLower(logLevel) {
|
||||
case "trace":
|
||||
return proto.LogLevel_TRACE, nil
|
||||
case "debug":
|
||||
return proto.LogLevel_DEBUG, nil
|
||||
case "info":
|
||||
return proto.LogLevel_INFO, nil
|
||||
case "warn":
|
||||
return proto.LogLevel_WARN, nil
|
||||
case "error":
|
||||
return proto.LogLevel_ERROR, nil
|
||||
default:
|
||||
return proto.LogLevel(0), xerrors.Errorf("invalid log level %q", logLevel)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user