Skip to content

Commit

Permalink
feat: Support helm chart deployments (#2723)
Browse files Browse the repository at this point in the history
* feat: Happy app example with Helm

* Bump

* Bump

* Bump

* commit from cli-ci -- updated coverage

* Update target_group_only.tf

* Update target_group_only.tf

* Removed target group support

* Bump

* commit from ci -- ran terraform-docs and pushed

* commit from ci -- ran terraform-docs and pushed

* Bump

* commit from ci -- ran terraform-docs and pushed

* commit from ci -- ran terraform fmt and pushed

* commit from ci -- ran terraform-docs and pushed

* Update main.tf

* Update main.tf

* Update main.tf

* Update main.tf

* Bump

* Update charts-release.yml

* Per code review

---------

Co-authored-by: alexlokshin-czi <alexlokshin-czi@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: czi-github-helper[bot] <czi-github-helper[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Nov 15, 2023
1 parent ca7308c commit 8f2b65d
Show file tree
Hide file tree
Showing 36 changed files with 443 additions and 262 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/charts-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
${HELM_CMD} repo index ${PACKAGE_DIR} --url ${CHART_DOWNLOAD_URL} --merge ${INDEX_DIR}/index.yaml
cd ../chart-repo
mv ${PACKAGE_DIR}/index.yaml .${INDEX_DIR}/index.yaml
mv ${INDEX_DIR}/index.yaml ./index.yaml
git add -A
git commit -m "chore: publish charts from ${{ github.repository }} ${{ needs.check-chart-released.outputs.tag }}"
git push
Expand Down
2 changes: 1 addition & 1 deletion cli/COVERAGE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.12
22.10
2 changes: 1 addition & 1 deletion cli/cmd/COVERAGE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
11.7
11.6
16 changes: 10 additions & 6 deletions cli/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ var createCmd = &cobra.Command{
RunE: runCreate,
}

// keep in sync with happy-stack-eks terraform module
const terraformECRTargetPathTemplate = `module.stack.module.services["%s"].module.ecr`

func runCreate(
cmd *cobra.Command,
args []string,
Expand All @@ -90,7 +87,7 @@ func runCreate(
validateStackNameGloballyAvailable(ctx, happyClient.StackService, stackName, force),
validateTFEBackLog(ctx, happyClient.AWSBackend),
validateStackExistsCreate(ctx, stackName, happyClient, message),
validateECRExists(ctx, stackName, terraformECRTargetPathTemplate, happyClient, message),
validateECRExists(ctx, stackName, happyClient, message),
validateImageExists(ctx, createTag, skipCheckTag, imageSrcEnv, imageSrcStack, imageSrcRoleArn, happyClient, cmd.Flags().Changed(config.FlagAWSProfile)),
)
if err != nil {
Expand All @@ -115,7 +112,10 @@ func runCreate(
return nil
}

func validateECRExists(ctx context.Context, stackName string, ecrTargetPathFormat string, happyClient *HappyClient, options ...workspace_repo.TFERunOption) validation {
// keep in sync with happy-stack-eks and happy-stack-helm-eks terraform module
var ecrTargetPathFormats = []string{`module.stack.module.services["%s"].module.ecr`, `module.stack.module.ecr["%s"]`}

func validateECRExists(ctx context.Context, stackName string, happyClient *HappyClient, options ...workspace_repo.TFERunOption) validation {
log.Debug("Scheduling validateECRExists()")
return func() error {
log.Debug("Running validateECRExists()")
Expand All @@ -141,7 +141,9 @@ func validateECRExists(ctx context.Context, stackName string, ecrTargetPathForma
log.Debugf("missing ECRs for the following services %s. making them now", strings.Join(missingServiceECRs, ","))
targetAddrs := []string{}
for _, service := range happyClient.HappyConfig.GetServices() {
targetAddrs = append(targetAddrs, fmt.Sprintf(ecrTargetPathFormat, service))
for _, ecrTargetPathFormat := range ecrTargetPathFormats {
targetAddrs = append(targetAddrs, fmt.Sprintf(ecrTargetPathFormat, service))
}
}
stack, err := happyClient.StackService.GetStack(ctx, stackName)
if err != nil {
Expand All @@ -160,6 +162,8 @@ func validateECRExists(ctx context.Context, stackName string, ecrTargetPathForma
tfDirPath := happyClient.HappyConfig.TerraformDirectory()
happyProjectRoot := happyClient.HappyConfig.GetProjectRoot()
srcDir := filepath.Join(happyProjectRoot, tfDirPath)

log.Debugf("Adding ECRs to stack %s, terraform targets: %s", stack.Name, strings.Join(targetAddrs, ","))
return stack.Apply(ctx, srcDir, makeWaitOptions(stackName, happyClient.HappyConfig, happyClient.AWSBackend), append(options, workspace_repo.TargetAddrs(targetAddrs))...)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var pushCmd = &cobra.Command{
validateGitTree(happyClient.HappyConfig.GetProjectRoot()),
validateStackNameAvailable(ctx, happyClient.StackService, stackName, force),
validateStackExistsCreate(ctx, stackName, happyClient),
validateECRExists(ctx, stackName, terraformECRTargetPathTemplate, happyClient),
validateECRExists(ctx, stackName, happyClient),
)
if err != nil {
return errors.Wrap(err, "failed one of the happy client validations")
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func runUpdate(cmd *cobra.Command, args []string) error {
validateStackNameAvailable(ctx, happyClient.StackService, stackName, force),
validateTFEBackLog(ctx, happyClient.AWSBackend),
validateStackExistsUpdate(ctx, stackName, happyClient),
validateECRExists(ctx, stackName, terraformECRTargetPathTemplate, happyClient),
validateECRExists(ctx, stackName, happyClient),
validateImageExists(ctx, createTag, skipCheckTag, imageSrcEnv, imageSrcStack, imageSrcRoleArn, happyClient, cmd.Flags().Changed(config.FlagAWSProfile)),
)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions examples/integration_test_helm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.happy/terraform/envs/*/.terraform/
.happy/terraform/envs/*/.terraform.lock.hcl
54 changes: 54 additions & 0 deletions examples/integration_test_helm/.happy/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"config_version": "v3",
"default_env": "rdev",
"app": "integration-test-helm",
"default_compose_env_file": ".env.ecr",
"environments": {
"rdev": {
"aws_profile": "czi-playground",
"aws_region": "us-west-2",
"k8s": {
"namespace": "si-rdev-happy-eks-rdev-happy-env",
"cluster_id": "si-playground-eks-v2",
"auth_method": "eks",
"context": "si-playground-eks-v2"
},
"terraform_directory": ".happy/terraform/envs/rdev",
"task_launch_type": "k8s"
}
},
"slice_default_tag": "branch-main",
"services": [
"frontend"
],
"features": {
"enable_dynamo_locking": true,
"enable_ecr_auto_creation": true
},
"api": {},
"stack_defaults": {
"routing_method": "CONTEXT",
"services": {
"frontend": {
"build": {
"context": "/Users/alokshin/GitHub/chanzuckerberg/happy/examples/integration_test_helm/src/api",
"dockerfile": "Dockerfile"
},
"cpu": "100m",
"desired_count": 1,
"health_check_path": "/health",
"max_count": 1,
"memory": "100Mi",
"name": "frontend",
"path": "/*",
"platform_architecture": "arm64",
"port": 3000,
"scaling_cpu_threshold_percentage": 50,
"scan_on_push": true,
"service_type": "INTERNAL",
"tag_mutability": false
}
},
"source": "git@github.com:chanzuckerberg/happy//terraform/modules/happy-stack-helm-eks?ref=main"
}
}
55 changes: 55 additions & 0 deletions examples/integration_test_helm/.happy/terraform/envs/rdev/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module "stack" {
source = "git@github.com:chanzuckerberg/happy//terraform/modules/happy-stack-helm-eks?ref=main"

image_tag = var.image_tag
image_tags = jsondecode(var.image_tags)
stack_name = var.stack_name
deployment_stage = "rdev"

stack_prefix = "/${var.stack_name}"
k8s_namespace = var.k8s_namespace

// this allow these services under the same domain
// each service is reachable via their path configured below
routing_method = "CONTEXT"

services = {
frontend = {
name = "frontend"
desired_count = 10
// the maximum number of copies of this service it can autoscale to
max_count = 50
// the signal used to trigger autoscaling (ie. 50% of CPU means scale up)
scaling_cpu_threshold_percentage = 50
// the port the service is running on
port = 3000
memory = "500Mi"
memory_requests = "300Mi"
cpu = "500m"
cpu_requests = "500m"
// an endpoint that returns a 200. Your service will not start if this endpoint is not healthy
health_check_path = "/health"
// oneof: INTERNAL, EXTERNAL, PRIVATE, TARGET_GROUP_ONLY, IMAGE_TEMPLATE
// INTERNAL: OIDC protected endpoints
// EXTERNAL: internet accessible
// PRIVATE: only accessible within the cluster
// TARGET_GROUP_ONLY: attach to an existing ALB rather than making a new one
// IMAGE_TEMPLATE: don't deploy any services, just use to create and push images
service_type = "INTERNAL"
// the path to reach this search
path = "/*"
// the platform architecture of the container. this should match what is in
// the platform attribute of your docker-compose.yml file for your service.
// oneof: amd64, arm64.
// Try to always select arm since it comes with a lot of cost savings and performance
// benefits and has little to no impact on developers.
platform_architecture = "arm64"
scan_on_push = true
tag_mutability = false
}
}

// tasks can be utilized to run post-deployment tasks such as database migrations or deletions
tasks = {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
output "service_urls" {
value = module.stack.service_endpoints
description = "The URL endpoint for the frontend website service"
sensitive = false
}

output "service_ecrs" {
value = module.stack.service_ecrs
description = "The services ECR locations for their docker containers"
sensitive = false
}

output "k8s_namespace" {
value = data.kubernetes_namespace.happy-namespace.metadata.0.name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
provider "aws" {
region = "us-west-2"
assume_role {
role_arn = "arn:aws:iam::${var.aws_account_id}:role/${var.aws_role}"
}
allowed_account_ids = [var.aws_account_id]
}

provider "aws" {
alias = "czi-si"
region = "us-west-2"

assume_role {
role_arn = "arn:aws:iam::626314663667:role/tfe-si"
}

allowed_account_ids = ["626314663667"]
}

data "aws_eks_cluster" "cluster" {
name = var.k8s_cluster_id
}

data "aws_eks_cluster_auth" "cluster" {
name = var.k8s_cluster_id
}

provider "kubernetes" {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
token = data.aws_eks_cluster_auth.cluster.token
}

data "kubernetes_namespace" "happy-namespace" {
metadata {
name = var.k8s_namespace
}
}

data "aws_ssm_parameter" "dd_app_key" {
name = "/shared-infra-prod-datadog/app_key"
provider = aws.czi-si
}
data "aws_ssm_parameter" "dd_api_key" {
name = "/shared-infra-prod-datadog/api_key"
provider = aws.czi-si
}

provider "datadog" {
app_key = data.aws_ssm_parameter.dd_app_key.value
api_key = data.aws_ssm_parameter.dd_api_key.value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
variable "aws_account_id" {
type = string
description = "AWS account ID to apply changes to"
}

variable "k8s_cluster_id" {
type = string
description = "EKS K8S Cluster ID"
}

variable "k8s_namespace" {
type = string
description = "K8S namespace for this stack"
}

variable "aws_role" {
type = string
description = "Name of the AWS role to assume to apply changes"
}

variable "image_tag" {
type = string
description = "Please provide an image tag"
}

variable "image_tags" {
type = string
description = "Override the default image tags (json-encoded map)"
default = "{}"
}

variable "stack_name" {
type = string
description = "Happy Path stack name"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.14"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.16"
}
datadog = {
source = "datadog/datadog"
version = ">= 3.20.0"
}
happy = {
source = "chanzuckerberg/happy"
version = ">= 0.53.5"
}
}
required_version = ">= 1.3"
}

3 changes: 3 additions & 0 deletions examples/integration_test_helm/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM nginx:1.23-alpine
EXPOSE 3000
CMD ["/bin/sh", "-c", "sed -i 's/listen .*/listen 3000;/g' /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"]
13 changes: 13 additions & 0 deletions examples/integration_test_helm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Integration test app with Helm

An integration test application deployed using happy path.

## Prerequistes

* Install the latest version of happy: `brew tap chanzuckerberg/tap && brew install happy`
* Make sure you have access to the czi-playground AWS environment

## Notes

* All stacks in this examples folder will be automatically cleaned up within 24 hours of creation; it is not intended for production usage
* All stacks are created in the czi-playground environment; all CZI employees should have access to this environmnet
11 changes: 11 additions & 0 deletions examples/integration_test_helm/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "3.8"

services:
frontend:
image: "frontend"
profiles: [ "*" ]
platform: linux/arm64
build:
context: src/api
dockerfile: Dockerfile

1 change: 1 addition & 0 deletions examples/integration_test_helm/src/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
api
20 changes: 20 additions & 0 deletions examples/integration_test_helm/src/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM golang:1.20-alpine AS builder
WORKDIR /app
RUN apk update && apk upgrade
RUN apk --update add --no-cache git tzdata
ADD . .
RUN GOPROXY=direct go build -o api

# This artificially adds high level vulnerabilities for testing purposes
FROM alpine:3.9
WORKDIR /app
RUN apk update && apk upgrade && apk --no-cache add curl
# # Uncomment the statement below to detect vulnerabilities
# RUN apk add --no-cache git make gcc g++ libc-dev pkgconfig \
# libxml2-dev libxslt-dev postgresql-dev coreutils curl wget bash \
# gnupg tar linux-headers bison readline-dev readline zlib-dev \
# zlib yaml-dev autoconf ncurses-dev curl-dev apache2-dev \
# libx11-dev libffi-dev tcl-dev tk-dev openjdk8
COPY --from=builder /app/api /app/
EXPOSE 3000
ENTRYPOINT ./api
Loading

0 comments on commit 8f2b65d

Please sign in to comment.