From 6214ccd15ce528e7b7279499469bfaf75fa05fe2 Mon Sep 17 00:00:00 2001 From: Gabriele Fedi Date: Mon, 12 Jan 2026 10:40:17 +0100 Subject: [PATCH 01/17] feat: new extension scaffolding dagger command Signed-off-by: Gabriele Fedi --- dagger/maintenance/main.go | 90 +++++++++++++++++++++++++++++++++++++ templates/Dockerfile.tmpl | 42 +++++++++++++++++ templates/README.md.tmpl | 72 +++++++++++++++++++++++++++++ templates/metadata.hcl.tmpl | 21 +++++++++ 4 files changed, 225 insertions(+) create mode 100644 templates/Dockerfile.tmpl create mode 100644 templates/README.md.tmpl create mode 100644 templates/metadata.hcl.tmpl diff --git a/dagger/maintenance/main.go b/dagger/maintenance/main.go index 6d3521d..ddb9133 100644 --- a/dagger/maintenance/main.go +++ b/dagger/maintenance/main.go @@ -4,15 +4,20 @@ package main import ( + "bytes" "context" "encoding/json" "fmt" "maps" "path" "slices" + "strings" + "text/template" "time" "go.yaml.in/yaml/v3" + "golang.org/x/text/cases" + "golang.org/x/text/language" "dagger/maintenance/internal/dagger" ) @@ -191,6 +196,91 @@ func (m *Maintenance) GenerateTestingValues( return result.File("values.yaml"), nil } +// Scaffolds a new Postgres extension directory structure +func (m *Maintenance) Create( + ctx context.Context, + // The source directory containing the extension template files + // +defaultPath="/templates" + templatesDir *dagger.Directory, + // The name of the extension + name string, + // The Postgres major versions the extension is supported for + // +default=["18"] + versions []string, + // The Debian distributions the extension is supported for + // +default=["trixie","bookworm"] + distros []string, + // The Debian package name for the extension. If the package name contains + // the postgres version, it can be templated using the "%version%" placeholder. + // (default "postgresql-%version%-") + // +optional + packageName string, +) (*dagger.Directory, error) { + extDir := dag.Directory() + + type Extension struct { + Name string + Versions []string + Distros []string + Package string + DefaultVersion int + DefaultDistro string + } + + if packageName == "" { + packageName = "postgresql-%version%-" + name + } + + extension := Extension{ + Name: name, + Versions: versions, + Distros: distros, + Package: packageName, + DefaultVersion: DefaultPgMajor, + DefaultDistro: DefaultDistribution, + } + + toTitle := func(s string) string { + return cases.Title(language.English).String(s) + } + + funcMap := template.FuncMap{ + "replaceAll": strings.ReplaceAll, + "toTitle": toTitle, + } + + executeTemplate := func(fileName string) error { + tmplFile := templatesDir.File(fileName + ".tmpl") + tmplContent, err := tmplFile.Contents(ctx) + if err != nil { + return err + } + tmpl, err := template.New(fileName).Funcs(funcMap).Parse(tmplContent) + if err != nil { + return err + } + buf := &bytes.Buffer{} + if err := tmpl.Execute(buf, extension); err != nil { + return err + } + extDir = extDir.WithNewFile(fileName, buf.String()) + return nil + } + + var templateFiles = []string{ + "metadata.hcl", + "Dockerfile", + "README.md", + } + for _, fileName := range templateFiles { + if err := executeTemplate(fileName); err != nil { + return nil, err + } + } + + return extDir, nil +} + // Tests the specified target using Chainsaw func (m *Maintenance) Test( ctx context.Context, diff --git a/templates/Dockerfile.tmpl b/templates/Dockerfile.tmpl new file mode 100644 index 0000000..6ee4bb4 --- /dev/null +++ b/templates/Dockerfile.tmpl @@ -0,0 +1,42 @@ +{{ $packageName := replaceAll .Package "%version%" "${PG_MAJOR}" }} +ARG BASE=ghcr.io/cloudnative-pg/postgresql:{{ .DefaultVersion }}-minimal-{{ .DefaultDistro }} +FROM $BASE AS builder + +ARG PG_MAJOR +ARG EXT_VERSION + +USER 0 + +# Install extension + +# RUN apt-get update && \ +# apt-get install -y --no-install-recommends "{{ $packageName }}=${EXT_VERSION}" + +FROM scratch +ARG PG_MAJOR + +# Licenses +# Include license files is essential for legal compliance and transparency. +# It ensures proper attribution to original authors, fulfills open source license +# requirements, and enables automated compliance scanning tools to verify licensing. + +# COPY --from=builder /usr/share/doc/{{ $packageName }}/copyright /licenses/{{ $packageName }}/ + +# Libraries +# Include the extension's shared objects and related dependencies under the /lib directory. +# CNPG will load these libraries at runtime by configuring the dynamic linker path (LD_LIBRARY_PATH) and +# the PostgreSQL GUC "dynamic_library_path" based on defaults and Cluster configuration. +# For more details, see: https://cloudnative-pg.io/docs/1.28/imagevolume_extensions#how-it-works + +# COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/{{ .Name }}* /lib/ +# COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/bitcode/ /lib/bitcode/ + +# Share +# Include the extension's SQL scripts and control files under the /share/extension directory. +# CNPG will configure PostgreSQL to include this path via the "extension_control_path" GUC to +# seamlessly find and manage the current extension. +# For more details, see: https://cloudnative-pg.io/docs/1.28/imagevolume_extensions#how-it-works + +# COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/extension/{{ .Name }}* /share/extension/ + +USER 65532:65532 diff --git a/templates/README.md.tmpl b/templates/README.md.tmpl new file mode 100644 index 0000000..f3effde --- /dev/null +++ b/templates/README.md.tmpl @@ -0,0 +1,72 @@ +# {{ toTitle .Name }} + + + +{{ .Name }} is a PostgreSQL extension that provides support for +vector data types and operations, enabling efficient storage and retrieval of +high-dimensional vectors. It is particularly useful for applications involving +machine learning, recommendation systems, and similarity search. + +## Usage + + + +### 1. Add the {{ .Name }} extension image to your Cluster + +Define the `{{ .Name }}` extension under the `postgresql.extensions` section of +your `Cluster` resource. For example: + +```yaml +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: cluster-{{ .Name }} +spec: + imageName: ghcr.io/cloudnative-pg/postgresql:{{ .DefaultVersion }}-minimal-{{ .DefaultDistro }} + instances: 1 + + storage: + size: 1Gi + + postgresql: + extensions: + - name: {{ .Name }} + image: + reference: ghcr.io/cloudnative-pg/{{ .Name }}:1.0-{{ .DefaultVersion }}-{{ .DefaultDistro }} +``` + +### 2. Enable the extension in a database + +You can install `{{ .Name }}` in a specific database by creating or updating a +`Database` resource. For example, to enable it in the `app` database: + +```yaml +apiVersion: postgresql.cnpg.io/v1 +kind: Database +metadata: + name: cluster-{{ .Name }}-app +spec: + name: app + owner: app + cluster: + name: cluster-{{ .Name }} + extensions: + - name: {{ .Name }} + version: '1.0' +``` + +### 3. Verify installation + +Once the database is ready, connect to it with `psql` and run: + +```sql +\dx +``` + +You should see `{{ .Name }}` listed among the installed extensions. diff --git a/templates/metadata.hcl.tmpl b/templates/metadata.hcl.tmpl new file mode 100644 index 0000000..b14f981 --- /dev/null +++ b/templates/metadata.hcl.tmpl @@ -0,0 +1,21 @@ +metadata = { + name = "{{ .Name }}" + sql_name = "{{ .Name }}" + image_name = "{{ .Name }}" + shared_preload_libraries = [] + extension_control_path = [] + dynamic_library_path = [] + ld_library_path = [] + auto_update_os_libs = false + + versions = { + {{- range $distro := .Distros}} + {{ $distro }} = { + {{- range $version := $.Versions}} + // renovate: suite={{ $distro }}-pgdg depName={{ replaceAll $.Package "%version%" $version }} + "{{ $version }}" = "" + {{- end}} + } + {{- end}} + } +} From 74170f62b8bbee8faf8de05bf844315f58186855 Mon Sep 17 00:00:00 2001 From: Gabriele Fedi Date: Thu, 15 Jan 2026 17:09:52 +0100 Subject: [PATCH 02/17] feat: add task entry Signed-off-by: Gabriele Fedi --- Taskfile.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index f90636d..8c8afa6 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -129,6 +129,14 @@ tasks: TARGET: "{{.ITEM}}" task: update-os-libs + create: + desc: Scaffold a new extension directory + cmds: + - dagger call -sm ./dagger/maintenance/ create --name {{.NAME}} export --path ./{{.NAME}} + requires: + vars: + - name: NAME + generate-values: desc: Generate Chainsaw testing values for the specified target deps: From e43ecf32ffa82b7d9a608129e56c04e25972cf6b Mon Sep 17 00:00:00 2001 From: Gabriele Fedi Date: Thu, 15 Jan 2026 17:09:52 +0100 Subject: [PATCH 03/17] feat: add task entry Signed-off-by: Gabriele Fedi --- Taskfile.yml | 2 +- templates/README.md.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 8c8afa6..549d786 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -129,7 +129,7 @@ tasks: TARGET: "{{.ITEM}}" task: update-os-libs - create: + create-extension: desc: Scaffold a new extension directory cmds: - dagger call -sm ./dagger/maintenance/ create --name {{.NAME}} export --path ./{{.NAME}} diff --git a/templates/README.md.tmpl b/templates/README.md.tmpl index f3effde..610fde1 100644 --- a/templates/README.md.tmpl +++ b/templates/README.md.tmpl @@ -5,7 +5,7 @@ Introduction: give a brief introduction of the extension and what it is useful f Add a reference to the official documentation if available. --> -{{ .Name }} is a PostgreSQL extension that provides support for +The {{ .Name }} PostgreSQL extension provides support for vector data types and operations, enabling efficient storage and retrieval of high-dimensional vectors. It is particularly useful for applications involving machine learning, recommendation systems, and similarity search. From 484a9b1be61c7e99241ebdab3a33a3d8633c6f2d Mon Sep 17 00:00:00 2001 From: Gabriele Fedi Date: Thu, 15 Jan 2026 17:50:35 +0100 Subject: [PATCH 04/17] docs: create new extension Signed-off-by: Gabriele Fedi --- BUILD.md | 44 +++++++++++++++++++++++++++++++++++++++ templates/Dockerfile.tmpl | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 2b3d7fd..32afa3a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -185,3 +185,47 @@ To clean up all the resources created by the `e2e:setup-env` task, run: ```bash task e2e:cleanup ``` + +--- + +## Create a new extension + +To create a new extension project, you can use the `create-extension` task: + +```bash +task create-extension NAME= +``` + +The command will create a new directory named `` using default configurations. +For a more advanced and customized setup, you can use the dagger command directly: + +```bash +dagger call -sm ./dagger/maintenance/ create --help + +Scaffolds a new Postgres extension directory structure + +USAGE + dagger call create [arguments] + +FUNCTIONS: + +[...] + +ARGUMENTS + --name string The name of the extension [required] + --distros strings The Debian distributions the extension is supported for (default [trixie,bookworm]) + --package-name string The Debian package name for the extension. If the package name contains + the postgres version, it can be templated using the "%version%" placeholder. + (default "postgresql-%version%-") + --templates-dir Directory The source directory containing the extension template files + --versions strings The Postgres major versions the extension is supported for (default [18]) +``` + +This command creates a new folder with the following files: + +- `Dockerfile`: the Dockerfile to build the extension container image. +- `metadata.hcl`: the file providing specific metadata information used to build and test the extension. +- `README.md`: a template README file to get started with documenting the extension usage. + +Those files are scaffolded with a generic template and are meant to be customized according to the +specific extension requirements. diff --git a/templates/Dockerfile.tmpl b/templates/Dockerfile.tmpl index 6ee4bb4..fb36854 100644 --- a/templates/Dockerfile.tmpl +++ b/templates/Dockerfile.tmpl @@ -16,7 +16,7 @@ FROM scratch ARG PG_MAJOR # Licenses -# Include license files is essential for legal compliance and transparency. +# Including license files is essential for legal compliance and transparency. # It ensures proper attribution to original authors, fulfills open source license # requirements, and enables automated compliance scanning tools to verify licensing. From 975ed4ffe6cf24bfd0837cf325ff61e3e218684d Mon Sep 17 00:00:00 2001 From: Gabriele Fedi Date: Mon, 19 Jan 2026 17:01:20 +0100 Subject: [PATCH 05/17] chore: check if extension already exists Signed-off-by: Gabriele Fedi --- Taskfile.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index 549d786..7180b9b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -133,6 +133,9 @@ tasks: desc: Scaffold a new extension directory cmds: - dagger call -sm ./dagger/maintenance/ create --name {{.NAME}} export --path ./{{.NAME}} + preconditions: + - sh: test ! -d {{ .NAME }} + msg: 'The provided extension already exists. Name: {{.NAME}}' requires: vars: - name: NAME From 2e011f8d99a4c1d51df8755214ddf625d0945f58 Mon Sep 17 00:00:00 2001 From: Gabriele Bartolini Date: Thu, 22 Jan 2026 17:08:40 +1100 Subject: [PATCH 06/17] docs: reordered the section in the BUILD file Signed-off-by: Gabriele Bartolini --- BUILD.md | 84 +++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/BUILD.md b/BUILD.md index 32afa3a..e6db1ef 100644 --- a/BUILD.md +++ b/BUILD.md @@ -20,6 +20,46 @@ which primarily include: --- +## Scaffolding a New Extension + +To create a new extension project structure, use the `create-extension` task: + +```bash +task create-extension NAME= +``` + +This command generates a new directory named after your extension with the +following scaffolded files: + +- `Dockerfile`: The base file to build the extension container image. +- `metadata.hcl`: Provides specific metadata information used to build and test + the extension. +- `README.md`: A template to help you document the extension's usage. + +> [!NOTE] +> These files are generated from generic templates and should be customized to +> meet your extension's specific requirements. + +### Advanced Scaffolding + +For more complex setups, you can use the `dagger` command directly to customize +distributions or package names: + +```bash +dagger call -sm ./dagger/maintenance/ create --name="" [ARGUMENTS] +``` + +**Common Arguments:** + +| Argument | Description | Default | +| --- | --- | --- | +| `--distros` | Debian distributions the extension supports. | `[trixie, bookworm]` | +| `--package-name` | The Debian package name (uses `%version%` placeholder). | `postgresql-%version%-` | +| `--versions` | Supported Postgres major versions. | `[18]` | +| `--templates-dir` | Source directory containing custom template files. | Internal template dir | + +--- + ## Usage and Targets ### 1. Check prerequisites only @@ -185,47 +225,3 @@ To clean up all the resources created by the `e2e:setup-env` task, run: ```bash task e2e:cleanup ``` - ---- - -## Create a new extension - -To create a new extension project, you can use the `create-extension` task: - -```bash -task create-extension NAME= -``` - -The command will create a new directory named `` using default configurations. -For a more advanced and customized setup, you can use the dagger command directly: - -```bash -dagger call -sm ./dagger/maintenance/ create --help - -Scaffolds a new Postgres extension directory structure - -USAGE - dagger call create [arguments] - -FUNCTIONS: - -[...] - -ARGUMENTS - --name string The name of the extension [required] - --distros strings The Debian distributions the extension is supported for (default [trixie,bookworm]) - --package-name string The Debian package name for the extension. If the package name contains - the postgres version, it can be templated using the "%version%" placeholder. - (default "postgresql-%version%-") - --templates-dir Directory The source directory containing the extension template files - --versions strings The Postgres major versions the extension is supported for (default [18]) -``` - -This command creates a new folder with the following files: - -- `Dockerfile`: the Dockerfile to build the extension container image. -- `metadata.hcl`: the file providing specific metadata information used to build and test the extension. -- `README.md`: a template README file to get started with documenting the extension usage. - -Those files are scaffolded with a generic template and are meant to be customized according to the -specific extension requirements. From 49095b5cdba16f142179e58fb24756c91c8a8840 Mon Sep 17 00:00:00 2001 From: Gabriele Bartolini Date: Thu, 22 Jan 2026 17:28:49 +1100 Subject: [PATCH 07/17] chore: reviewed templates Added copyright info Signed-off-by: Gabriele Bartolini --- templates/Dockerfile.tmpl | 30 +++++++++++++++++++++--------- templates/README.md.tmpl | 4 ++++ templates/metadata.hcl.tmpl | 2 ++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/templates/Dockerfile.tmpl b/templates/Dockerfile.tmpl index fb36854..b606ec5 100644 --- a/templates/Dockerfile.tmpl +++ b/templates/Dockerfile.tmpl @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright © contributors to CloudNativePG, established as CloudNativePG a Series of LF Projects, LLC. +# SPDX-License-Identifier: Apache-2.0 + {{ $packageName := replaceAll .Package "%version%" "${PG_MAJOR}" }} ARG BASE=ghcr.io/cloudnative-pg/postgresql:{{ .DefaultVersion }}-minimal-{{ .DefaultDistro }} FROM $BASE AS builder @@ -7,36 +10,45 @@ ARG EXT_VERSION USER 0 -# Install extension +# +# Instructions: +# 1. Remove all blocks. +# 2. Uncomment and customize the COPY/RUN commands below. +# 3. If your extension requires additional system libraries, ensure they are +# copied to /lib. +# -# RUN apt-get update && \ -# apt-get install -y --no-install-recommends "{{ $packageName }}=${EXT_VERSION}" +# Install extension via `apt-get` +# RUN apt-get update && apt-get install -y --no-install-recommends \ +# "{{ $packageName }}=${EXT_VERSION}" FROM scratch ARG PG_MAJOR # Licenses +# # Including license files is essential for legal compliance and transparency. # It ensures proper attribution to original authors, fulfills open source license # requirements, and enables automated compliance scanning tools to verify licensing. - +# # COPY --from=builder /usr/share/doc/{{ $packageName }}/copyright /licenses/{{ $packageName }}/ # Libraries +# # Include the extension's shared objects and related dependencies under the /lib directory. # CNPG will load these libraries at runtime by configuring the dynamic linker path (LD_LIBRARY_PATH) and # the PostgreSQL GUC "dynamic_library_path" based on defaults and Cluster configuration. -# For more details, see: https://cloudnative-pg.io/docs/1.28/imagevolume_extensions#how-it-works - +# For more details, see: https://cloudnative-pg.io/docs/current/imagevolume_extensions#how-it-works +# # COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/{{ .Name }}* /lib/ -# COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/bitcode/ /lib/bitcode/ # Share +# # Include the extension's SQL scripts and control files under the /share/extension directory. # CNPG will configure PostgreSQL to include this path via the "extension_control_path" GUC to # seamlessly find and manage the current extension. -# For more details, see: https://cloudnative-pg.io/docs/1.28/imagevolume_extensions#how-it-works - +# For more details, see: https://cloudnative-pg.io/docs/current/imagevolume_extensions#how-it-works +# # COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/extension/{{ .Name }}* /share/extension/ USER 65532:65532 diff --git a/templates/README.md.tmpl b/templates/README.md.tmpl index 610fde1..fff8a96 100644 --- a/templates/README.md.tmpl +++ b/templates/README.md.tmpl @@ -1,4 +1,8 @@ # {{ toTitle .Name }} + -The {{ .Name }} PostgreSQL extension provides support for -vector data types and operations, enabling efficient storage and retrieval of -high-dimensional vectors. It is particularly useful for applications involving -machine learning, recommendation systems, and similarity search. +The {{ .Name }} PostgreSQL extension provides [describe the main functionality +here]. For more information, see the [official documentation](https://example.com). ## Usage From 9a8a5d4be0ea0b4ca95d42b16be89523d2445876 Mon Sep 17 00:00:00 2001 From: Marco Nenciarini Date: Thu, 22 Jan 2026 18:43:39 +0100 Subject: [PATCH 16/17] chore: improve Dockerfile template comments Replace unconventional blocks with standard TODO comments for better readability and consistency with common practices. Signed-off-by: Marco Nenciarini --- templates/Dockerfile.tmpl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/templates/Dockerfile.tmpl b/templates/Dockerfile.tmpl index 426562a..70ac856 100644 --- a/templates/Dockerfile.tmpl +++ b/templates/Dockerfile.tmpl @@ -10,13 +10,12 @@ ARG EXT_VERSION USER 0 -# +# TODO: Remove this comment block after customizing the Dockerfile # Instructions: -# 1. Remove all blocks. +# 1. Remove all TODO comment blocks after completing the customization. # 2. Uncomment and customize the COPY/RUN commands below. # 3. If your extension requires additional system libraries, ensure they are # copied to /lib. -# # Install extension via `apt-get` # RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -26,29 +25,26 @@ FROM scratch ARG PG_MAJOR # Licenses -# +# TODO: Uncomment and customize the COPY command below # Including license files is essential for legal compliance and transparency. # It ensures proper attribution to original authors, fulfills open source license # requirements, and enables automated compliance scanning tools to verify licensing. -# # COPY --from=builder /usr/share/doc/{{ $packageName }}/copyright /licenses/{{ $packageName }}/ # Libraries -# +# TODO: Uncomment and customize the COPY command below # Include the extension's shared objects and related dependencies under the /lib directory. # CNPG will configure PostgreSQL to locate these libraries at runtime by configuring # the "dynamic_library_path" GUC to include this path. # For more details, see: https://cloudnative-pg.io/docs/current/imagevolume_extensions#how-it-works -# # COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/{{ .Name }}* /lib/ # Share -# +# TODO: Uncomment and customize the COPY command below # Include the extension's SQL scripts and control files under the /share/extension directory. # CNPG will configure PostgreSQL to include this path via the "extension_control_path" GUC to # seamlessly find and manage the current extension. # For more details, see: https://cloudnative-pg.io/docs/current/imagevolume_extensions#how-it-works -# # COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/extension/{{ .Name }}* /share/extension/ USER 65532:65532 From d15fe0f80197e30d5f2e045c79cbdf89ab68a31f Mon Sep 17 00:00:00 2001 From: Marco Nenciarini Date: Thu, 22 Jan 2026 18:44:07 +0100 Subject: [PATCH 17/17] chore: improve create-extension task validation Add precondition to check NAME is not empty and improve error messages with usage examples. Add description to NAME variable requirement. Signed-off-by: Marco Nenciarini --- Taskfile.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 7180b9b..3071cee 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -130,15 +130,18 @@ tasks: task: update-os-libs create-extension: - desc: Scaffold a new extension directory + desc: Scaffold a new extension directory. Usage - task create-extension NAME=myextension cmds: - dagger call -sm ./dagger/maintenance/ create --name {{.NAME}} export --path ./{{.NAME}} preconditions: - - sh: test ! -d {{ .NAME }} - msg: 'The provided extension already exists. Name: {{.NAME}}' + - sh: test -n "{{.NAME}}" + msg: 'NAME variable is required. Usage - task create-extension NAME=myextension' + - sh: test ! -d {{.NAME}} + msg: 'Extension directory already exists at ./{{.NAME}}. Please choose a different name or remove the existing directory.' requires: vars: - name: NAME + desc: The name of the extension to create (lowercase alphanumeric, hyphens, and underscores only) generate-values: desc: Generate Chainsaw testing values for the specified target