Skip to content

Commit 971b964

Browse files
authored
Added tests (#18)
# What * [x] Add `basic` component test * [x] Add `disabled` component test * [x] Test component drifting # Why * Test basic component features * Verify that the component does not create any resources when input `enabled: false` set * Verify that the component does not drift on a second run with the same inputs * Add test for any additional than basic use cases for the component # References * [Component test writing guide](https://docs.cloudposse.com/community/contribute/component-testing/) * [List of component tests are done](https://github.com/orgs/cloudposse-terraform-components/projects/1/views/3) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved Kubernetes provider authentication handling for accurate role determination. - **Chores** - Updated remote state modules and added ignore rules. - Established a new Go testing module with updated dependencies. - Removed an outdated test script. - **New Features** - Introduced comprehensive CLI and sample configuration files for multi-account setups, EKS clusters, VPCs, and AWS Load Balancer Controller (both enabled and disabled modes). - Added vendor configuration for managing Terraform module dependencies. - **Tests** - Added a new test suite to validate AWS Load Balancer Controller deployment. - Introduced additional test configurations for various components and environments. - Added import section for dependencies in test configurations. - Included new test cases for various configurations and components. - Added new YAML configuration files for various components and organizational setups. - Created a new test suite for validating deployment functionality in the EKS cluster. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 9dd9cc0 commit 971b964

File tree

16 files changed

+1248
-7
lines changed

16 files changed

+1248
-7
lines changed

src/provider-helm.tf

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,8 @@ locals {
133133
"--profile", var.kube_exec_auth_aws_profile
134134
] : []
135135

136-
kube_exec_auth_role_arn = coalesce(var.kube_exec_auth_role_arn, module.iam_roles.terraform_role_arn)
137136
exec_role = local.kube_exec_auth_enabled && var.kube_exec_auth_role_arn_enabled ? [
138-
"--role-arn", local.kube_exec_auth_role_arn
137+
"--role-arn", coalesce(var.kube_exec_auth_role_arn, module.iam_roles.terraform_role_arn)
139138
] : []
140139

141140
# Provide dummy configuration for the case where the EKS cluster is not available.

src/remote-state.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module "eks" {
22
source = "cloudposse/stack-config/yaml//modules/remote-state"
3-
version = "1.5.0"
3+
version = "1.8.0"
44

55
component = var.eks_component_name
66

@@ -9,7 +9,7 @@ module "eks" {
99

1010
module "vpc" {
1111
source = "cloudposse/stack-config/yaml//modules/remote-state"
12-
version = "1.5.0"
12+
version = "1.8.0"
1313

1414
component = var.vpc_component_name
1515

test/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
state/
2+
.cache
3+
test/test-suite.json
4+
.atmos
5+
test_suite.yaml

test/component_test.go

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"strings"
7+
"fmt"
8+
"time"
9+
helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
10+
awsHelper "github.com/cloudposse/test-helpers/pkg/aws"
11+
"github.com/gruntwork-io/terratest/modules/random"
12+
"github.com/cloudposse/test-helpers/pkg/atmos"
13+
"github.com/stretchr/testify/assert"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
networkingv1 "k8s.io/api/networking/v1"
16+
corev1 "k8s.io/api/core/v1"
17+
18+
"k8s.io/client-go/informers"
19+
"k8s.io/client-go/tools/cache"
20+
)
21+
22+
type ComponentSuite struct {
23+
helper.TestSuite
24+
}
25+
26+
func (s *ComponentSuite) TestBasic() {
27+
const component = "eks/alb-controller/basic"
28+
const stack = "default-test"
29+
const awsRegion = "us-east-2"
30+
31+
randomID := strings.ToLower(random.UniqueId())
32+
33+
controllerNamespace := fmt.Sprintf("alb-controller-%s", randomID)
34+
35+
input := map[string]interface{}{
36+
"kubernetes_namespace": controllerNamespace,
37+
}
38+
39+
defer s.DestroyAtmosComponent(s.T(), component, stack, &input)
40+
options, _ := s.DeployAtmosComponent(s.T(), component, stack, &input)
41+
assert.NotNil(s.T(), options)
42+
43+
type Metadata struct {
44+
AppVersion string `json:"app_version"`
45+
Chart string `json:"chart"`
46+
FirstDeployed float64 `json:"first_deployed"`
47+
LastDeployed float64 `json:"last_deployed"`
48+
Name string `json:"name"`
49+
Namespace string `json:"namespace"`
50+
Notes string `json:"notes"`
51+
Revision int `json:"revision"`
52+
Values string `json:"values"`
53+
Version string `json:"version"`
54+
}
55+
56+
metadataArray := []Metadata{}
57+
58+
atmos.OutputStruct(s.T(), options, "metadata", &metadataArray)
59+
60+
metadata := metadataArray[0]
61+
62+
assert.Equal(s.T(), metadata.AppVersion, "v2.7.1")
63+
assert.Equal(s.T(), metadata.Chart, "aws-load-balancer-controller")
64+
assert.NotNil(s.T(), metadata.FirstDeployed)
65+
assert.NotNil(s.T(), metadata.LastDeployed)
66+
assert.Equal(s.T(), metadata.Name, "aws-load-balancer-controller")
67+
assert.Equal(s.T(), metadata.Namespace, controllerNamespace)
68+
assert.Equal(s.T(), metadata.Notes, "AWS Load Balancer controller installed!\n")
69+
assert.Equal(s.T(), metadata.Revision, 1)
70+
assert.NotNil(s.T(), metadata.Values)
71+
assert.Equal(s.T(), metadata.Version, "1.7.1")
72+
73+
clusterOptions := s.GetAtmosOptions("eks/cluster", stack, nil)
74+
clusrerId := atmos.Output(s.T(), clusterOptions, "eks_cluster_id")
75+
cluster := awsHelper.GetEksCluster(s.T(), context.Background(), awsRegion, clusrerId)
76+
clientset, err := awsHelper.NewK8SClientset(cluster)
77+
assert.NoError(s.T(), err)
78+
assert.NotNil(s.T(), clientset)
79+
80+
deployment, err := clientset.AppsV1().Deployments(controllerNamespace).Get(context.Background(), "aws-load-balancer-controller", metav1.GetOptions{})
81+
assert.NoError(s.T(), err)
82+
assert.NotNil(s.T(), deployment)
83+
assert.Equal(s.T(), deployment.Name, "aws-load-balancer-controller")
84+
assert.Equal(s.T(), deployment.Namespace, controllerNamespace)
85+
assert.Equal(s.T(), *deployment.Spec.Replicas, int32(2))
86+
87+
88+
ingressNamespace := fmt.Sprintf("example-%s", randomID)
89+
ingressName := fmt.Sprintf("example-ingress-%s", randomID)
90+
91+
defer func() {
92+
if err := clientset.CoreV1().Namespaces().Delete(context.Background(), ingressNamespace, metav1.DeleteOptions{}); err != nil {
93+
fmt.Printf("Error deleting namespace %s: %v\n", ingressNamespace, err)
94+
}
95+
}()
96+
_, err = clientset.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{
97+
ObjectMeta: metav1.ObjectMeta{
98+
Name: ingressNamespace,
99+
},
100+
}, metav1.CreateOptions{})
101+
assert.NoError(s.T(), err)
102+
103+
104+
pathType := networkingv1.PathTypeImplementationSpecific
105+
106+
ingress := &networkingv1.Ingress{
107+
ObjectMeta: metav1.ObjectMeta{
108+
Name: ingressName,
109+
Annotations: map[string]string{
110+
"alb.ingress.kubernetes.io/load-balancer-name": ingressName,
111+
"alb.ingress.kubernetes.io/group.name": "example-group",
112+
"external-dns.alpha.kubernetes.io/hostname": "example.com",
113+
},
114+
},
115+
Spec: networkingv1.IngressSpec{
116+
DefaultBackend: &networkingv1.IngressBackend{
117+
Service: &networkingv1.IngressServiceBackend{
118+
Name: "default",
119+
Port: networkingv1.ServiceBackendPort{
120+
Name: "use-annotation",
121+
},
122+
},
123+
},
124+
TLS: []networkingv1.IngressTLS{
125+
{
126+
Hosts: []string{"example.com"},
127+
},
128+
},
129+
Rules: []networkingv1.IngressRule{
130+
{
131+
Host: "example.com",
132+
IngressRuleValue: networkingv1.IngressRuleValue{
133+
HTTP: &networkingv1.HTTPIngressRuleValue{
134+
Paths: []networkingv1.HTTPIngressPath{
135+
{
136+
Path: "/",
137+
PathType: &pathType,
138+
Backend: networkingv1.IngressBackend{
139+
Service: &networkingv1.IngressServiceBackend{
140+
Name: "default",
141+
Port: networkingv1.ServiceBackendPort{
142+
Number: 80,
143+
},
144+
},
145+
},
146+
},
147+
},
148+
},
149+
},
150+
},
151+
},
152+
},
153+
}
154+
155+
defer func() {
156+
if err := clientset.NetworkingV1().Ingresses(ingressNamespace).Delete(context.Background(), ingressName, metav1.DeleteOptions{}); err != nil {
157+
fmt.Printf("Error deleting ingress %s: %v\n", ingressName, err)
158+
}
159+
}()
160+
_, err = clientset.NetworkingV1().Ingresses(ingressNamespace).Create(context.Background(), ingress, metav1.CreateOptions{})
161+
assert.NoError(s.T(), err)
162+
assert.NotNil(s.T(), ingress)
163+
164+
// Wait for the Ingress to be updated with the LoadBalancer metadata
165+
factory := informers.NewSharedInformerFactory(clientset, 0)
166+
informer := factory.Networking().V1().Ingresses().Informer()
167+
168+
stopChannel := make(chan struct{})
169+
170+
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
171+
UpdateFunc: func(oldObj, newObj interface{}) {
172+
_, ok := oldObj.(*networkingv1.Ingress)
173+
if !ok {
174+
return
175+
}
176+
newIngress, ok := newObj.(*networkingv1.Ingress)
177+
if !ok {
178+
return
179+
}
180+
181+
// Check if the Ingress's LoadBalancer status has been populated
182+
if len(newIngress.Status.LoadBalancer.Ingress) > 0 && len(newIngress.Status.LoadBalancer.Ingress[0].Hostname) > 0 {
183+
fmt.Printf("Ingress %s is ready\n", newIngress.Name)
184+
close(stopChannel)
185+
} else {
186+
fmt.Printf("Ingress %s is not ready yet\n", newIngress.Name)
187+
}
188+
},
189+
})
190+
go informer.Run(stopChannel)
191+
192+
select {
193+
case <-stopChannel:
194+
msg := "All ingress have joined the EKS cluster"
195+
fmt.Println(msg)
196+
case <-time.After(1 * time.Minute):
197+
msg := "Not all worker nodes have joined the EKS cluster"
198+
fmt.Println(msg)
199+
}
200+
201+
ingressStatus, err := clientset.NetworkingV1().Ingresses(ingressNamespace).Get(context.Background(), ingressName, metav1.GetOptions{})
202+
assert.NoError(s.T(), err)
203+
assert.Equal(s.T(), ingressStatus.Name, ingressName)
204+
assert.NotEmpty(s.T(), ingressStatus.Status.LoadBalancer.Ingress[0].Hostname)
205+
206+
s.DriftTest(component, stack, &input)
207+
}
208+
209+
func (s *ComponentSuite) TestEnabledFlag() {
210+
const component = "eks/alb-controller/disabled"
211+
const stack = "default-test"
212+
s.VerifyEnabledFlag(component, stack, nil)
213+
}
214+
215+
func (s *ComponentSuite) SetupSuite() {
216+
s.TestSuite.InitConfig()
217+
s.TestSuite.Config.ComponentDestDir = "components/terraform/eks/alb-controller"
218+
s.TestSuite.SetupSuite()
219+
}
220+
221+
func TestRunSuite(t *testing.T) {
222+
suite := new(ComponentSuite)
223+
suite.AddDependency(t, "vpc", "default-test", nil)
224+
suite.AddDependency(t, "eks/cluster", "default-test", nil)
225+
helper.Run(t, suite)
226+
}

