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

feat: handle terragrunt codebases #91

Merged
merged 16 commits into from
Mar 14, 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 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
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