Skip to content

Commit

Permalink
update tf runtime workspace in stack dir
Browse files Browse the repository at this point in the history
  • Loading branch information
markliby committed Jan 17, 2023
1 parent 3729ccb commit 6af7182
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 288 deletions.
108 changes: 65 additions & 43 deletions pkg/engine/runtime/terraform/terraform_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package terraform

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/imdario/mergo"
"github.com/spf13/afero"
Expand All @@ -15,31 +16,19 @@ import (
var _ runtime.Runtime = &TerraformRuntime{}

type TerraformRuntime struct {
tfops.WorkspaceStore
tfops.WorkSpace
}

func NewTerraformRuntime() (runtime.Runtime, error) {
fs := afero.Afero{Fs: afero.NewOsFs()}
ws, err := tfops.GetWorkspaceStore(fs)
if err != nil {
return nil, err
}
TFRuntime := &TerraformRuntime{ws}
ws := tfops.NewWorkSpace(fs)
TFRuntime := &TerraformRuntime{*ws}
return TFRuntime, nil
}

// Apply terraform apply resource
func (t *TerraformRuntime) Apply(ctx context.Context, request *runtime.ApplyRequest) *runtime.ApplyResponse {
planState := request.PlanResource
w, ok := t.Store[planState.ResourceKey()]
if !ok {
err := t.Create(ctx, planState)
if err != nil {
return &runtime.ApplyResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
w = t.Store[planState.ResourceKey()]
}

// terraform dry run merge state
// TODO: terraform dry run apply,not only merge state
if request.DryRun {
Expand All @@ -56,19 +45,35 @@ func (t *TerraformRuntime) Apply(ctx context.Context, request *runtime.ApplyRequ
Extensions: planState.Extensions,
}, Status: nil}
}
w.SetResource(planState)

if err := w.WriteHCL(); err != nil {
stackPath := request.Stack.GetPath()
tfCacheDir := filepath.Join(stackPath, "."+planState.ResourceKey())
t.WorkSpace.SetStackDir(stackPath)
t.WorkSpace.SetCacheDir(tfCacheDir)
t.WorkSpace.SetResource(planState)

if err := t.WorkSpace.WriteHCL(); err != nil {
return &runtime.ApplyResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}

tfstate, err := w.Apply(ctx)
_, err := os.Stat(filepath.Join(tfCacheDir, tfops.HCLLOCKFILE))
if err != nil {
if os.IsNotExist(err) {
if err := t.WorkSpace.InitWorkSpace(ctx); err != nil {
return &runtime.ApplyResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
} else {
return &runtime.ApplyResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
}

tfstate, err := t.WorkSpace.Apply(ctx)
if err != nil {
return &runtime.ApplyResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}

// get terraform provider version
providerAddr, err := w.GetProvider()
providerAddr, err := t.WorkSpace.GetProvider()
if err != nil {
return &runtime.ApplyResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
Expand All @@ -90,24 +95,42 @@ func (t *TerraformRuntime) Apply(ctx context.Context, request *runtime.ApplyRequ
// Read terraform show state
func (t *TerraformRuntime) Read(ctx context.Context, request *runtime.ReadRequest) *runtime.ReadResponse {
priorState := request.PriorResource
planState := request.PlanResource
requestResource := request.PlanResource

// When the operation is delete, the planResource is empty,
if requestResource == nil {
requestResource = request.PriorResource
}
if priorState == nil {
return &runtime.ReadResponse{Resource: nil, Status: nil}
}
var tfstate *tfops.TFState
w, ok := t.Store[planState.ResourceKey()]
if !ok {
err := t.Create(ctx, planState)
if err != nil {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
w = t.Store[priorState.ResourceKey()]
if err := w.WriteTFState(priorState); err != nil {

stackPath := request.Stack.GetPath()
tfCacheDir := filepath.Join(stackPath, "."+requestResource.ResourceKey())
t.WorkSpace.SetStackDir(stackPath)
t.WorkSpace.SetCacheDir(tfCacheDir)
t.WorkSpace.SetResource(requestResource)
if err := t.WorkSpace.WriteHCL(); err != nil {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
_, err := os.Stat(filepath.Join(tfCacheDir, tfops.HCLLOCKFILE))
if err != nil {
if os.IsNotExist(err) {
if err := t.WorkSpace.InitWorkSpace(ctx); err != nil {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
} else {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
}

tfstate, err := w.RefreshOnly(ctx)
// priorState overwirte tfstate in workspace
if err := t.WorkSpace.WriteTFState(priorState); err != nil {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}

tfstate, err = t.WorkSpace.RefreshOnly(ctx)
if err != nil {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}
Expand All @@ -116,37 +139,36 @@ func (t *TerraformRuntime) Read(ctx context.Context, request *runtime.ReadReques
}

// get terraform provider addr
providerAddr, err := w.GetProvider()
providerAddr, err := t.WorkSpace.GetProvider()
if err != nil {
return &runtime.ReadResponse{Resource: nil, Status: status.NewErrorStatus(err)}
}

r := tfops.ConvertTFState(tfstate, providerAddr)
return &runtime.ReadResponse{
Resource: &models.Resource{
ID: planState.ID,
Type: planState.Type,
ID: requestResource.ID,
Type: requestResource.Type,
Attributes: r.Attributes,
DependsOn: planState.DependsOn,
Extensions: planState.Extensions,
DependsOn: requestResource.DependsOn,
Extensions: requestResource.Extensions,
},
Status: nil,
}
}

// Delete terraform resource and remove workspace
func (t *TerraformRuntime) Delete(ctx context.Context, request *runtime.DeleteRequest) *runtime.DeleteResponse {
w, ok := t.Store[request.Resource.ResourceKey()]
if !ok {
return &runtime.DeleteResponse{Status: status.NewErrorStatus(fmt.Errorf("%s terraform workspace not exist, cannot delete", request.Resource.ResourceKey()))}
}
if err := w.Destroy(ctx); err != nil {
stackPath := request.Stack.GetPath()
tfCacheDir := filepath.Join(stackPath, "."+request.Resource.ResourceKey())
defer os.RemoveAll(tfCacheDir)
t.WorkSpace.SetStackDir(stackPath)
t.WorkSpace.SetCacheDir(tfCacheDir)
t.WorkSpace.SetResource(request.Resource)
if err := t.WorkSpace.Destroy(ctx); err != nil {
return &runtime.DeleteResponse{Status: status.NewErrorStatus(err)}
}

if err := t.Remove(ctx, request.Resource); err != nil {
return &runtime.DeleteResponse{Status: status.NewErrorStatus(err)}
}
return &runtime.DeleteResponse{Status: nil}
}

Expand Down
47 changes: 27 additions & 20 deletions pkg/engine/runtime/terraform/terraform_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,55 @@ package terraform
import (
"context"
"os"
"path/filepath"
"testing"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"kusionstack.io/kusion/pkg/engine/models"
"kusionstack.io/kusion/pkg/engine/runtime"
"kusionstack.io/kusion/pkg/engine/runtime/terraform/tfops"
"kusionstack.io/kusion/pkg/projectstack"
)

var testResource = models.Resource{
ID: "example",
Type: "Terraform",
Attributes: map[string]interface{}{
"content": "kusion",
"filename": "test.txt",
},
Extensions: map[string]interface{}{
"provider": "registry.terraform.io/hashicorp/local/2.2.3",
"resourceType": "local_file",
},
}
var (
testResource = models.Resource{
ID: "example",
Type: "Terraform",
Attributes: map[string]interface{}{
"content": "kusion",
"filename": "test.txt",
},
Extensions: map[string]interface{}{
"provider": "registry.terraform.io/hashicorp/local/2.2.3",
"resourceType": "local_file",
},
}
stack = &projectstack.Stack{
StackConfiguration: projectstack.StackConfiguration{Name: "fakeStack"},
Path: "fakePath",
}
)

func TestTerraformRuntime(t *testing.T) {
wd, _ := tfops.GetWorkSpaceDir()
defer os.RemoveAll(filepath.Join(wd, testResource.ID))
tfRuntime, _ := NewTerraformRuntime()
defer os.RemoveAll(stack.GetPath())
tfRuntime := TerraformRuntime{*tfops.NewWorkSpace(afero.Afero{Fs: afero.NewOsFs()})}

t.Run("ApplyDryRun", func(t *testing.T) {
response := tfRuntime.Apply(context.TODO(), &runtime.ApplyRequest{PlanResource: &testResource, DryRun: true})
response := tfRuntime.Apply(context.TODO(), &runtime.ApplyRequest{PlanResource: &testResource, DryRun: true, Stack: stack})
assert.Equalf(t, nil, response.Status, "Execute(%v)", "Apply")
})
t.Run("Apply", func(t *testing.T) {
response := tfRuntime.Apply(context.TODO(), &runtime.ApplyRequest{PlanResource: &testResource, DryRun: false})
response := tfRuntime.Apply(context.TODO(), &runtime.ApplyRequest{PlanResource: &testResource, DryRun: false, Stack: stack})
assert.Equalf(t, nil, response.Status, "Execute(%v)", "Apply")
})

t.Run("Read", func(t *testing.T) {
response := tfRuntime.Read(context.TODO(), &runtime.ReadRequest{PlanResource: &testResource})
response := tfRuntime.Read(context.TODO(), &runtime.ReadRequest{PlanResource: &testResource, Stack: stack})
assert.Equalf(t, nil, response.Status, "Execute(%v)", "Read")
})

t.Run("Delete", func(t *testing.T) {
response := tfRuntime.Delete(context.TODO(), &runtime.DeleteRequest{Resource: &testResource})
response := tfRuntime.Delete(context.TODO(), &runtime.DeleteRequest{Resource: &testResource, Stack: stack})
assert.Equalf(t, nil, response.Status, "Execute(%v)", "Delete")
})
}
83 changes: 0 additions & 83 deletions pkg/engine/runtime/terraform/tfops/store.go

This file was deleted.

Loading

0 comments on commit 6af7182

Please sign in to comment.