diff --git a/.gitignore b/.gitignore index 72d8844259..8483ea7bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.js !jest.config.js +!jest.setup.js !test.js *.d.ts node_modules diff --git a/docs/working-with-cdk-for-terraform/remote-templates.md b/docs/working-with-cdk-for-terraform/remote-templates.md new file mode 100644 index 0000000000..5f36ecf0d3 --- /dev/null +++ b/docs/working-with-cdk-for-terraform/remote-templates.md @@ -0,0 +1,50 @@ +# Remote Templates + +Templates allow scaffolding a new CDK for Terraform project. When you setup a new project via `cdktf init` you can supply one of the [built-in templates](../packages/cdktf-cli/templates) (e.g. `typescript` or `python`) or use your own. This document describes how to create your own template to use with `cdktf init`. + +## Using Remote Templates +Currently the `cdktf` supports downloading and extracting a zip archive containing the files for the template. When extracting the archive, it searches for the `cdktf.json` file. If that file cannot be found in the root directory, it walks all directories until it finds the file. This allows creating an archive that contains e.g. a `README.md` in the root directory explaining things which itself won't turn up in the created project directory. However, most templates won't make use of it. + +If you're using a Github repository for your template, you can create URLs to your repo as follows +#### main branch +`https://github.com///archive/refs/heads/main.zip` +#### tag `v.0.0.1` +`https://github.com///archive/refs/tags/v0.0.1.zip` + +**Please Note:** Currently only urls to zip archives can be specified, hence only url based authentication mechanisms are supported. If you need support for private packages, please [file an issue](https://github.com/hashicorp/terraform-cdk/issues/new?labels=enhancement%2C+new&template=feature-request.md). + +## Creating Remote Templates +A template is a directory, containing at least a `cdktf.json` file, which is required for the `cdktf` CLI. +For scaffolding a new project the library [`sscaff`](https://github.com/awslabs/node-sscaff) is used. `sscaff` basically copies all files into the new directory while allowing for substitutions and hooks. + +### Using Substitutions +A template can make use of substitutions for filenames and file content. To specify your own variables, use Hooks (see below). +Besides the [built-in substitutions of](https://github.com/awslabs/node-sscaff#built-in-substitutions) `sccaff` the CDK for Terraform supplies some more variables that can be used in templates: + +#### User input +```typescript +Name: string; +Description: string; +OrganizationName: string; +WorkspaceName: string; +``` +These variables hold the input of the user and can for example be used in project files like `package.json` or similar. + +#### Versions +```typescript +cdktf_version: string; +constructs_version: string; +npm_cdktf: string; +npm_cdktf_cli: string; +pypi_cdktf: string; +mvn_cdktf: string; +nuget_cdktf: string; +``` +Those variables contain versions that are relative to the cdktf-cli that scaffolds the template. See the [built-in templates](../packages/cdktf-cli/templates) as reference of how you can use them. + +### Using `pre` and `post` Hooks +[Hooks](https://github.com/awslabs/node-sscaff#hooks) allow you to run additional logic before and after the generation of the output. + +### Debugging +To debug your templates you can add `console.log()` statements to your hook functions. Their output is displayed while initializing from a template. +If you set the environment flag `CDKTF_LOG_LEVEL` to `debug` you will see more debugging output (e.g. the temporary directory into which your zip archive is downloaded; which can help if you're unsure how your archive behaves). diff --git a/packages/cdktf-cli/bin/cmds/init.ts b/packages/cdktf-cli/bin/cmds/init.ts index 85c2e2d6e3..b0696e0d51 100644 --- a/packages/cdktf-cli/bin/cmds/init.ts +++ b/packages/cdktf-cli/bin/cmds/init.ts @@ -1,7 +1,9 @@ import yargs from 'yargs' import * as readlineSync from 'readline-sync'; +import extract from 'extract-zip'; import { TerraformLogin } from './helper/terraform-login' import * as fs from 'fs-extra'; +import * as os from 'os'; import * as path from 'path'; import { sscaff } from 'sscaff'; import * as terraformCloudClient from './helper/terraform-cloud-client'; @@ -9,6 +11,8 @@ import * as chalk from 'chalk'; import { terraformCheck } from './terraform-check'; import { displayVersionMessage } from './version-check' import { FUTURE_FLAGS } from 'cdktf/lib/features'; +import { downloadFile, HttpError } from '../../lib/util'; +import { logger } from '../../lib/logging'; const chalkColour = new chalk.Instance(); @@ -28,14 +32,13 @@ class Command implements yargs.CommandModule { public readonly describe = 'Create a new cdktf project from a template.'; public readonly builder = (args: yargs.Argv) => args .showHelpOnFail(true) - .option('template', { type: 'string', desc: 'The template name to be used to create a new project.' }) - .option('project-name', { type: 'string', desc: 'The name of the project.'}) - .option('project-description', { type: 'string', desc: 'The description of the project.'}) + .option('template', { type: 'string', desc: `The template to be used to create a new project. Either URL to zip file or one of the built-in templates: [${templates.map(t => `"${t}"`).join(', ')}]` }) + .option('project-name', { type: 'string', desc: 'The name of the project.' }) + .option('project-description', { type: 'string', desc: 'The description of the project.' }) .option('dist', { type: 'string', desc: 'Install dependencies from a "dist" directory (for development)' }) - .option('local', { type: 'boolean', desc: 'Use local state storage for generated Terraform.', default: false}) + .option('local', { type: 'boolean', desc: 'Use local state storage for generated Terraform.', default: false }) .option('cdktf-version', { type: 'string', desc: 'The cdktf version to use while creating a new project.', default: pkg.version }) .strict() - .choices('template', templates); public async handler(argv: any) { await terraformCheck() @@ -65,7 +68,7 @@ This means that your Terraform state file will be stored locally on disk in a fi } // Gather information about the template and the project - const templateInfo = await getTemplatePath(template); + const templateInfo = await getTemplate(template); const projectInfo: any = await gatherInfo(token, templateInfo.Name, argv.projectName, argv.projectDescription); @@ -88,6 +91,10 @@ This means that your Terraform state file will be stored locally on disk in a fi await sscaff(templateInfo.Path, '.', { ...deps, ...projectInfo, futureFlags }); + + if (templateInfo.cleanupTemporaryFiles) { + await templateInfo.cleanupTemporaryFiles() + } } } @@ -185,7 +192,7 @@ If you want to exit, press {magenta ^C}. console.log(chalkColour`\nWe are going to create a new {blueBright Terraform Cloud Workspace} for your project.\n`) - const workspaceName = readlineSync.question(chalkColour`{blueBright Terraform Cloud Workspace Name:} (default: '${templateName}') `, { defaultInput: templateName } ) + const workspaceName = readlineSync.question(chalkColour`{blueBright Terraform Cloud Workspace Name:} (default: '${templateName}') `, { defaultInput: templateName }) project.OrganizationName = organizationOptions[organizationSelect] project.WorkspaceName = workspaceName } @@ -193,23 +200,109 @@ If you want to exit, press {magenta ^C}. return project; } -async function getTemplatePath(templateName: string): Promise