Skip to content

Commit

Permalink
feat: Cache workspace blob on tf-controller filesystem
Browse files Browse the repository at this point in the history
- block stream encryption + remove non-stream endpoint
- Nothing uses the feature yet.

```
❯ kubectl exec -it -n flux-system chart-tf-controller-648fbc54f8-7sprc -- ls -la /blob-cache/
total 12
drwxrwsrwx    2 root     1337          4096 Dec 19 15:29 .
drwxr-xr-x    1 root     root          4096 Dec 19 15:29 ..
-rw-r--r--    1 controll 1337           800 Dec 20 09:41 terraform-helloworld-tf-priv.tar.gz

❯ kubectl exec -it -n flux-system chart-tf-controller-648fbc54f8-7sprc -- hexdump -C /blob-cache/terraform-helloworld-tf-priv.tar.gz | head -n 3
00000000  1b 3f 00 8d 25 67 17 79  87 04 d7 b9 03 f2 6c ba  |.?..%g.y......l.|
00000010  bc 0c 7e 75 29 de 25 1f  bb 99 c4 49 2d 99 1b e0  |..~u).%....I-...|
00000020  b3 72 2f ca ab fb 5f 93  ee b4 ba bd a6 76 83 38  |.r/..._......v.8|
```

with a small go temp app, after decryption and untar (the repo itself
has only one file: `main.tf`):
```
❯ kubectl cp -n flux-system chart-tf-controller-648fbc54f8-7sprc:/blob-cache/terraform-helloworld-tf-priv.tar.gz ./terraform-helloworld-tf-priv.tar.gz
tar: removing leading '/' from member names

❯ go run .
INFO[0000] /tmp/1417200660

❯ tree /tmp/1417200660
/tmp/1417200660
├── backend_override.tf
├── generated.auto.tfvars.json
└── main.tf

1 directory, 3 files
```

Extra To Do items:
- Add feature flag

Signed-off-by: Balazs Nadasdi <[email protected]>
Signed-off-by: Victoria Nadasdi <[email protected]>
  • Loading branch information
