Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workspace checksum to avoid unnecessary terraform init execution #90

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apis/v1beta1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ type WorkspaceParameters struct {

// WorkspaceObservation are the observable fields of a Workspace.
type WorkspaceObservation struct {
Outputs map[string]string `json:"outputs,omitempty"`
Checksum string `json:"checksum,omitempty"`
Outputs map[string]string `json:"outputs,omitempty"`
}

// A WorkspaceSpec defines the desired state of a Workspace.
Expand Down
45 changes: 33 additions & 12 deletions internal/controller/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"
"time"

"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/pkg/errors"
"github.com/spf13/afero"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -70,6 +71,7 @@ const (
errDestroy = "cannot destroy Terraform configuration"
errVarFile = "cannot get tfvars"
errDeleteWorkspace = "cannot delete Terraform workspace"
errChecksum = "cannot calculate workspace checksum"

gitCredentialsFilename = ".git-credentials"
)
Expand Down Expand Up @@ -99,6 +101,7 @@ type tfclient interface {
Apply(ctx context.Context, o ...terraform.Option) error
Destroy(ctx context.Context, o ...terraform.Option) error
DeleteCurrentWorkspace(ctx context.Context) error
GenerateChecksum(ctx context.Context) (string, error)
}

// Setup adds a controller that reconciles Workspace managed resources.
Expand All @@ -119,6 +122,7 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error
c := &connector{
kube: mgr.GetClient(),
usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1beta1.ProviderConfigUsage{}),
logger: o.Logger,
fs: fs,
terraform: func(dir string) tfclient { return terraform.Harness{Path: tfPath, Dir: dir} },
}
Expand All @@ -140,9 +144,9 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error
}

type connector struct {
kube client.Client
usage resource.Tracker

kube client.Client
usage resource.Tracker
logger logging.Logger
fs afero.Afero
terraform func(dir string) tfclient
}
Expand All @@ -156,7 +160,7 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
if !ok {
return nil, errors.New(errNotWorkspace)
}

l := c.logger.WithValues("request", cr.Name)
bobh66 marked this conversation as resolved.
Show resolved Hide resolved
// NOTE(negz): This directory will be garbage collected by the workdir
// garbage collector that is started in Setup.
dir := filepath.Join(tfDir, string(cr.GetUID()))
Expand Down Expand Up @@ -210,14 +214,14 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
return nil, errors.Wrap(err, errRemoteModule)
}

client := getter.Client{
gc := getter.Client{
Src: cr.Spec.ForProvider.Module,
Dst: dir,
Pwd: dir,

Mode: getter.ClientModeDir,
}
err := client.Get()
err := gc.Get()
if err != nil {
return nil, errors.Wrap(err, errRemoteModule)
}
Expand Down Expand Up @@ -250,26 +254,37 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
}
}

tf := c.terraform(dir)
o := make([]terraform.InitOption, 0, len(cr.Spec.ForProvider.InitArgs))
o = append(o, terraform.WithInitArgs(cr.Spec.ForProvider.InitArgs))
// NOTE(ytsarev): user tf provider cache mechanism to speed up
// reconciliation, see https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache
if pc.Spec.PluginCache == nil {
pc.Spec.PluginCache = new(bool)
*pc.Spec.PluginCache = true
}
tf := c.terraform(dir)
if cr.Status.AtProvider.Checksum != "" {
checksum, err := tf.GenerateChecksum(ctx)
if err != nil {
return nil, errors.Wrap(err, errChecksum)
}
if cr.Status.AtProvider.Checksum == checksum {
l.Debug("Checksums match - skip running terraform init")
return &external{tf: tf, kube: c.kube, logger: c.logger}, errors.Wrap(tf.Workspace(ctx, meta.GetExternalName(cr)), errWorkspace)
}
l.Debug("Checksums don't match so run terraform init:", "old", cr.Status.AtProvider.Checksum, "new", checksum)
}

o := make([]terraform.InitOption, 0, len(cr.Spec.ForProvider.InitArgs))
o = append(o, terraform.WithInitArgs(cr.Spec.ForProvider.InitArgs))
if err := tf.Init(ctx, *pc.Spec.PluginCache, o...); err != nil {
return nil, errors.Wrap(err, errInit)
}

return &external{tf: tf, kube: c.kube}, errors.Wrap(tf.Workspace(ctx, meta.GetExternalName(cr)), errWorkspace)
}

type external struct {
tf tfclient
kube client.Client
tf tfclient
kube client.Client
logger logging.Logger
}

func (c *external) checkDiff(ctx context.Context, cr *v1beta1.Workspace) (bool, error) {
Expand Down Expand Up @@ -319,6 +334,12 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
}
cr.Status.AtProvider = generateWorkspaceObservation(op)

checksum, err := c.tf.GenerateChecksum(ctx)
if err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, errChecksum)
}
cr.Status.AtProvider.Checksum = checksum

if !differs {
// TODO(negz): Allow Workspaces to optionally derive their readiness from an
// output - similar to the logic XRs use to derive readiness from a field of
Expand Down
Loading