Skip to content

Commit

Permalink
feat: handle terragrunt codebases (#91)
Browse files Browse the repository at this point in the history
* feat: update specs to handle terragrunt

* feat: add terragrunt in runner WIP

* build: regenerate CRDs

* chore: add logging

* chore: clean naming conventions

* feat: add terragrunt implementation of runner exec

* test: remove useless module

* fix: use linux in download url

* chore: add logs for debugging

* fix(terragrunt): issue with testdata module sourcing

* chore: use main branch in manifests/all.yaml

* chore: clean runner implementation

* docs: update doc with new spec and terragrunt

* fix: use url from repo instead of config

* docs: reformulate

* chore: remove dead code

---------

Co-authored-by: Alan <alanl@padok.fr>
  • Loading branch information
spoukke and Alan-pad authored Mar 14, 2023
1 parent 4719975 commit 9e1f38c
Show file tree
Hide file tree
Showing 21 changed files with 596 additions and 136 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ metadata:
name: random-pets
namespace: burrito
spec:
terraformVersion: "1.3.1"
terraform:
version: "1.3.1"
path: "internal/e2e/testdata/random-pets"
branch: "main"
repository:
Expand Down
37 changes: 37 additions & 0 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,40 @@ const (
DryRemediationStrategy RemediationStrategy = "dry"
AutoApplyRemediationStrategy RemediationStrategy = "autoApply"
)

type TerraformConfig struct {
Version string `json:"version,omitempty"`
TerragruntConfig TerragruntConfig `json:"terragrunt,omitempty"`
}

type TerragruntConfig struct {
Enabled *bool `json:"enabled,omitempty"`
Version string `json:"version,omitempty"`
}

func GetTerraformVersion(repository *TerraformRepository, layer *TerraformLayer) string {
version := repository.Spec.TerraformConfig.Version
if len(layer.Spec.TerraformConfig.Version) > 0 {
version = layer.Spec.TerraformConfig.Version
}
return version
}

func GetTerragruntVersion(repository *TerraformRepository, layer *TerraformLayer) string {
version := repository.Spec.TerraformConfig.TerragruntConfig.Version
if len(layer.Spec.TerraformConfig.TerragruntConfig.Version) > 0 {
version = layer.Spec.TerraformConfig.TerragruntConfig.Version
}
return version
}

func GetTerragruntEnabled(repository *TerraformRepository, layer *TerraformLayer) bool {
enabled := false
if repository.Spec.TerraformConfig.TerragruntConfig.Enabled != nil {
enabled = *repository.Spec.TerraformConfig.TerragruntConfig.Enabled
}
if layer.Spec.TerraformConfig.TerragruntConfig.Enabled != nil {
enabled = *layer.Spec.TerraformConfig.TerragruntConfig.Enabled
}
return enabled
}
2 changes: 1 addition & 1 deletion api/v1alpha1/terraformlayer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type TerraformLayerSpec struct {

Path string `json:"path,omitempty"`
Branch string `json:"branch,omitempty"`
TerraformVersion string `json:"terraformVersion,omitempty"`
TerraformConfig TerraformConfig `json:"terraform,omitempty"`
Repository TerraformLayerRepository `json:"repository,omitempty"`
RemediationStrategy RemediationStrategy `json:"remediationStrategy,omitempty"`
PlanOnPullRequest bool `json:"planOnPullRequest,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/terraformrepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type TerraformRepositorySpec struct {
// Important: Run "make" to regenerate code after modifying this file

Repository TerraformRepositoryRepository `json:"repository,omitempty"`
TerraformConfig TerraformConfig `json:"terraform,omitempty"`
RemediationStrategy RemediationStrategy `json:"remediationStrategy,omitempty"`
OverrideRunnerSpec OverrideRunnerSpec `json:"overrideRunnerSpec,omitempty"`
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/controllers/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func buildControllersStartCmd(app *burrito.App) *cobra.Command {
cmd.Flags().StringSliceVar(&app.Config.Controller.Types, "types", []string{"layer", "repository"}, "list of controllers to start")

cmd.Flags().DurationVar(&app.Config.Controller.Timers.DriftDetection, "drift-detection-period", defaultDriftDetectionTimer, "period between two plans. Must end with s, m or h.")
cmd.Flags().DurationVar(&app.Config.Controller.Timers.OnError, "on-error-period", defaultOnErrorTimer, "period between two runners launch when an error occured. Must end with s, m or h.")
cmd.Flags().DurationVar(&app.Config.Controller.Timers.OnError, "on-error-period", defaultOnErrorTimer, "period between two runners launch when an error occurred. Must end with s, m or h.")
cmd.Flags().DurationVar(&app.Config.Controller.Timers.WaitAction, "wait-action-period", defaultWaitActionTimer, "period between two runners when a layer is locked. Must end with s, m or h.")
cmd.Flags().BoolVar(&app.Config.Controller.LeaderElection.Enabled, "leader-election", true, "whether leader election is enabled or not, default to true")
cmd.Flags().StringVar(&app.Config.Controller.LeaderElection.ID, "leader-election-id", "6d185457.terraform.padok.cloud", "lease id used for leader election")
Expand Down
14 changes: 12 additions & 2 deletions config/crd/bases/config.terraform.padok.cloud_terraformlayers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,18 @@ spec:
namespace:
type: string
type: object
terraformVersion:
type: string
terraform:
properties:
terragrunt:
properties:
enabled:
type: boolean
version:
type: string
type: object
version:
type: string
type: object
type: object
status:
description: TerraformLayerStatus defines the observed state of TerraformLayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ spec:
url:
type: string
type: object
terraform:
properties:
terragrunt:
properties:
enabled:
type: boolean
version:
type: string
type: object
version:
type: string
type: object
type: object
status:
description: TerraformRepositoryStatus defines the observed state of TerraformRepository
Expand Down
44 changes: 41 additions & 3 deletions docs/contents/usage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- [User guide](#user-guide)
- [Override the runner pod spec](#override-the-runner-pod-spec)
- [Choose your remediation strategy](#choose-your-remediation-strategy)
- [Choose your terraform version](#choose-your-terraform-version)
- [Use Terragrunt](#use-terragrunt)
- [Operator guide](#operator-guide)
- [Setup a git webhook](#setup-a-git-webhook)
- [Configuration](#configuration)
Expand Down Expand Up @@ -48,7 +50,8 @@ metadata:
name: random-pets
namespace: burrito
spec:
terraformVersion: "1.3.1"
terraform:
version: "1.3.1"
path: "internal/e2e/testdata/random-pets"
branch: "main"
repository:
Expand Down Expand Up @@ -85,7 +88,8 @@ metadata:
name: random-pets
namespace: burrito
spec:
terraformVersion: "1.3.1"
terraform:
version: "1.3.1"
path: "internal/e2e/testdata/random-pets"
branch: "main"
repository:
Expand All @@ -112,6 +116,40 @@ The configuration of the `TerraformLayer` will take precedence.

> :warning: This operator is still experimental. Use `spec.remediationStrategy: "autoApply"` at your own risk.

### Choose your terraform version

Both `TerraformRepository` and `TerraformLayer` expose a `spec.terrafrom.version` map field.

If the field is specified for a given `TerraformRepository` it will be applied by default to all `TerraformLayer` linked to it.

If the field is specified for a given `TerraformLayer` it will take precedence over the `TerraformRepository` configuration.

### Use Terragrunt

You can specify usage of terragrunt as follow:

```yaml
apiVersion: config.terraform.padok.cloud/v1alpha1
kind: TerraformLayer
metadata:
name: random-pets-terragrunt
spec:
terraform:
version: "1.3.1"
terragrunt:
enabled: true
version: "0.44.5"
remediationStrategy: dry
path: "internal/e2e/testdata/terragrunt/random-pets/prod"
branch: "feat/handle-terragrunt"
repository:
kind: TerraformRepository
name: burrito
namespace: burrito
```

> This configuration can be specified at the `TerraformRepository` level to be enabled by default in each of its layers.

## Operator guide

### Setup a git webhook
Expand Down Expand Up @@ -151,7 +189,7 @@ You can configure `burrito` with environment variables.
| :-----------------------------------------: | :--------------------------------------------------------------------: | :------------------------------: |
| `BURRITO_CONTROLLER_TYPES` | list of controllers to start | `layer,repository` |
| `BURRITO_CONTROLLER_TIMERS_DRIFTDETECTION` | period between two plans for drift detection | `20m` |
| `BURRITO_CONTROLLER_TIMERS_ONERROR` | period between two runners launch when an error occured | `1m` |
| `BURRITO_CONTROLLER_TIMERS_ONERROR` | period between two runners launch when an error occurred | `1m` |
| `BURRITO_CONTROLLER_TIMERS_WAITACTION` | period between two runners launch when a layer is locked | `1m` |
| `BURRITO_CONTROLLER_LEADERELECTION_ENABLED` | whether leader election is enabled or not | `true` |
| `BURRITO_CONTROLLER_LEADERELECTION_ID` | lease id used for leader election | `6d185457.terraform.padok.cloud` |
Expand Down
11 changes: 6 additions & 5 deletions internal/burrito/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,23 @@ type ControllerTimers struct {
}

type RepositoryConfig struct {
URL string `yaml:"url"`
SSHPrivateKey string `yaml:"sshPrivateKey"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}

type RunnerConfig struct {
Path string `yaml:"path"`
Branch string `yaml:"branch"`
Version string `yaml:"version"`
Action string `yaml:"action"`
Repository RepositoryConfig `yaml:"repository"`
Layer Layer `yaml:"layer"`
Repository RepositoryConfig `yaml:"repository"`
SSHKnownHostsConfigMapName string `yaml:"sshKnowHostsConfigMapName"`
}

type TerragruntConfig struct {
Enabled bool `yaml:"enabled"`
Version string `yaml:"version"`
}

type Layer struct {
Name string `yaml:"name"`
Namespace string `yaml:"namespace"`
Expand Down
16 changes: 0 additions & 16 deletions internal/controllers/terraformlayer/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,6 @@ func defaultPodSpec(config *config.Config, layer *configv1alpha1.TerraformLayer,
Name: "BURRITO_REDIS_DATABASE",
Value: fmt.Sprintf("%d", config.Redis.Database),
},
{
Name: "BURRITO_RUNNER_REPOSITORY_URL",
Value: repository.Spec.Repository.Url,
},
{
Name: "BURRITO_RUNNER_PATH",
Value: layer.Spec.Path,
},
{
Name: "BURRITO_RUNNER_BRANCH",
Value: layer.Spec.Branch,
},
{
Name: "BURRITO_RUNNER_VERSION",
Value: layer.Spec.TerraformVersion,
},
{
Name: "BURRITO_RUNNER_LAYER_NAME",
Value: layer.GetObjectMeta().GetName(),
Expand Down
File renamed without changes.
11 changes: 11 additions & 0 deletions internal/e2e/testdata/terragrunt/modules/random-pets/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
resource "random_pet" "first" {
length = 1
}

resource "random_pet" "second" {
length = 2
}

resource "random_pet" "third" {
length = 3
}
3 changes: 3 additions & 0 deletions internal/e2e/testdata/terragrunt/random-pets/module.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
source = "../../modules//random-pets"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
inputs = {}
14 changes: 14 additions & 0 deletions internal/e2e/testdata/terragrunt/random-pets/prod/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
include "root" {
path = find_in_parent_folders()
merge_strategy = "deep"
}

include "module" {
path = find_in_parent_folders("module.hcl")
merge_strategy = "deep"
}

include "inputs" {
path = "inputs.hcl"
merge_strategy = "deep"
}
Empty file.
57 changes: 57 additions & 0 deletions internal/runner/clone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package runner

import (
"strings"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/padok-team/burrito/internal/burrito/config"
log "github.com/sirupsen/logrus"
)

func clone(repository config.RepositoryConfig, URL, branch, path string) (*git.Repository, error) {
cloneOptions, err := getCloneOptions(repository, URL, branch, path)
if err != nil {
return &git.Repository{}, err
}
return git.PlainClone(WorkingDir, false, cloneOptions)
}

func getCloneOptions(repository config.RepositoryConfig, URL, branch, path string) (*git.CloneOptions, error) {
authMethod := "ssh"
cloneOptions := &git.CloneOptions{
ReferenceName: plumbing.NewBranchReferenceName(branch),
URL: URL,
}
if strings.Contains(URL, "https://") {
authMethod = "https"
}
log.Infof("clone method is %s", authMethod)
switch authMethod {
case "ssh":
if repository.SSHPrivateKey == "" {
log.Infof("detected keyless authentication")
return cloneOptions, nil
}
log.Infof("private key found")
publicKeys, err := ssh.NewPublicKeys("git", []byte(repository.SSHPrivateKey), "")
if err != nil {
return cloneOptions, err
}
cloneOptions.Auth = publicKeys

case "https":
if repository.Username != "" && repository.Password != "" {
log.Infof("username and password found")
cloneOptions.Auth = &http.BasicAuth{
Username: repository.Username,
Password: repository.Password,
}
} else {
log.Infof("passwordless authentication detected")
}
}
return cloneOptions, nil
}
Loading

0 comments on commit 9e1f38c

Please sign in to comment.