Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,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=<extension-name>
```

The command will create a new directory named `<extension-name>` 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] <function>

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%-<name>")
--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.
8 changes: 8 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ tasks:
TARGET: "{{.ITEM}}"
task: update-os-libs

create-extension:
desc: Scaffold a new extension directory
cmds:
- dagger call -sm ./dagger/maintenance/ create --name {{.NAME}} export --path ./{{.NAME}}
requires:
vars:
- name: NAME

e2e:create-docker-network:
desc: Create Docker network to connect all the services, such as the Registry, Kind nodes, Chainsaw, etc.
run: once
Expand Down
90 changes: 90 additions & 0 deletions dagger/maintenance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"maps"
"path"
"slices"
"strings"
"text/template"

"go.yaml.in/yaml/v3"
"golang.org/x/text/cases"
"golang.org/x/text/language"

"dagger/maintenance/internal/dagger"
)
Expand Down Expand Up @@ -189,3 +194,88 @@ 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%-<name>")
// +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
}
42 changes: 42 additions & 0 deletions templates/Dockerfile.tmpl
Original file line number Diff line number Diff line change
@@ -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
# 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

# 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
72 changes: 72 additions & 0 deletions templates/README.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# {{ toTitle .Name }}

<!--
Introduction: give a brief introduction of the extension and what it is useful for.
Add a reference to the official documentation if available.
-->

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.

## Usage

<!--
Usage: add instructions on how to use the extension with CloudNativePG.
Include code snippets for Cluster and Database resources as needed.
-->

### 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.
21 changes: 21 additions & 0 deletions templates/metadata.hcl.tmpl
Original file line number Diff line number Diff line change
@@ -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 }}" = "<package-version-here>"
{{- end}}
}
{{- end}}
}
}