test/fixtures/atmos.yaml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# CLI config is loaded from the following locations (from lowest to highest priority):
2+
# system dir (`/usr/local/etc/atmos` on Linux, `%LOCALAPPDATA%/atmos` on Windows)
3+
# home dir (~/.atmos)
4+
# current directory
5+
# ENV vars
6+
# Command-line arguments
7+
#
8+
# It supports POSIX-style Globs for file names/paths (double-star `**` is supported)
9+
# https://en.wikipedia.org/wiki/Glob_(programming)
10+
11+
# Base path for components, stacks and workflows configurations.
12+
# Can also be set using `ATMOS_BASE_PATH` ENV var, or `--base-path` command-line argument.
13+
# Supports both absolute and relative paths.
14+
# If not provided or is an empty string, `components.terraform.base_path`, `components.helmfile.base_path`, `stacks.base_path` and `workflows.base_path`
15+
# are independent settings (supporting both absolute and relative paths).
16+
# If `base_path` is provided, `components.terraform.base_path`, `components.helmfile.base_path`, `stacks.base_path` and `workflows.base_path`
17+
# are considered paths relative to `base_path`.
18+
base_path: ""
19+
20+
components:
21+
terraform:
22+
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_BASE_PATH` ENV var, or `--terraform-dir` command-line argument
23+
# Supports both absolute and relative paths
24+
base_path: "components/terraform"
25+
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE` ENV var
26+
apply_auto_approve: true
27+
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT` ENV var, or `--deploy-run-init` command-line argument
28+
deploy_run_init: true
29+
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE` ENV var, or `--init-run-reconfigure` command-line argument
30+
init_run_reconfigure: true
31+
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE` ENV var, or `--auto-generate-backend-file` command-line argument
32+
auto_generate_backend_file: true
33+
34+
stacks:
35+
# Can also be set using `ATMOS_STACKS_BASE_PATH` ENV var, or `--config-dir` and `--stacks-dir` command-line arguments
36+
# Supports both absolute and relative paths
37+
base_path: "stacks"
38+
# Can also be set using `ATMOS_STACKS_INCLUDED_PATHS` ENV var (comma-separated values string)
39+
# Since we are distinguishing stacks based on namespace, and namespace is not part
40+
# of the stack name, we have to set `included_paths` via the ENV var in the Dockerfile
41+
included_paths:
42+
- "orgs/**/*"
43+
44+
# Can also be set using `ATMOS_STACKS_EXCLUDED_PATHS` ENV var (comma-separated values string)
45+
excluded_paths:
46+
- "**/_defaults.yaml"
47+
48+
# Can also be set using `ATMOS_STACKS_NAME_PATTERN` ENV var
49+
name_pattern: "{tenant}-{stage}"
50+
51+
workflows:
52+
# Can also be set using `ATMOS_WORKFLOWS_BASE_PATH` ENV var, or `--workflows-dir` command-line arguments
53+
# Supports both absolute and relative paths
54+
base_path: "stacks/workflows"
55+
56+
# https://github.com/cloudposse/atmos/releases/tag/v1.33.0
57+
logs:
58+
file: "/dev/stdout"
59+
# Supported log levels: Trace, Debug, Info, Warning, Off
60+
level: Info
61+
62+
settings:
63+
# Can also be set using 'ATMOS_SETTINGS_LIST_MERGE_STRATEGY' environment variable, or '--settings-list-merge-strategy' command-line argument
64+
list_merge_strategy: replace
65+
66+
# `Go` templates in Atmos manifests
67+
# https://atmos.tools/core-concepts/stacks/templating
68+
# https://pkg.go.dev/text/template
69+
templates:
70+
settings:
71+
enabled: true
72+
# https://masterminds.github.io/sprig
73+
sprig:
74+
enabled: true
75+
# https://docs.gomplate.ca
76+
gomplate:
77+
enabled: true
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
components:
2+
terraform:
3+
account-map:
4+
metadata:
5+
terraform_workspace: core-gbl-root
6+
vars:
7+
tenant: core
8+
environment: gbl
9+
stage: root
10+
11+
# This remote state is only for Cloud Posse internal use.
12+
# It references the Cloud Posse test organizations actual infrastructure.
13+
# remote_state_backend:
14+
# s3:
15+
# bucket: cptest-core-ue2-root-tfstate-core
16+
# dynamodb_table: cptest-core-ue2-root-tfstate-core-lock
17+
# role_arn: arn:aws:iam::822777368227:role/cptest-core-gbl-root-tfstate-core-ro
18+
# encrypt: true
19+
# key: terraform.tfstate
20+
# acl: bucket-owner-full-control
21+
# region: us-east-2
22+
23+
remote_state_backend_type: static
24+
remote_state_backend:
25+
# This static backend is used for tests that only need to use the account map iam-roles module
26+
# to find the role to assume for Terraform operations. It is configured to use whatever
27+
# the current user's role is, but the environment variable `TEST_ACCOUNT_ID` must be set to
28+
# the account ID of the account that the user is currently assuming a role in.
29+
#
30+
# For some components, this backend is missing important data, and those components
31+
# will need that data added to the backend configuration in order to work properly.
32+
static:
33+
account_info_map: {}
34+
all_accounts: []
35+
aws_partition: aws
36+
full_account_map: {}
37+
iam_role_arn_templates: {}
38+
non_eks_accounts: []
39+
profiles_enabled: false
40+
root_account_aws_name: root
41+
terraform_access_map: {}
42+
terraform_dynamic_role_enabled: false
43+
terraform_role_name_map:
44+
apply: terraform
45+
plan: planner
46+
terraform_roles: {}

0 commit comments

Comments
 (0)