Skip to content
Closed
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
16 changes: 8 additions & 8 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sql:
queries: "postgresql/query.sql"
engine: "postgresql"
gen:
go:
go:
package: "authors"
out: "postgresql"
database:
Expand Down Expand Up @@ -122,7 +122,7 @@ The `analyzer` mapping supports the following keys:

- `database`:
- If false, do not use the configured database for query analysis. Defaults to `true`.

### gen

The `gen` mapping supports the following keys:
Expand Down Expand Up @@ -254,21 +254,21 @@ Each mapping in the `plugins` collection has the following keys:
- The format expected. Supports `json` and `protobuf` formats. Defaults to `protobuf`.
- `wasm`: A mapping with a two keys `url` and `sha256`
- `url`:
- The URL to fetch the WASM file. Supports the `https://` or `file://` schemes.
- The URL to fetch the WASM file. Supports the `https://`, `file://`, or `oci://` schemes. OCI artifacts may be referenced by tag or digest and must use the [CNCF Wasm OCI artifact layout](https://tag-runtime.cncf.io/wgs/wasm/deliverables/wasm-oci-artifact/): exactly one `application/wasm` layer and a config with media type `application/vnd.wasm.config.v0+json`, `architecture: "wasm"`, `os: "wasip1"`, and `layerDigests` matching the layer digest. WASI Preview 2 components are not supported. Registry authentication uses the Docker config, including configured credential helpers.
- `sha256`
- The SHA256 checksum for the downloaded file.

```yaml
version: "2"
plugins:
- name: "py"
wasm:
wasm:
url: "https://github.com/sqlc-dev/sqlc-gen-python/releases/download/v0.16.0-alpha/sqlc-gen-python.wasm"
sha256: "428476c7408fd4c032da4ec74e8a7344f4fa75e0f98a5a3302f238283b9b95f2"
- name: "js"
env:
- PATH
process:
process:
cmd: "sqlc-gen-json"
```

Expand All @@ -285,7 +285,7 @@ Each mapping in the `rules` collection has the following keys:

See the [vet](../howto/vet.md) documentation for a list of built-in rules and
help writing custom rules.

```yaml
version: "2"
sql:
Expand Down Expand Up @@ -319,7 +319,7 @@ rules:
rule: |
query.cmd == "exec"
```

### Global overrides

Sometimes, the same configuration must be done across various specifications of
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ require (
github.com/jackc/pgx/v5 v5.11.0
github.com/jinzhu/inflection v1.0.0
github.com/ncruces/go-sqlite3 v0.35.6
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/riza-io/grpc-go v0.2.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
Expand All @@ -31,6 +33,7 @@ require (
google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12
gopkg.in/yaml.v3 v3.0.1
oras.land/oras-go/v2 v2.6.2
)

require (
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ github.com/ncruces/go-sqlite3-wasm/v6 v6.3.35304 h1:dBSZlcEFdtBMvNRg34y50mConBPO
github.com/ncruces/go-sqlite3-wasm/v6 v6.3.35304/go.mod h1:YvoJzbJpX6phd3BGdtiXu2NuD5RX6G8zsUdzt47GgOY=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ=
Expand Down Expand Up @@ -114,3 +118,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo=
oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4=
122 changes: 122 additions & 0 deletions internal/ext/wasm/oci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package wasm

import (
"context"
"encoding/json"
"fmt"
"runtime"
"strings"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
"oras.land/oras-go/v2/registry/remote/retry"

"github.com/sqlc-dev/sqlc/internal/info"
)

const (
wasmLayerMediaType = "application/wasm"
wasmConfigMediaType = "application/vnd.wasm.config.v0+json"
)

type wasmConfig struct {
Architecture string `json:"architecture"`
OS string `json:"os"`
LayerDigests []string `json:"layerDigests"`
}

func fetchOCI(ctx context.Context, uri string, plainHTTP bool) ([]byte, error) {
reference := strings.TrimPrefix(uri, "oci://")
repo, err := remote.NewRepository(reference)
if err != nil {
return nil, fmt.Errorf("parse OCI reference %q: %w", uri, err)
}
repo.PlainHTTP = plainHTTP

store, err := credentials.NewStoreFromDocker(credentials.StoreOptions{})
if err != nil {
return nil, fmt.Errorf("load Docker credentials: %w", err)
}
client := &auth.Client{
Client: retry.DefaultClient,
Cache: auth.NewCache(),
Credential: credentials.Credential(store),
}
client.SetUserAgent(fmt.Sprintf("sqlc/%s Go/%s (%s %s)", info.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH))
repo.Client = client

desc, manifestReader, err := repo.FetchReference(ctx, repo.Reference.Reference)
if err != nil {
return nil, fmt.Errorf("fetch OCI manifest %q: %w", uri, err)
}
defer manifestReader.Close()
if desc.MediaType != ocispec.MediaTypeImageManifest {
return nil, fmt.Errorf("OCI reference %q resolved to unsupported media type %q", uri, desc.MediaType)
}
manifestData, err := content.ReadAll(manifestReader, desc)
if err != nil {
return nil, fmt.Errorf("read OCI manifest %q: %w", uri, err)
}
var manifest ocispec.Manifest
if err := json.Unmarshal(manifestData, &manifest); err != nil {
return nil, fmt.Errorf("decode OCI manifest %q: %w", uri, err)
}
if manifest.SchemaVersion != 2 {
return nil, fmt.Errorf("OCI artifact %q: unsupported schema version %d", uri, manifest.SchemaVersion)
}
layer, err := wasmLayer(manifest.Layers)
if err != nil {
return nil, fmt.Errorf("OCI artifact %q: %w", uri, err)
}
if manifest.Config.MediaType != wasmConfigMediaType {
return nil, fmt.Errorf("OCI artifact %q: unsupported config media type %q", uri, manifest.Config.MediaType)
}
configData, err := content.FetchAll(ctx, repo, manifest.Config)
if err != nil {
return nil, fmt.Errorf("fetch OCI WASM config %q: %w", uri, err)
}
if err := validateWASMConfig(configData, layer); err != nil {
return nil, fmt.Errorf("OCI artifact %q: %w", uri, err)
}

body, err := repo.Fetch(ctx, layer)
if err != nil {
return nil, fmt.Errorf("fetch OCI WASM layer %q: %w", uri, err)
}
defer body.Close()
wmod, err := content.ReadAll(body, layer)
if err != nil {
return nil, fmt.Errorf("read OCI WASM layer %q: %w", uri, err)
}
return wmod, nil
}

func wasmLayer(layers []ocispec.Descriptor) (ocispec.Descriptor, error) {
if len(layers) != 1 {
return ocispec.Descriptor{}, fmt.Errorf("manifest must contain exactly one WASM layer")
}
if layers[0].MediaType != wasmLayerMediaType {
return ocispec.Descriptor{}, fmt.Errorf("unsupported WASM layer media type %q", layers[0].MediaType)
}
return layers[0], nil
}

func validateWASMConfig(data []byte, layer ocispec.Descriptor) error {
var config wasmConfig
if err := json.Unmarshal(data, &config); err != nil {
return fmt.Errorf("decode WASM config: %w", err)
}
if config.Architecture != "wasm" {
return fmt.Errorf("unsupported WASM architecture %q", config.Architecture)
}
if config.OS != "wasip1" {
return fmt.Errorf("unsupported WASM OS %q: only wasip1 plugins are supported", config.OS)
}
if len(config.LayerDigests) != 1 || config.LayerDigests[0] != layer.Digest.String() {
return fmt.Errorf("WASM config layerDigests must match the manifest layers")
}
return nil
}
Loading
Loading