From b3b3e628878800099ce34e9ac7877cd2a869d98b Mon Sep 17 00:00:00 2001 From: Haim Bell Date: Sun, 4 Aug 2024 18:53:20 +0300 Subject: [PATCH 1/2] feat(dotnet-terraform-aws-deployment-ecs): add plugin --- package-lock.json | 45 ++++ .../.amplicationrc.json | 25 +++ .../.eslintrc.json | 4 + .../.npmignore | 2 + .../.prettierignore | 1 + .../README.md | 27 +++ .../package.json | 36 ++++ .../project.json | 6 + .../src/constants.ts | 16 ++ .../src/index.ts | 148 +++++++++++++ .../src/static/ecs-template.tf | 195 ++++++++++++++++++ .../src/tests/.keep | 0 .../src/types.ts | 20 ++ .../src/utils.ts | 45 ++++ .../tsconfig.json | 5 + .../webpack.config.js | 43 ++++ 16 files changed, 618 insertions(+) create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/tests/.keep create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json create mode 100644 plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js diff --git a/package-lock.json b/package-lock.json index 2603ab6d9..414bdb86a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,6 +230,10 @@ "resolved": "plugins/dotnet-provisioning-terraform-aws-database-rds", "link": true }, + "node_modules/@amplication/plugin-dotnet-provisioning-terraform-aws-deployment-ecs": { + "resolved": "plugins/dotnet-provisioning-terraform-aws-deployment-ecs", + "link": true + }, "node_modules/@amplication/plugin-formatter-prettier": { "resolved": "plugins/formatter-prettier", "link": true @@ -17122,6 +17126,7 @@ } }, "plugins/dotnet-provisioning-terraform-aws-database-rds": { + "name": "@amplication/plugin-dotnet-provisioning-terraform-aws-database-rds", "version": "0.0.1", "license": "Apache-2.0", "devDependencies": { @@ -17161,6 +17166,46 @@ "@amplication/csharp-ast": "*" } }, + "plugins/dotnet-provisioning-terraform-aws-deployment-ecs": { + "version": "1.0.5", + "license": "Apache-2.0", + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "@types/lodash": "^4.14.200", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.52.0", + "jest-mock-extended": "^3.0.5", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.0", + "typescript": "^5.2.2", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" + } + }, + "plugins/dotnet-provisioning-terraform-aws-deployment-ecs/node_modules/@amplication/code-gen-types": { + "version": "2.0.34", + "resolved": "https://registry.npmjs.org/@amplication/code-gen-types/-/code-gen-types-2.0.34.tgz", + "integrity": "sha512-RLjFuooQoCRomZ6KBHw+Kp25OsvpJ1eAwLi+jO/6S/9QkAGZ+jHWhUUoSiyI8pmWYvi7T2VI8b6d5K8s1Nzh9w==", + "dev": true, + "dependencies": { + "ast-types": "^0.14.2", + "json-schema": "^0.4.0", + "prisma-schema-dsl-types": "^1.1.2", + "tslib": "^2.6.2", + "type-fest": "^3.11.0" + }, + "peerDependencies": { + "@amplication/csharp-ast": "*" + } + }, "plugins/formatter-prettier": { "name": "@amplication/plugin-formatter-prettier", "version": "1.0.7", diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json new file mode 100644 index 000000000..c98640fd2 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json @@ -0,0 +1,25 @@ +{ + "settings": { + "cluster": { + "name": "", + "capacity_provider": { + "type": "fargate", + "fargate": { + "fargate_weight": 100, + "fargate_base": 0, + "fargate_spot_weight": 0 + } + } + }, + "service": { + "name": "", + "container_definitions": { + "image": "0000000000.dkr.ecr.eu-west-1.amazonaws.com/service-name", + "port": 3000 + } + } + }, + "systemSettings": { + "requireAuthenticationEntity": "false" + } +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json new file mode 100644 index 000000000..052a5874e --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"] +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore new file mode 100644 index 000000000..99ba36b26 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore @@ -0,0 +1,2 @@ +.prettierignore +.gitignore \ No newline at end of file diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md new file mode 100644 index 000000000..20c4c1ca8 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md @@ -0,0 +1,27 @@ +# @amplication/plugin-provisioning-terraform-aws-deployment-ecs + +[![NPM Downloads](https://img.shields.io/npm/dt/@amplication/plugin-provisioning-terraform-aws-deployment-ecs)](https://www.npmjs.com/package/@amplication/plugin-provisioning-terraform-aws-deployment-ecs) + +Adds terraform code for provisioning Amazon Web Services Elastic Container Service (ECS) as an addition to the 'core' terraform code base. + +## Purpose + +Adds terraform code for provisioning Amazon Web Services Elastic Container Service (ECS) as an addition to the 'core' terraform code base. + +## Configuration + +If a configuration is required, add it here. + +## Scripts + +### `build` + +Running `npm run build` will bundle your plugin with Webpack for production. + +### `dev` + +Running `npm run dev` will watch your plugin's source code and automatically bundle it with every change. + +## Usage + +Explain the usage of this plugin and its effect on the final build. diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json new file mode 100644 index 000000000..768b4b30d --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json @@ -0,0 +1,36 @@ +{ + "name": "@amplication/plugin-dotnet-provisioning-terraform-aws-deployment-ecs", + "version": "1.0.5", + "description": "Adds terraform code for provisioning Amazon Web Services core network infrastructure", + "main": "dist/index.js", + "nx": {}, + "scripts": { + "prepublishOnly": "npm run build", + "dev": "webpack --watch", + "build": "webpack", + "prebuild": "rimraf dist" + }, + "author": "Haim Bell", + "license": "Apache-2.0", + "dependencies": {}, + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "@types/lodash": "^4.14.200", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.52.0", + "jest-mock-extended": "^3.0.5", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.0", + "typescript": "^5.2.2", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" + } +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json new file mode 100644 index 000000000..1407e4eb5 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json @@ -0,0 +1,6 @@ +{ + "targets": { + "lint": {}, + "npm:publish": {} + } +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts new file mode 100644 index 000000000..43c57e9f4 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts @@ -0,0 +1,16 @@ +// generic constants +export const moduleNameEcsClusterKey = "${{ ECS_CLUSTER_MODULE_NAME }}"; +export const moduleNameEcsServiceKey = "${{ ECS_SERVICE_MODULE_NAME }}"; +export const moduleNameEcsAlbKey = "${{ ECS_ALB_MODULE_NAME }}"; +export const moduleNameEcsSgKey = "${{ ECS_SG_MODULE_NAME }}"; + +// settings cluster constants +export const clusterHyphenNameKey = "${{ CLUSTER_NAME }}"; +export const clusterUnderscoreNameKey = "${{ CLUSTER_NAME_UNDERSCORE }}"; +export const clusterCapacityProviderKey = "${{ CLUSTER_CAPACITY_PROVIDER }}"; + +// settings service constants +export const serviceHyphenNameKey = "${{ SERVICE_NAME }}"; +export const serviceUnderscoreNameKey = "${{ SERVICE_NAME_UNDERSCORE }}"; +export const serviceContainerImage = "${{ SERVICE_CONTAINER_IMAGE }}"; +export const serviceContainerPort = "${{ SERVICE_CONTAINER_PORT }}"; diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts new file mode 100644 index 000000000..f2f435508 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts @@ -0,0 +1,148 @@ +import { + dotnetPluginEventsTypes, + dotnetPluginEventsParams as dotnet, + dotnetTypes, + FileMap, + IFile, +} from "@amplication/code-gen-types"; +import { CodeBlock } from "@amplication/csharp-ast"; +import { + clusterHyphenNameKey, + clusterUnderscoreNameKey, + moduleNameEcsClusterKey, + moduleNameEcsServiceKey, + moduleNameEcsAlbKey, + moduleNameEcsSgKey, + clusterCapacityProviderKey, + serviceContainerImage, + serviceContainerPort, + serviceHyphenNameKey, + serviceUnderscoreNameKey, +} from "./constants"; +import { resolve } from "path"; +import { getPluginSettings, getTerraformDirectory } from "./utils"; +import { kebabCase, snakeCase } from "lodash"; + +class TerraformAwsDeploymentEcsPlugin implements dotnetTypes.AmplicationPlugin { + register(): dotnetPluginEventsTypes.DotnetEvents { + return { + LoadStaticFiles: { + after: this.afterLoadStaticFiles, + }, + }; + } + async afterLoadStaticFiles( + context: dotnetTypes.DsgContext, + eventParams: dotnet.LoadStaticFilesParams, + files: FileMap + ): Promise> { + context.logger.info(`Generating Terraform AWS Deployment ECS...`); + + // get the name for the service, to be used as a fallback for the + // repository name + const serviceName = kebabCase(context.resourceInfo?.name); + if (!serviceName) { + throw new Error( + "TerraformAwsRepositoryEcrPlugin: Service name is undefined" + ); + } + + // instantiate a variable consisting of the path on the + // 'provisioning-terraform-aws-core' made up of the settings + // 'root_directory' & 'directory_name', this function will throw + // an error if the aforementioned plugin wasnt installed. + const terraformDirectory = getTerraformDirectory( + context.pluginInstallations, + context.serverDirectories.baseDirectory + ); + + // fetch the plugin specific settings and merge them with the defaults + const settings = getPluginSettings(context.pluginInstallations); + + const templateFileName = "ecs-template.tf"; + const fileNamePrefix = "ecs-"; + const fileNameSuffix = ".tf"; + const ecsServiceName: string = settings.service.name + ? settings.service.name + : serviceName; + const ecsClusterName: string = settings.cluster.name + ? settings.cluster.name + : serviceName; + + const staticPath = resolve(__dirname, "./static"); + const staticFiles = await context.utils.importStaticFiles( + staticPath, + terraformDirectory + ); + + // switch statement for determining capacity provider within the + // cluster configuration, default to fargate + let capacityProvider: string; + switch (settings.cluster.capacity_provider.type) { + default: { + capacityProvider = `fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = ${settings.cluster.capacity_provider.fargate?.fargate_weight} + base = ${settings.cluster.capacity_provider.fargate?.fargate_base} + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = ${settings.cluster.capacity_provider.fargate?.fargate_spot_weight} + } + } + }`; + } + } + + const hyphenServiceName: string = kebabCase(ecsServiceName); + const underscoreServiceName: string = snakeCase(ecsServiceName); + const hyphenClusterName: string = kebabCase(ecsClusterName); + const underscoreClusterName: string = snakeCase(ecsClusterName); + for (const item of staticFiles.getAll()) { + const newPath = item.path.replace( + templateFileName, + fileNamePrefix + kebabCase(serviceName) + fileNameSuffix + ); + + const newCode = item.code + .replaceAll(clusterHyphenNameKey, hyphenClusterName) + .replaceAll(clusterUnderscoreNameKey, underscoreClusterName) + .replaceAll(serviceHyphenNameKey, hyphenServiceName) + .replaceAll(serviceUnderscoreNameKey, underscoreServiceName) + .replaceAll( + moduleNameEcsClusterKey, + "ecs_cluster_" + underscoreClusterName + ) + .replaceAll( + moduleNameEcsServiceKey, + "ecs_service_" + underscoreServiceName + ) + .replaceAll(moduleNameEcsAlbKey, "ecs_alb_" + underscoreServiceName) + .replaceAll(moduleNameEcsSgKey, "ecs_alb_sg_" + underscoreServiceName) + .replaceAll(clusterCapacityProviderKey, capacityProvider) + .replaceAll( + serviceContainerImage, + settings.service.container_definitions.image + ) + .replaceAll( + serviceContainerPort, + String(settings.service.container_definitions.port) + ); + const file: IFile = { + path: newPath, + code: new CodeBlock({ + code: newCode, + }), + }; + files.set(file); + } + + context.logger.info(`Generated Terraform AWS Deployment ECS...`); + + return files; + } +} + +export default TerraformAwsDeploymentEcsPlugin; diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf new file mode 100644 index 000000000..0945b889d --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf @@ -0,0 +1,195 @@ +module "${{ ECS_CLUSTER_MODULE_NAME }}" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "5.2.2" + + cluster_name = "${{ CLUSTER_NAME }}" + + ${{ CLUSTER_CAPACITY_PROVIDER }} +} + +module "${{ ECS_SERVICE_MODULE_NAME }}" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "5.2.2" + + name = "${{ SERVICE_NAME }}" + cluster_arn = module.${{ ECS_CLUSTER_MODULE_NAME }}.arn + + cpu = 1024 + memory = 4096 + + container_definitions = { + ("${{ SERVICE_NAME }}") = { + essential = true + cpu = 512 + memory = 1024 + image = "${{ SERVICE_CONTAINER_IMAGE }}" + + port_mappings = [ + { + name = "${{ SERVICE_NAME }}" + containerPort = ${{ SERVICE_CONTAINER_PORT }} + hostPort = ${{ SERVICE_CONTAINER_PORT }} + protocol = "tcp" + } + ] + + readonly_root_filesystem = false + + enable_cloudwatch_logging = false + + log_configuration = { + logDriver = "awslogs" + options = { + awslogs-create-group = "true" + awslogs-group = "/ecs/${{ SERVICE_NAME }}" + awslogs-region = local.region + awslogs-stream-prefix = "ecs" + } + } + + memory_reservation = 100 + } + } + + load_balancer = { + service = { + target_group_arn = element(module.${{ ECS_ALB_MODULE_NAME }}.target_group_arns, 0) + container_name = "${{ SERVICE_NAME }}" + container_port = ${{ SERVICE_CONTAINER_PORT }} + } + } + + subnet_ids = module.vpc.private_subnets + + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = ${{ SERVICE_CONTAINER_PORT }} + to_port = ${{ SERVICE_CONTAINER_PORT }} + protocol = "tcp" + source_security_group_id = module.${{ ECS_SG_MODULE_NAME }}.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } +} + +resource "aws_service_discovery_http_namespace" "${{ CLUSTER_NAME_UNDERSCORE }}" { + name = "${{ CLUSTER_NAME }}" +} + +module "${{ ECS_SG_MODULE_NAME }}" { + source = "terraform-aws-modules/security-group/aws" + version = "5.1.0" + + name = "${{ SERVICE_NAME }}" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] + + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks +} + +module "${{ ECS_ALB_MODULE_NAME }}" { + source = "terraform-aws-modules/alb/aws" + version = "8.7.0" + + name = "${{ SERVICE_NAME }}" + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + security_groups = [module.${{ ECS_SG_MODULE_NAME }}.security_group_id] + + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + }, + ] + + target_groups = [ + { + name = "${{ SERVICE_NAME }}" + backend_protocol = "HTTP" + backend_port = ${{ SERVICE_CONTAINER_PORT }} + target_type = "ip" + + health_check = { + enabled = true + interval = 30 + path = "/api/_health/live" + port = "traffic-port" + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 6 + protocol = "HTTP" + matcher = "200-299" + } + }, + ] +} + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.cluster_capacity_providers +} + +output "service_id" { + description = "ARN that identifies the service" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.id +} + +output "service_name" { + description = "Name of the service" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.task_definition_arn +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/tests/.keep b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/tests/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts new file mode 100644 index 000000000..fde72d54b --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts @@ -0,0 +1,20 @@ +export interface Settings { + cluster: { + name: string; + capacity_provider: { + type: string; + fargate?: { + fargate_weight: number; + fargate_base: number; + fargate_spot_weight: number; + }; + }; + }; + service: { + name: string; + container_definitions: { + image: string; + port: number; + }; + }; +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts new file mode 100644 index 000000000..1c0f20f02 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts @@ -0,0 +1,45 @@ +import { PluginInstallation } from "@amplication/code-gen-types"; +import { name as PackageName } from "../package.json"; +import { Settings } from "./types"; +import defaultSettings from "../.amplicationrc.json"; + +export const getPluginSettings = ( + pluginInstallations: PluginInstallation[] +): Settings => { + const plugin = pluginInstallations.find( + (plugin) => plugin.npm === PackageName + ); + + const userSettings = plugin?.settings ?? {}; + + const settings: Settings = { + ...defaultSettings.settings, + ...userSettings, + }; + + return settings; +}; + +export const getTerraformDirectory = ( + pluginInstallations: PluginInstallation[], + serverBaseDirectory: string +): string => { + const plugin = pluginInstallations.find( + (plugin) => + plugin.npm === "@amplication/plugin-dotnet-provisioning-terraform-aws-core" + ); + + if (!plugin) { + throw new Error( + "TerraformAwsDeploymentEcsPlugin: is dependent on 'Terraform - AWS Core' plugin" + ); + } + + const { root_level, directory_name } = plugin.settings; + + if (root_level) { + return `./${directory_name}`; + } else { + return `./${serverBaseDirectory}/${directory_name}`; + } +}; diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json new file mode 100644 index 000000000..6646f44f5 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/index.ts"], + "exclude": ["node_modules"] +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js new file mode 100644 index 000000000..07eadf115 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js @@ -0,0 +1,43 @@ +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); + +/** @type {import("webpack").Configuration} */ +module.exports = { + mode: "production", + target: "node", + entry: "./src/index.ts", + externals: ["@amplication/code-gen-utils", "@amplication/code-gen-types"], + plugins: [ + new webpack.SourceMapDevToolPlugin({ + filename: "[name].js.map", + }), + new CopyWebpackPlugin({ + patterns: [ + { from: "src/static", to: "static", noErrorOnMissing: true }, + { from: "src/templates", to: "templates", noErrorOnMissing: true }, + ], + }), + ], + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".ts", ".js", ".json"], + }, + optimization: { + minimize: false, + }, + output: { + filename: "index.js", + path: path.resolve(__dirname, "dist"), + libraryTarget: "commonjs2", + clean: true, + }, +}; From ccf86e4f7e05c81217c38b0228ed0c10fea0fa33 Mon Sep 17 00:00:00 2001 From: Haim Bell Date: Sun, 4 Aug 2024 19:18:44 +0300 Subject: [PATCH 2/2] feat(dotnet-terraform-gcp-core): add plugin --- package-lock.json | 46 +++++++ .../.amplicationrc.json | 33 +++++ .../.eslintrc.json | 18 +++ .../.npmignore | 2 + .../.prettierignore | 1 + .../README.md | 46 +++++++ .../package.json | 54 ++++++++ .../project.json | 6 + .../src/constants.ts | 10 ++ .../src/index.ts | 128 ++++++++++++++++++ .../src/static/README.md | 24 ++++ .../src/static/backend.tf | 1 + .../src/static/data.tf | 3 + .../src/static/folders.tf | 24 ++++ .../src/static/groups.tf | 16 +++ .../src/static/logging.tf | 21 +++ .../src/static/main.tf | 18 +++ .../src/static/outputs.tf | 0 .../src/static/projects-common.tf | 70 ++++++++++ .../src/static/projects-host.tf | 27 ++++ .../src/static/projects-service.tf | 39 ++++++ .../src/static/provider.tf | 22 +++ .../src/static/variables.tf | 29 ++++ .../src/static/vpc.tf | 32 +++++ .../src/tests/.keep | 0 .../src/types.ts | 34 +++++ .../src/utils.ts | 21 +++ .../tsconfig.json | 5 + .../webpack.config.js | 43 ++++++ 29 files changed, 773 insertions(+) create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/.amplicationrc.json create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/.eslintrc.json create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/.npmignore create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/.prettierignore create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/README.md create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/package.json create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/project.json create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/constants.ts create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/index.ts create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/README.md create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/backend.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/data.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/folders.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/groups.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/logging.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/main.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/outputs.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-common.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-host.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-service.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/provider.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/variables.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/static/vpc.tf create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/tests/.keep create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/types.ts create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/src/utils.ts create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/tsconfig.json create mode 100644 plugins/dotnet-provisioning-terraform-gcp-core/webpack.config.js diff --git a/package-lock.json b/package-lock.json index 414bdb86a..99d48c47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,10 @@ "resolved": "plugins/dotnet-deployment-helm-chart", "link": true }, + "node_modules/@amplication/dotnet-plugin-provisioning-terraform-gcp-core": { + "resolved": "plugins/dotnet-provisioning-terraform-gcp-core", + "link": true + }, "node_modules/@amplication/plugin-auth-auth0": { "resolved": "plugins/auth-auth0", "link": true @@ -17167,6 +17171,7 @@ } }, "plugins/dotnet-provisioning-terraform-aws-deployment-ecs": { + "name": "@amplication/plugin-dotnet-provisioning-terraform-aws-deployment-ecs", "version": "1.0.5", "license": "Apache-2.0", "devDependencies": { @@ -17206,6 +17211,47 @@ "@amplication/csharp-ast": "*" } }, + "plugins/dotnet-provisioning-terraform-gcp-core": { + "version": "0.0.10", + "license": "Apache-2.0", + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "@types/jest": "^29.5.11", + "@types/lodash": "^4.14.202", + "@typescript-eslint/eslint-plugin": "^6.13.2", + "@typescript-eslint/parser": "^6.13.2", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.55.0", + "jest-mock-extended": "^3.0.5", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^5.0.5", + "ts-jest": "^29.1.1", + "ts-loader": "^9.5.1", + "typescript": "^5.3.3", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + } + }, + "plugins/dotnet-provisioning-terraform-gcp-core/node_modules/@amplication/code-gen-types": { + "version": "2.0.34", + "resolved": "https://registry.npmjs.org/@amplication/code-gen-types/-/code-gen-types-2.0.34.tgz", + "integrity": "sha512-RLjFuooQoCRomZ6KBHw+Kp25OsvpJ1eAwLi+jO/6S/9QkAGZ+jHWhUUoSiyI8pmWYvi7T2VI8b6d5K8s1Nzh9w==", + "dev": true, + "dependencies": { + "ast-types": "^0.14.2", + "json-schema": "^0.4.0", + "prisma-schema-dsl-types": "^1.1.2", + "tslib": "^2.6.2", + "type-fest": "^3.11.0" + }, + "peerDependencies": { + "@amplication/csharp-ast": "*" + } + }, "plugins/formatter-prettier": { "name": "@amplication/plugin-formatter-prettier", "version": "1.0.7", diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/.amplicationrc.json b/plugins/dotnet-provisioning-terraform-gcp-core/.amplicationrc.json new file mode 100644 index 000000000..cec399aa8 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/.amplicationrc.json @@ -0,0 +1,33 @@ +{ + "settings": { + "root_level": true, + "directory_name": "terraform", + "global": { + "organization_id": "123456789012", + "billing_account": "012A34-B56C57-890DEF", + "billing_project": "billing-012345", + "domain": "example.com", + "region_prefix": "europe-west" + }, + "environments": { + "production": { + "cidr": "10.10.0.0/16", + "teams": ["operations"] + }, + "non-production": { + "cidr": "10.20.0.0/16", + "teams": ["development", "operations"] + } + }, + "backend": { + "type": "gcs", + "gcs": { + "bucket": "terraform-state", + "prefix": "terraform/state" + } + } + }, + "systemSettings": { + "requireAuthenticationEntity": "false" + } +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/.eslintrc.json b/plugins/dotnet-provisioning-terraform-gcp-core/.eslintrc.json new file mode 100644 index 000000000..9d9c0db55 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/.npmignore b/plugins/dotnet-provisioning-terraform-gcp-core/.npmignore new file mode 100644 index 000000000..99ba36b26 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/.npmignore @@ -0,0 +1,2 @@ +.prettierignore +.gitignore \ No newline at end of file diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/.prettierignore b/plugins/dotnet-provisioning-terraform-gcp-core/.prettierignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/.prettierignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/README.md b/plugins/dotnet-provisioning-terraform-gcp-core/README.md new file mode 100644 index 000000000..c3bd3bedb --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/README.md @@ -0,0 +1,46 @@ +# @amplication/plugin-provisioning-terraform-gcp-core + +[![NPM Downloads](https://img.shields.io/npm/dt/@amplication/plugin-provisioning-terraform-gcp-core)](https://www.npmjs.com/package/@amplication/plugin-provisioning-terraform-gcp-core) + +Adds a core networking setup of terraform for Google Cloud to the generated service. + +## Purpose + +Adds a core networking setup of terraform for Google Cloud to the generated service. + +## Configuration + +`root_level` determines whether the directory where the terraform code will exists, lives under the root of the repository or on the level of the generated service. + +`directory_name` determines the name for the directory where the generated code will live under, defaults to 'terraform'. + +`global.organization_id` the identifier for the organization under which to create the core resources. + +`global.billing_account` the identifier for the billing account to which to associate the resources. + +`global.billing_project` the identifier for a pre-configured google cloud project with a linked billing account. + +`global.domain` the domain name associated with the organization. + +`global.region_prefix` the base identifier for the region, different options can be found here: https://cloud.google.com/compute/docs/regions-zones. + +`environments` the environments block allows for specifying multiple environments and the teams that are associated with that applicationb environment, example: + +```json +"environments": { + "production": { + "cidr": "10.10.0.0/16", + "teams": ["operations"] + }, + "non-production": { + "cidr": "10.20.0.0/16", + "teams": ["development", "operations"] + } +} +``` + +`backend` the backend part of the configuraiton allows for specify whether to use a `local` backend or a `gcs` backend, the latter is advised. + +## Usage + +As this is an addition to the code base, where non of the other code is touched, using the plugin won't impact the final build. diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/package.json b/plugins/dotnet-provisioning-terraform-gcp-core/package.json new file mode 100644 index 000000000..5273423b8 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/package.json @@ -0,0 +1,54 @@ +{ + "name": "@amplication/dotnet-plugin-provisioning-terraform-gcp-core", + "version": "0.0.10", + "description": "Adds a core networking setup of terraform for Google Cloud to the generated service.", + "main": "dist/index.js", + "nx": {}, + "scripts": { + "prepublishOnly": "npm run build", + "dev": "webpack --watch", + "build": "webpack", + "prebuild": "rimraf dist", + "test": "jest" + }, + "author": "Haim Bell", + "license": "Apache-2.0", + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "lodash": "^4.17.21", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "@types/jest": "^29.5.11", + "@types/lodash": "^4.14.202", + "@typescript-eslint/eslint-plugin": "^6.13.2", + "@typescript-eslint/parser": "^6.13.2", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.55.0", + "jest-mock-extended": "^3.0.5", + "prettier": "^2.6.2", + "rimraf": "^5.0.5", + "ts-jest": "^29.1.1", + "ts-loader": "^9.5.1", + "typescript": "^5.3.3", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + }, + "jest": { + "passWithNoTests": true, + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src/tests", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "testEnvironment": "node" + } +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/project.json b/plugins/dotnet-provisioning-terraform-gcp-core/project.json new file mode 100644 index 000000000..1407e4eb5 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/project.json @@ -0,0 +1,6 @@ +{ + "targets": { + "lint": {}, + "npm:publish": {} + } +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/constants.ts b/plugins/dotnet-provisioning-terraform-gcp-core/src/constants.ts new file mode 100644 index 000000000..00bc1174a --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/constants.ts @@ -0,0 +1,10 @@ +// settings +export const globalOrganisationIdKey = "${{ GLOBAL_ORGANISATION_ID }}"; +export const globalBillingAccountKey = "${{ GLOBAL_BILLING_ACCOUNT }}"; +export const globalBillingProjectKey = "${{ GLOBAL_BILLING_PROJECT }}"; +export const globalDomainKey = "${{ GLOBAL_DOMAIN }}"; +export const globalRegionPrefixKey = "${{ GLOBAL_REGION_PREFIX }}"; + +export const environmentsKey = "${{ ENVIRONMENTS }}"; + +export const backendKey = "${{ BACKEND }}"; diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/index.ts b/plugins/dotnet-provisioning-terraform-gcp-core/src/index.ts new file mode 100644 index 000000000..4cfc7a1a6 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/index.ts @@ -0,0 +1,128 @@ +import { + dotnetPluginEventsTypes, + dotnetPluginEventsParams as dotnet, + dotnetTypes, + FileMap, + IFile, +} from "@amplication/code-gen-types"; +import { CodeBlock } from "@amplication/csharp-ast"; +import { join } from "node:path"; +import { resolve } from "path"; +import { getPluginSettings } from "./utils"; +import { kebabCase } from "lodash"; +import { BackendTypes } from "./types"; +import { + backendKey, + environmentsKey, + globalOrganisationIdKey, + globalBillingAccountKey, + globalBillingProjectKey, + globalDomainKey, + globalRegionPrefixKey, +} from "./constants"; + +class TerraformGcpCorePlugin implements dotnetTypes.AmplicationPlugin { + register(): dotnetPluginEventsTypes.DotnetEvents { + return { + LoadStaticFiles: { + after: this.afterLoadStaticFiles, + }, + }; + } + async afterLoadStaticFiles( + context: dotnetTypes.DsgContext, + eventParams: dotnet.LoadStaticFilesParams, + modules: FileMap + ): Promise> { + context.logger.info("Generating Terraform Google Cloud Platform Core ..."); + + // determine the name of the service which will be used as the name for the workflow + // workflow names must be lower case letters and numbers. words may be separated with dashes (-): + const serviceName = kebabCase(context.resourceInfo?.name); + + if (!serviceName) { + throw new Error("Service name is undefined"); + } + + // fetch the plugin specific settings and merge them with the defaults + const settings = getPluginSettings(context.pluginInstallations); + + /** + * save the renderedOutput to the desired directory the options are on the root of the repository + * and within the directory of the services itself setting "root_directory": + * + * option 1 (value: true): // + * option 2 (value: false): /[optional: mono_prefix]// + */ + + const rootDirectoryPath = "./"; + const terraformDirectoryPath: string = settings.root_level + ? join(rootDirectoryPath, settings.directory_name) + : join(context.serverDirectories.baseDirectory, settings.directory_name); + + let backendConfiguration = ""; + + switch (settings.backend.type) { + case BackendTypes.Local: + backendConfiguration = `terraform { + backend "${BackendTypes.Local}" { + path = "${settings.backend?.local?.path}" + } + }`; + break; + case BackendTypes.Gcs: + backendConfiguration = `terraform { + backend "${BackendTypes.Gcs}" { + bucket = "${settings.backend?.gcs?.bucket}" + prefix = "${settings.backend?.gcs?.prefix}" + } + }`; + break; + } + + let environmentsConfiguration: string; + + if (Object.keys(settings.environments).length > 0) { + environmentsConfiguration = JSON.stringify( + settings.environments, + null, + "\t" + ); + } else { + context.logger.warn( + "TerraformGcpCorePlugin: no environments were passed..." + ); + environmentsConfiguration = "{}"; + } + + // set the path to the static files and fetch them for manipulation + const staticPath = resolve(__dirname, "./static"); + const staticFiles = await context.utils.importStaticFiles( + staticPath, + terraformDirectoryPath + ); + for (const item of staticFiles.getAll()) { + const newCode = item.code + .replaceAll(globalOrganisationIdKey, settings.global.organization_id) + .replaceAll(globalBillingAccountKey, settings.global.billing_account) + .replaceAll(globalBillingProjectKey, settings.global.billing_project) + .replaceAll(globalDomainKey, settings.global.domain) + .replaceAll(globalRegionPrefixKey, settings.global.region_prefix) + .replaceAll(environmentsKey, environmentsConfiguration) + .replaceAll(backendKey, backendConfiguration); + const file: IFile = { + path: item.path, + code: new CodeBlock({ + code: newCode, + }), + }; + modules.set(file); + } + + context.logger.info("Generated Terraform Google Cloud Platform Core ..."); + + return modules; + } +} + +export default TerraformGcpCorePlugin; diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/README.md b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/README.md new file mode 100644 index 000000000..8efb77378 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/README.md @@ -0,0 +1,24 @@ +# Terraform Google Cloud Core + +This terraform code base for Google Cloud Platform, creates the infrastructure needed to run the Amplication generated service(s). + +## Pre-requisites + +To run the commands described in this document, you need the following: + +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/install). +2. Install [Terraform](https://www.terraform.io/downloads.html). +3. Set up a Google Cloud [organization](https://cloud.google.com/resource-manager/docs/creating-managing-organization). +4. Set up a Google Cloud [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account). +5. For the user who will run the Terraform install, grant the following roles: + - The `roles/billing.admin` role on the billing account. + - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. + - The `roles/resourcemanager.folderCreator` role on the Google Cloud organization. + - The `roles/resourcemanager.projectCreator` role on the Google Cloud organization. +6. Create a [Cloud Storage bucket](https://cloud.google.com/docs/terraform/resource-management/store-state) for the terraform state and point the terraform backend to this bucket - this can be done through terraform or manual manner through the console. + +## Deploying + +1. Run `terraform init`. +2. Run `terraform plan`. +3. Run `terraform apply`. diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/backend.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/backend.tf new file mode 100644 index 000000000..e77eee287 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/backend.tf @@ -0,0 +1 @@ +${{ BACKEND }} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/data.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/data.tf new file mode 100644 index 000000000..360ba7bd1 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/data.tf @@ -0,0 +1,3 @@ +data "google_organization" "organization" { + organization = "organizations/${var.organization_id}" +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/folders.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/folders.tf new file mode 100644 index 000000000..bce578321 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/folders.tf @@ -0,0 +1,24 @@ +// https://registry.terraform.io/modules/terraform-google-modules/folders/google/4.0.1 +module "folders_top_level" { + source = "terraform-google-modules/folders/google" + version = "4.0.1" + + parent = "organizations/${var.organization_id}" + names = [ + "common", + "environments" + ] +} + +// https://registry.terraform.io/modules/terraform-google-modules/folders/google/4.0.1 +module "folders_environments_level" { + source = "terraform-google-modules/folders/google" + version = "4.0.1" + + parent = module.folders_top_level.ids["environments"] + names = keys(local.environments) + + depends_on = [ + module.folders_top_level, + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/groups.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/groups.tf new file mode 100644 index 000000000..a6694a863 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/groups.tf @@ -0,0 +1,16 @@ +// https://registry.terraform.io/modules/terraform-google-modules/group/google/0.6.0 +module "groups" { + source = "terraform-google-modules/group/google" + version = "~> 0.5" + + for_each = local.teams + + id = format("%s@%s", each.value, var.domain) + display_name = format("%s@%s", each.value, var.domain) + customer_id = data.google_organization.organization.directory_customer_id + + types = [ + "default", + "security", + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/logging.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/logging.tf new file mode 100644 index 000000000..ab35014bb --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/logging.tf @@ -0,0 +1,21 @@ +module "logging_destination" { + source = "terraform-google-modules/log-export/google//modules/logbucket" + version = "7.7.0" + + project_id = module.host_project_logging.project_id + name = "organization-logging-destination" + location = "global" + retention_days = 30 + log_sink_writer_identity = module.logging_export.writer_identity +} + +module "logging_export" { + source = "terraform-google-modules/log-export/google" + version = "7.7.0" + + destination_uri = module.logging_destination.destination_uri + log_sink_name = "organization-logging-export" + parent_resource_id = var.organization_id + parent_resource_type = "organization" + include_children = true +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/main.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/main.tf new file mode 100644 index 000000000..992707412 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/main.tf @@ -0,0 +1,18 @@ +locals { + environments = ${{ ENVIRONMENTS }} + + groups = flatten([ + for environment, configuration in local.environments : [ + for team in configuration.teams : { + environment = "${environment}" + team = "${team}" + } + ] + ]) + + teams = toset(flatten([ + for environment, configuration in local.environments : [ + for team in configuration.teams : team + ] + ])) +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/outputs.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/outputs.tf new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-common.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-common.tf new file mode 100644 index 000000000..f9e820f71 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-common.tf @@ -0,0 +1,70 @@ +// https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/14.4.0 +module "host_project_logging" { + source = "terraform-google-modules/project-factory/google" + version = "14.4.0" + + random_project_id = true + + name = "logging" + project_id = "logging" + org_id = var.organization_id + folder_id = module.folders_top_level.ids["common"] + + billing_account = var.billing_account + + activate_apis = [ + "storage.googleapis.com", + "logging.googleapis.com" + ] + + depends_on = [ + module.folders_top_level, + ] +} + +// https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/14.4.0 +module "host_project_monitoring" { + source = "terraform-google-modules/project-factory/google" + version = "14.4.0" + + random_project_id = true + + name = "monitoring" + project_id = "monitoring" + org_id = var.organization_id + folder_id = module.folders_top_level.ids["common"] + + billing_account = var.billing_account + + activate_apis = [ + "storage.googleapis.com", + "monitoring.googleapis.com" + ] + + depends_on = [ + module.folders_top_level, + ] +} + +// https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/14.4.0 +module "host_project_artifacts" { + source = "terraform-google-modules/project-factory/google" + version = "14.4.0" + + random_project_id = true + + name = "artifacts" + project_id = "artifacts" + org_id = var.organization_id + folder_id = module.folders_top_level.ids["common"] + + billing_account = var.billing_account + + activate_apis = [ + "artifactregistry.googleapis.com", + ] + + depends_on = [ + module.folders_top_level, + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-host.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-host.tf new file mode 100644 index 000000000..e9433fd23 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-host.tf @@ -0,0 +1,27 @@ +// https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/14.4.0 +module "host_project_environments" { + source = "terraform-google-modules/project-factory/google" + version = "14.4.0" + + for_each = local.environments + + random_project_id = true + + name = format("%s-hst", each.key) + project_id = format("%s-hst", each.key) + org_id = var.organization_id + folder_id = module.folders_top_level.ids["common"] + + enable_shared_vpc_host_project = true + + billing_account = var.billing_account + + activate_apis = [ + "storage.googleapis.com", + "compute.googleapis.com" + ] + + depends_on = [ + module.folders_top_level, + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-service.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-service.tf new file mode 100644 index 000000000..3ae9bd71b --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/projects-service.tf @@ -0,0 +1,39 @@ +// https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/14.4.0/submodules/svpc_service_project +module "service_project_teams" { + source = "terraform-google-modules/project-factory/google//modules/svpc_service_project" + version = "14.4.0" + + for_each = { for group in local.groups : "${group.environment}-${group.team}" => group } + + random_project_id = true + + name = format("%s-%s-svc", each.value.environment, each.value.team) + project_id = format("%s-%s-svc", each.value.environment, each.value.team) + org_id = var.organization_id + billing_account = var.billing_account + folder_id = module.folders_environments_level.ids[each.value.environment] + + shared_vpc = module.host_project_environments[each.value.environment].project_id + shared_vpc_subnets = module.network[each.value.environment].subnets_self_links + + activate_apis = [ + "storage.googleapis.com", + "servicenetworking.googleapis.com", + "artifactregistry.googleapis.com", + "sqladmin.googleapis.com", + "compute.googleapis.com", + "run.googleapis.com", + "container.googleapis.com" + ] + + domain = data.google_organization.organization.domain + group_name = module.groups[each.value.team].name + group_role = "roles/viewer" + + depends_on = [ + module.folders_environments_level, + module.host_project_environments, + module.network, + module.groups + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/provider.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/provider.tf new file mode 100644 index 000000000..5b41ca19e --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/provider.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + google = { + source = "hashicorp/google" + version = "4.84.0" + } + } +} + +provider "google" { + project = var.billing_project + billing_project = var.billing_project + user_project_override = true +} + +provider "google-beta" { + project = var.billing_project + billing_project = var.billing_project + user_project_override = true +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/variables.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/variables.tf new file mode 100644 index 000000000..285439241 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/variables.tf @@ -0,0 +1,29 @@ +variable "billing_account" { + description = "The ID of the billing account to associate projects with" + type = string + default = "${{ GLOBAL_BILLING_ACCOUNT }}" +} + +variable "billing_project" { + description = "The ID of the billing project" + type = string + default = "${{ GLOBAL_BILLING_PROJECT }}" +} + +variable "organization_id" { + description = "The organization id for the associated resources" + type = string + default = "${{ GLOBAL_ORGANISATION_ID }}" +} + +variable "region_prefix" { + description = "The region prefix identifier to be used when provisioning resources" + type = string + default = "${{ GLOBAL_REGION_PREFIX }}" +} + +variable "domain" { + description = "The domain name that is used within the organisation" + type = string + default = "${{ GLOBAL_DOMAIN }}" +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/static/vpc.tf b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/vpc.tf new file mode 100644 index 000000000..c208a937b --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/static/vpc.tf @@ -0,0 +1,32 @@ +// https://registry.terraform.io/modules/terraform-google-modules/network/google/latest +module "network" { + source = "terraform-google-modules/network/google" + version = "8.0.0" + + for_each = local.environments + + project_id = module.host_project_environments[each.key].project_id + network_name = "vpc-${each.key}-shared" + routing_mode = "GLOBAL" + + subnets = [ + { + subnet_name = "subnet-${each.key}-1" + subnet_ip = cidrsubnet(each.value.cidr, 8, 0) + subnet_region = "${var.region_prefix}1" + subnet_private_access = true + }, + { + subnet_name = "subnet-${each.key}-2" + subnet_ip = cidrsubnet(each.value.cidr, 8, 1) + subnet_region = "${var.region_prefix}2" + subnet_private_access = true + }, + { + subnet_name = "subnet-${each.key}-3" + subnet_ip = cidrsubnet(each.value.cidr, 8, 2) + subnet_region = "${var.region_prefix}3" + subnet_private_access = true + }, + ] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/tests/.keep b/plugins/dotnet-provisioning-terraform-gcp-core/src/tests/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/types.ts b/plugins/dotnet-provisioning-terraform-gcp-core/src/types.ts new file mode 100644 index 000000000..8d214c401 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/types.ts @@ -0,0 +1,34 @@ +export enum BackendTypes { + Gcs = "gcs", + Local = "local", +} + +export interface EnvironmentConfig { + cidr: string; + teams: string[]; +} + +export interface Settings { + root_level: boolean; + directory_name: string; + global: { + organization_id: string; + billing_account: string; + billing_project: string; + domain: string; + region_prefix: string; + }; + environments: { + [environment: string]: EnvironmentConfig; + }; + backend: { + type: string; + local?: { + path: string; + }; + gcs?: { + bucket: string; + prefix: string; + }; + }; +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/src/utils.ts b/plugins/dotnet-provisioning-terraform-gcp-core/src/utils.ts new file mode 100644 index 000000000..a94bfe69f --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/src/utils.ts @@ -0,0 +1,21 @@ +import { PluginInstallation } from "@amplication/code-gen-types"; +import { name as PackageName } from "../package.json"; +import { Settings } from "./types"; +import defaultSettings from "../.amplicationrc.json"; + +export const getPluginSettings = ( + pluginInstallations: PluginInstallation[] +): Settings => { + const plugin = pluginInstallations.find( + (plugin) => plugin.npm === PackageName + ); + + const userSettings = plugin?.settings ?? {}; + + const settings: Settings = { + ...defaultSettings.settings, + ...userSettings, + }; + + return settings; +}; diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/tsconfig.json b/plugins/dotnet-provisioning-terraform-gcp-core/tsconfig.json new file mode 100644 index 000000000..6646f44f5 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/index.ts"], + "exclude": ["node_modules"] +} diff --git a/plugins/dotnet-provisioning-terraform-gcp-core/webpack.config.js b/plugins/dotnet-provisioning-terraform-gcp-core/webpack.config.js new file mode 100644 index 000000000..07eadf115 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-gcp-core/webpack.config.js @@ -0,0 +1,43 @@ +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); + +/** @type {import("webpack").Configuration} */ +module.exports = { + mode: "production", + target: "node", + entry: "./src/index.ts", + externals: ["@amplication/code-gen-utils", "@amplication/code-gen-types"], + plugins: [ + new webpack.SourceMapDevToolPlugin({ + filename: "[name].js.map", + }), + new CopyWebpackPlugin({ + patterns: [ + { from: "src/static", to: "static", noErrorOnMissing: true }, + { from: "src/templates", to: "templates", noErrorOnMissing: true }, + ], + }), + ], + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".ts", ".js", ".json"], + }, + optimization: { + minimize: false, + }, + output: { + filename: "index.js", + path: path.resolve(__dirname, "dist"), + libraryTarget: "commonjs2", + clean: true, + }, +};