yitsushi committed Feb 5, 2024
1 parent 6b07f66 commit 0d9e1e9
Show file tree
Hide file tree
Showing 14 changed files with 403 additions and 147 deletions.
1 change: 1 addition & 0 deletions charts/tf-controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ __Note__: If you need to use the `imagePullSecrets` it would be best to set `ser
| awsPackage.install | bool | `true` | |
| awsPackage.repository | string | `"ghcr.io/tf-controller/aws-primitive-modules"` | |
| awsPackage.tag | string | `"v4.38.0-v1alpha11"` | |
| blobCacheVolume.emptyDir | object | `{}` | |
| branchPlanner | object | `{"configMap":"branch-planner","enabled":false,"image":{"pullPolicy":"IfNotPresent","repository":"ghcr.io/weaveworks/branch-planner","tag":""},"podSecurityContext":{"fsGroup":1337},"pollingInterval":"30s","resources":{"limits":{"cpu":"1000m","memory":"1Gi"},"requests":{"cpu":"200m","memory":"64Mi"}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"runAsUser":65532,"seccompProfile":{"type":"RuntimeDefault"}},"sourceInterval":"30s"}` | Branch Planner-specific configurations |
| caCertValidityDuration | string | `"168h0m"` | Argument for `--ca-cert-validity-duration` (Controller) |
| certRotationCheckFrequency | string | `"30m0s"` | Argument for `--cert-rotation-check-frequency` (Controller) |
Expand Down
7 changes: 6 additions & 1 deletion charts/tf-controller/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
volumes:
- name: blob-cache
{{- toYaml .Values.blobCacheVolume | nindent 8 }}
containers:
- args:
- --allow-cross-namespace-refs={{ .Values.allowCrossNamespaceRefs }}
Expand Down Expand Up @@ -79,8 +82,10 @@ spec:
{{- toYaml .Values.resources | nindent 10 }}
securityContext:
{{- toYaml .Values.securityContext | nindent 10 }}
{{- with .Values.volumeMounts }}
volumeMounts:
- name: blob-cache
mountPath: /blob-cache/
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 10 }}
{{- end }}
securityContext:
Expand Down
2 changes: 1 addition & 1 deletion charts/tf-controller/templates/runner-serviceaccount.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ metadata:
{{- with $.Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 2 }}
{{- end }}
---
apiVersion: v1
kind: Secret
Expand All @@ -26,4 +27,3 @@ metadata:
type: kubernetes.io/service-account-token
{{- end }}
{{- end }}
{{- end }}
4 changes: 4 additions & 0 deletions charts/tf-controller/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ awsPackage:
install: true
tag: v4.38.0-v1alpha11
repository: ghcr.io/tf-controller/aws-primitive-modules
blobCacheVolume:
emptyDir: {}
# persistentVolumeClaim:
# claimName: tf-controller-blob-cache
# -- Runner-specific configurations
runner:
image:
Expand Down
27 changes: 27 additions & 0 deletions controllers/filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package controllers

import (
"io"
"os"
"path"
)

type Filesystem interface {
GetWriter(filePath string) (io.WriteCloser, error)
GetReader(filePath string) (io.ReadCloser, error)
}

type LocalFilesystem struct {
root string
}

func NewLocalFilesystem(root string) *LocalFilesystem {
return &LocalFilesystem{root: root}
}

func (fs *LocalFilesystem) GetWriter(filePath string) (io.WriteCloser, error) {
return os.Create(path.Join(fs.root, filePath))
}
func (fs *LocalFilesystem) GetReader(filePath string) (io.ReadCloser, error) {
return os.Open(path.Join(fs.root, filePath))
}
5 changes: 5 additions & 0 deletions controllers/tf_controller_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@ terraform {
}
}

// Save workspace after init
if err := r.GetWorkspaceBlobCache(ctx, runnerClient, &terraform, tfInstance, workingDir); err != nil {
log.Error(err, "unable to get workspace blob cache")
}

return terraform, tfInstance, tmpDir, nil
}

Expand Down
86 changes: 86 additions & 0 deletions controllers/tf_controller_blob_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package controllers

import (
"bytes"
"context"
"crypto/sha256"
"fmt"
"io"

infrav1 "github.com/weaveworks/tf-controller/api/v1alpha2"
"github.com/weaveworks/tf-controller/runner"
ctrl "sigs.k8s.io/controller-runtime"
)

// TODO: This path is hard-coded in the Deployment of the controller as mount
// point. Why not /tmp? Good question, it's a ro mount so we can't write there.
const cacheDir = "/blob-cache"

func (r *TerraformReconciler) GetWorkspaceBlobCache(ctx context.Context, runnerClient runner.RunnerClient, terraform *infrav1.Terraform, tfInstance, workdir string) error {
log := ctrl.LoggerFrom(ctx).WithValues("step", "get workspace blob cache")

log.Info("request workspace blob from runner", "workdir", workdir, "tfInstance", tfInstance)
streamClient, err := runnerClient.CreateWorkspaceBlobStream(ctx, &runner.CreateWorkspaceBlobRequest{
TfInstance: tfInstance,
WorkingDir: workdir,
Namespace: terraform.Namespace,
})
if err != nil {
return err
}

fs := NewLocalFilesystem(cacheDir)
sha := sha256.New()
checksum := []byte{}

// TODO: This file pattern needs some love, it's there as a placeholder.
// It would be beneficial if we can add the commit hash to the filename, but
// then it would be problematic to retrieve when the source is not available,
// and that's one of the reasons why we do this in the first place, so we can
// get the cached content even if source is not available.
//
// NOTE: We can use commit hash from Source and if it's not available use from
// lastAppliedRevision, lastAttemptedRevision, or lastPlannedRevision.
file, err := fs.GetWriter(fmt.Sprintf("%s-%s.tar.gz", terraform.GetNamespace(), terraform.GetName()))
if err != nil {
return err
}
defer file.Close()

for {
chunk, err := streamClient.Recv()
if err != nil {
if err == io.EOF {
if err := streamClient.CloseSend(); err != nil {
log.Error(err, "unabel to close stream")
break
}
}

return err
}

if len(chunk.Blob) > 0 {
if _, err := sha.Write(chunk.Blob); err != nil {
return err
}

if _, err := file.Write(chunk.Blob); err != nil {
return err
}
}

if len(chunk.Sha256Checksum) > 0 {
checksum = chunk.GetSha256Checksum()
}
}

log.Info("calculating checksum")
sum := sha.Sum(nil)

if !bytes.Equal(sum, checksum) {
return fmt.Errorf("invalid checksum, got: '%x'; expected: '%x'", sum, checksum)
}

return nil
}
2 changes: 1 addition & 1 deletion internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func ArchiveDir(dir string) (out string, err error) {
}
}()

if err := os.Chdir(filepath.Dir(dir)); err != nil {
if err := os.Chdir(dir); err != nil {
return "", err
}

Expand Down
94 changes: 47 additions & 47 deletions runner/runner.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion runner/runner.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ service Runner {

rpc Init(InitRequest) returns (InitReply) {}
rpc SelectWorkspace(WorkspaceRequest) returns (WorkspaceReply) {}
rpc CreateWorkspaceBlob(CreateWorkspaceBlobRequest) returns (CreateWorkspaceBlobReply) {}
rpc Upload(UploadRequest) returns (UploadReply) {}

rpc FinalizeSecrets(FinalizeSecretsRequest) returns (FinalizeSecretsReply) {}
rpc ForceUnlock(ForceUnlockRequest) returns (ForceUnlockReply) {}

rpc StartBreakTheGlassSession(BreakTheGlassRequest) returns (BreakTheGlassReply) {}
rpc HasBreakTheGlassSessionDone(BreakTheGlassRequest) returns (BreakTheGlassReply) {}

rpc CreateWorkspaceBlobStream(CreateWorkspaceBlobRequest) returns (stream CreateWorkspaceBlobReply) {}
}

message LookPathRequest {
Expand Down
Loading

0 comments on commit 0d9e1e9

Please sign in to comment.