diff --git a/.vscode/launch.json b/.vscode/launch.json index 0967ef424..18108322f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1 +1,39 @@ -{} +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug cndi CLI", + "type": "node", + "request": "launch", + "runtimeExecutable": "deno", + "program": "${workspaceFolder}/main.ts", + "cwd": "${workspaceFolder}/../${input:workDir}", // prompt here + "args": ["${input:cndiVerb}"], + "runtimeArgs": [ + "run", + "--unstable", + "--watch", // restart on changes + "--inspect" + ], + "console": "integratedTerminal", + "restart": true, // auto-restart the debug session + "internalConsoleOptions": "neverOpen", + "stopOnEntry": true + } + ], + "inputs": [ + { + "id": "cndiVerb", + "type": "pickString", + "description": "Which cndi subcommand?", + "options": ["ow", "run", "create", "destroy", "init"] + }, + { + "id": "workDir", + "type": "promptString", + "description": "Choose working directory relative to theparent of the workspace root", + "default": "cndi-example" + } + ] +} diff --git a/cndi_config.yaml b/cndi_config.yaml index 5bad3363e..4de9d1815 100644 --- a/cndi_config.yaml +++ b/cndi_config.yaml @@ -13,7 +13,7 @@ infrastructure: hostname: observability.polyseam.io cert_manager: email: matt.johnston@polyseam.io - nodes: + nodes: # or 'auto' - name: airflow-alpha instance_type: t3.medium # or 'n1-standard-2', 'Standard_D2_v2' disk_size: 100 diff --git a/src/cndi_config/types.ts b/src/cndi_config/types.ts index 4e0178026..8501411ba 100644 --- a/src/cndi_config/types.ts +++ b/src/cndi_config/types.ts @@ -44,7 +44,7 @@ export interface NormalizedCNDIConfig extends CNDIConfigSpec { export type CNDIInfrastructureSpec = { terraform?: TFBlocks; cndi: { - nodes: Array; // only omitted if distribution is "clusterless" + nodes: Array | "auto"; // only omitted if distribution is "clusterless" network?: CNDINetworkConfig; functions?: CNDIFnsConfig; ingress?: CNDIIngressConfig; diff --git a/src/outputs/cluster_manifests/core-applications/public-nginx.application.yaml.ts b/src/outputs/cluster_manifests/core-applications/public-nginx.application.yaml.ts index 23c8158da..d0e6524f3 100644 --- a/src/outputs/cluster_manifests/core-applications/public-nginx.application.yaml.ts +++ b/src/outputs/cluster_manifests/core-applications/public-nginx.application.yaml.ts @@ -38,14 +38,18 @@ const getBaseValues = (cndi_config: NormalizedCNDIConfig) => { const eksValues = (cndi_config: NormalizedCNDIConfig) => { const { project_name } = cndi_config; + + const annotations: Record = { + "service.beta.kubernetes.io/aws-load-balancer-type": "nlb", + "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags": + `CNDIProject=${project_name}`, + "service.beta.kubernetes.io/aws-load-balancer-scheme": "internet-facing", + }; + return deepMerge(getBaseValues(cndi_config), { controller: { service: { - annotations: { - "service.beta.kubernetes.io/aws-load-balancer-type": "nlb", - "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags": - `CNDIProject=${project_name}`, - }, + annotations, }, }, }); diff --git a/src/outputs/github/cndi-run-workflow.ts b/src/outputs/github/cndi-run-workflow.ts index 76a503707..a8fa79ba4 100644 --- a/src/outputs/github/cndi-run-workflow.ts +++ b/src/outputs/github/cndi-run-workflow.ts @@ -67,7 +67,7 @@ const getSourceRefSteps = (sourceRef: string): Array => [{ }, }, { name: "checkout cndi repo", - uses: "actions/checkout@v3", + uses: "actions/checkout@v4", with: { repository: "polyseam/cndi", "fetch-depth": 0, @@ -78,15 +78,17 @@ const getSourceRefSteps = (sourceRef: string): Array => [{ uses: "denoland/setup-deno@v2", }, { name: "build cndi", - run: "deno task build-linux", + run: "deno task build-linux-amd64", }, { name: "persist cndi", - run: "mkdir -p $HOME/.cndi/bin && mv ./dist/linux/in/* $HOME/.cndi/bin/", + run: + "mkdir -p $HOME/.cndi/bin && mv ./dist/linux-amd64/in/* $HOME/.cndi/bin/", }, { - name: "checkout repo", - uses: "actions/checkout@v3", + name: "checkout cluster repo", + uses: "actions/checkout@v4", with: { "fetch-depth": 0, + repository: "${{ github.repository }}", }, }]; diff --git a/src/outputs/terraform/aws/eks/classic/network-mode-create/module/cndi_aws_eks_module.tf.json.ts b/src/outputs/terraform/aws/eks/classic/network-mode-create/module/cndi_aws_eks_module.tf.json.ts index 82f9eafda..8ea9b218f 100644 --- a/src/outputs/terraform/aws/eks/classic/network-mode-create/module/cndi_aws_eks_module.tf.json.ts +++ b/src/outputs/terraform/aws/eks/classic/network-mode-create/module/cndi_aws_eks_module.tf.json.ts @@ -1,34 +1,70 @@ import { getPrettyJSONString } from "src/utils.ts"; import { DEFAULT_K8S_VERSION } from "versions"; import { NormalizedCNDIConfig } from "src/cndi_config/types.ts"; + const MODULE_SOURCE = "git::https://github.com/terraform-aws-modules/terraform-aws-eks.git?ref=a713f6f464eb579a39918f60f130a5fbb77a6b30"; -export default function (_cndi_config: NormalizedCNDIConfig) { +type AWSAutoModeNodePool = "system" | "general-purpose" | string; + +type AWSAutoModeComputeConfig = { + enabled: boolean; + node_pools: AWSAutoModeNodePool[]; + node_role_arn: string; +}; + +type CNDIAWSEKSModule = { + cluster_addons?: Record< + string, + { mostRecent: boolean; serviceAccountRoleArn: string } + >; + cluster_name: "${local.cndi_project_name}"; + cluster_version: string; + cluster_endpoint_public_access: boolean; + enable_cluster_creator_admin_permissions: boolean; + vpc_id: string; + subnet_ids: string; + source: string; + cluster_compute_config?: AWSAutoModeComputeConfig; // if defined enroll in automode +}; + +export default function (cndi_config: NormalizedCNDIConfig) { + const autoMode = cndi_config?.infrastructure?.cndi?.nodes === "auto"; + + const cndi_aws_eks_module: CNDIAWSEKSModule = { + cluster_name: "${local.cndi_project_name}", + cluster_version: DEFAULT_K8S_VERSION, + cluster_endpoint_public_access: true, // TODO: probably bad + enable_cluster_creator_admin_permissions: true, + vpc_id: "${module.cndi_aws_vpc_module.vpc_id}", + subnet_ids: "${module.cndi_aws_vpc_module.private_subnets}", + source: MODULE_SOURCE, + }; + + if (autoMode) { + cndi_aws_eks_module["cluster_compute_config"] = { + enabled: true, + node_pools: ["general-purpose", "system"], + node_role_arn: "${aws_iam_role.cndi_aws_iam_role.arn}", // extrapolated from non-auto + }; + } else { + cndi_aws_eks_module["cluster_addons"] = { + "aws-ebs-csi-driver": { + mostRecent: true, + serviceAccountRoleArn: + "${module.cndi_aws_iam_assumable_role_ebs_with_oidc.iam_role_arn}", + }, + "aws-efs-csi-driver": { + mostRecent: true, + serviceAccountRoleArn: + "${module.cndi_aws_iam_assumable_role_efs_with_oidc.iam_role_arn}", + }, + }; + } + return getPrettyJSONString({ module: { - cndi_aws_eks_module: { - cluster_addons: { - "aws-ebs-csi-driver": { - mostRecent: true, - serviceAccountRoleArn: - "${module.cndi_aws_iam_assumable_role_ebs_with_oidc.iam_role_arn}", - }, - "aws-efs-csi-driver": { - mostRecent: true, - serviceAccountRoleArn: - "${module.cndi_aws_iam_assumable_role_efs_with_oidc.iam_role_arn}", - }, - }, - cluster_name: "${local.cndi_project_name}", - cluster_version: DEFAULT_K8S_VERSION, - cluster_endpoint_public_access: true, // TODO: probably bad - enable_cluster_creator_admin_permissions: true, - vpc_id: "${module.cndi_aws_vpc_module.vpc_id}", - subnet_ids: - "${concat(module.cndi_aws_vpc_module.private_subnets, module.cndi_aws_vpc_module.public_subnets)}", - source: MODULE_SOURCE, - }, + cndi_aws_eks_module, }, }); } diff --git a/src/outputs/terraform/aws/eks/classic/network-mode-create/resource/cndi_aws_eks_node_group.tf.json.ts b/src/outputs/terraform/aws/eks/classic/network-mode-create/resource/cndi_aws_eks_node_group.tf.json.ts index cf73890ae..aca5914fc 100644 --- a/src/outputs/terraform/aws/eks/classic/network-mode-create/resource/cndi_aws_eks_node_group.tf.json.ts +++ b/src/outputs/terraform/aws/eks/classic/network-mode-create/resource/cndi_aws_eks_node_group.tf.json.ts @@ -36,7 +36,7 @@ interface AwsEksNodeGroup { export default function (cndi_config: NormalizedCNDIConfig) { const aws_eks_node_group: Record = {}; - if (cndi_config.infrastructure.cndi.nodes as unknown !== "automatic") { + if (cndi_config.infrastructure.cndi.nodes !== "auto") { // original non-automatic node group let i = 0; for (const nodeSpec of cndi_config.infrastructure.cndi.nodes) { @@ -98,11 +98,13 @@ export default function (cndi_config: NormalizedCNDIConfig) { }; i++; } + return getPrettyJSONString({ + resource: { + aws_eks_node_group, + }, + }); } - return getPrettyJSONString({ - resource: { - aws_eks_node_group, - }, - }); + // no node pool resource in auto mode + return null; } diff --git a/src/outputs/terraform/aws/eks/classic/network-mode-insert/resource/cndi_aws_eks_node_group.tf.json.ts b/src/outputs/terraform/aws/eks/classic/network-mode-insert/resource/cndi_aws_eks_node_group.tf.json.ts index 759583903..e8932dedf 100644 --- a/src/outputs/terraform/aws/eks/classic/network-mode-insert/resource/cndi_aws_eks_node_group.tf.json.ts +++ b/src/outputs/terraform/aws/eks/classic/network-mode-insert/resource/cndi_aws_eks_node_group.tf.json.ts @@ -36,7 +36,7 @@ interface AwsEksNodeGroup { export default function (cndi_config: NormalizedCNDIConfig) { const aws_eks_node_group: Record = {}; - if (cndi_config.infrastructure.cndi.nodes as unknown !== "automatic") { + if (cndi_config.infrastructure.cndi.nodes !== "auto") { // original non-automatic node group let i = 0; for (const nodeSpec of cndi_config.infrastructure.cndi.nodes) { diff --git a/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_aws_launch_template.tf.json.ts b/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_aws_launch_template.tf.json.ts index e284f771f..655d79a1d 100644 --- a/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_aws_launch_template.tf.json.ts +++ b/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_aws_launch_template.tf.json.ts @@ -57,8 +57,9 @@ export default function (cndi_config: NormalizedCNDIConfig) { }; i++; } - } else if (cndi_config.infrastructure.cndi.nodes === "automatic") { + } else if (cndi_config.infrastructure.cndi.nodes === "auto") { // automatic node group + return null; } return getPrettyJSONString({ diff --git a/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_kubernetes_storage_class.tf.json.ts b/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_kubernetes_storage_class.tf.json.ts index eb69e2370..cd76b0288 100644 --- a/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_kubernetes_storage_class.tf.json.ts +++ b/src/outputs/terraform/aws/eks/classic/shared/resource/cndi_kubernetes_storage_class.tf.json.ts @@ -2,7 +2,11 @@ import { NormalizedCNDIConfig } from "src/cndi_config/types.ts"; import { getPrettyJSONString } from "src/utils.ts"; import type { KubernetesStorageClass } from "src/outputs/terraform/shared/resource/KubernetesStorageClass.ts"; -export default function (_cndi_config: NormalizedCNDIConfig) { +export default function (cndi_config: NormalizedCNDIConfig) { + const ebsStorageProvisioner = cndi_config.infrastructure.cndi.nodes === "auto" + ? "ebs.csi.eks.amazonaws.com" + : "ebs.csi.aws.com"; + const kubernetes_storage_class: Record = { cndi_kubernetes_storage_class_rwo: { allow_volume_expansion: true, @@ -17,7 +21,7 @@ export default function (_cndi_config: NormalizedCNDIConfig) { type: "gp3", }, reclaim_policy: "Delete", - storage_provisioner: "ebs.csi.aws.com", + storage_provisioner: ebsStorageProvisioner, volume_binding_mode: "WaitForFirstConsumer", }, cndi_kubernetes_storage_class_rwm: { diff --git a/src/outputs/terraform/aws/microk8s/resource/cndi_aws_instance.tf.json.ts b/src/outputs/terraform/aws/microk8s/resource/cndi_aws_instance.tf.json.ts index 427ccb65e..d5b06e8c6 100644 --- a/src/outputs/terraform/aws/microk8s/resource/cndi_aws_instance.tf.json.ts +++ b/src/outputs/terraform/aws/microk8s/resource/cndi_aws_instance.tf.json.ts @@ -31,7 +31,11 @@ export default function (cndi_config: NormalizedCNDIConfig) { const _project_name = "${local.cndi_project_name}"; const nodes = cndi_config.infrastructure.cndi.nodes; const instances: Record = {}; - + if (nodes === "auto") { + throw new Error( + "'auto' nodes are not supported in microk8s clusters", + ); + } nodes.forEach((node, index) => { const count = node?.count || 1; const role = index === 0 diff --git a/src/outputs/terraform/aws/microk8s/resource/cndi_aws_lb_listener.tf.json.ts b/src/outputs/terraform/aws/microk8s/resource/cndi_aws_lb_listener.tf.json.ts index 378677e6f..7bf257a8a 100644 --- a/src/outputs/terraform/aws/microk8s/resource/cndi_aws_lb_listener.tf.json.ts +++ b/src/outputs/terraform/aws/microk8s/resource/cndi_aws_lb_listener.tf.json.ts @@ -23,6 +23,12 @@ type AWSLBTargetGroupAttachment = { export default function (cndi_config: NormalizedCNDIConfig) { const project_name = "${local.cndi_project_name}"; + const nodes = cndi_config.infrastructure.cndi.nodes; + if (nodes === "auto") { + throw new Error( + "'auto' nodes are not supported in microk8s clusters", + ); + } const open_ports = resolveCNDIPorts(cndi_config); const listeners: Record = {}; const attachments: Record = {}; @@ -45,7 +51,7 @@ export default function (cndi_config: NormalizedCNDIConfig) { }; // Add target group attachments for each node - cndi_config.infrastructure.cndi.nodes.forEach((node) => { + nodes.forEach((node) => { const count = node?.count || 1; for (let i = 0; i < count; i++) { const nodeName = `${node.name}-${i}`; diff --git a/src/outputs/terraform/azure/aks/classic/network-mode-create/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts b/src/outputs/terraform/azure/aks/classic/network-mode-create/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts index 1a07196b1..aaf99bb58 100644 --- a/src/outputs/terraform/azure/aks/classic/network-mode-create/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts +++ b/src/outputs/terraform/azure/aks/classic/network-mode-create/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts @@ -28,6 +28,11 @@ export interface AzurermKubernetesClusterNodePool { export function getNodePools( cndi_config: NormalizedCNDIConfig, ): AzurermKubernetesClusterNodePool[] { + if (cndi_config.infrastructure.cndi.nodes === "auto") { + throw new Error( + 'Node Pools should not be generated for "auto" node clusters', + ); + } const azurerm_kubernetes_cluster_node_pool = cndi_config.infrastructure.cndi .nodes.map( (nodeSpec: CNDINodeSpec, i: number) => { @@ -84,6 +89,8 @@ export function getNodePools( } export default function (cndi_config: NormalizedCNDIConfig) { + if (cndi_config.infrastructure.cndi.nodes === "auto") return null; + const cndi_azurerm_kubernetes_cluster_node_pool = getNodePools( cndi_config, ); diff --git a/src/outputs/terraform/azure/aks/classic/network-mode-insert/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts b/src/outputs/terraform/azure/aks/classic/network-mode-insert/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts index 7aa0355ff..d692c811e 100644 --- a/src/outputs/terraform/azure/aks/classic/network-mode-insert/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts +++ b/src/outputs/terraform/azure/aks/classic/network-mode-insert/resource/cndi_azurerm_kubernetes_cluster_node_pool.tf.json.ts @@ -27,6 +27,11 @@ export interface AzurermKubernetesClusterNodePool { export function getNodePools( cndi_config: NormalizedCNDIConfig, ): AzurermKubernetesClusterNodePool[] { + if (cndi_config.infrastructure.cndi.nodes === "auto") { + throw new Error( + 'Node Pools should not be generated for "auto" node clusters', + ); + } const azurerm_kubernetes_cluster_node_pool = cndi_config.infrastructure.cndi .nodes.map( (nodeSpec: CNDINodeSpec, i) => { @@ -83,6 +88,7 @@ export function getNodePools( } export default function (cndi_config: NormalizedCNDIConfig) { + if (cndi_config.infrastructure.cndi.nodes === "auto") return null; const cndi_azurerm_kubernetes_cluster_node_pool = getNodePools( cndi_config, ); diff --git a/src/outputs/terraform/azure/microk8s/resource/cndi_azurerm_linux_virtual_machine.tf.json.ts b/src/outputs/terraform/azure/microk8s/resource/cndi_azurerm_linux_virtual_machine.tf.json.ts index 613a1f31a..1229f75cd 100644 --- a/src/outputs/terraform/azure/microk8s/resource/cndi_azurerm_linux_virtual_machine.tf.json.ts +++ b/src/outputs/terraform/azure/microk8s/resource/cndi_azurerm_linux_virtual_machine.tf.json.ts @@ -2,6 +2,14 @@ import { NormalizedCNDIConfig } from "src/cndi_config/types.ts"; import { getPrettyJSONString } from "src/utils.ts"; export default function (cndi_config: NormalizedCNDIConfig) { + if (cndi_config.infrastructure.cndi.nodes === "auto") { + console.error( + "src/outputs/terraform/azure/microk8s/resource/cndi_azurerm_linux_virtual_machine.tf.json.ts", + "Nodes cannot be 'auto' in Azure MicroK8s mode.", + ); + throw new Error("Nodes cannot be 'auto' in Azure MicroK8s mode."); + } + const nodeConfig = cndi_config.infrastructure?.cndi?.nodes?.[0] || {}; const vmSize = nodeConfig.machine_type || nodeConfig.instance_type || "Standard_D2s_v3"; diff --git a/src/outputs/terraform/gcp/gke/classic/shared/cndi_google_container_node_pool.tf.json.ts b/src/outputs/terraform/gcp/gke/classic/shared/cndi_google_container_node_pool.tf.json.ts index c736ba83e..99b6e0a08 100644 --- a/src/outputs/terraform/gcp/gke/classic/shared/cndi_google_container_node_pool.tf.json.ts +++ b/src/outputs/terraform/gcp/gke/classic/shared/cndi_google_container_node_pool.tf.json.ts @@ -44,7 +44,7 @@ export default function (cndi_config: NormalizedCNDIConfig) { const google_container_node_pool: Record = {}; - if (cndi_config.infrastructure.cndi.nodes as unknown !== "automatic") { + if (cndi_config.infrastructure.cndi.nodes !== "auto") { // original non-automatic node group let i = 0; diff --git a/src/outputs/terraform/shared/output.tf.json.ts b/src/outputs/terraform/shared/output.tf.json.ts index 62dea2296..bbfe4fb0a 100644 --- a/src/outputs/terraform/shared/output.tf.json.ts +++ b/src/outputs/terraform/shared/output.tf.json.ts @@ -2,7 +2,7 @@ import { NormalizedCNDIConfig } from "src/cndi_config/types.ts"; import { getPrettyJSONString } from "src/utils.ts"; import { ccolors } from "deps"; -const _label = ccolors.faded("\nsrc/outputs/terraform/output.tf.json.ts:\n"); +const label = ccolors.faded("\nsrc/outputs/terraform/output.tf.json.ts:\n"); type Outputs = Record; @@ -53,7 +53,15 @@ function getOutputsForProvider( }, }; case "dev": { - const [node] = cndi_config.infrastructure.cndi.nodes; + const nodes = cndi_config.infrastructure.cndi.nodes; + if (nodes === "auto") { + console.error( + label, + ccolors.error("Nodes cannot be 'auto' in dev mode."), + ); + throw new Error("Nodes cannot be 'auto' in dev mode."); + } + const [node] = nodes; return { cndi_dev_tutorial: { value: `Accessing ArgoCD UI