diff --git a/backend/controller/artefacts/hybrid_registry.go b/backend/controller/artefacts/hybrid_registry.go index 5939e5069f..e3af3f72af 100644 --- a/backend/controller/artefacts/hybrid_registry.go +++ b/backend/controller/artefacts/hybrid_registry.go @@ -60,8 +60,12 @@ func (s *hybridRegistry) Download(ctx context.Context, digest sha256.SHA256) (io } func (s *hybridRegistry) GetReleaseArtefacts(ctx context.Context, releaseID int64) ([]ReleaseArtefact, error) { - // note: the container and database store currently use release_artefacts to associated - return s.container.GetReleaseArtefacts(ctx, releaseID) + if ras, err := s.dal.GetReleaseArtefacts(ctx, releaseID); err != nil { + return nil, fmt.Errorf("unable to get release artefacts from container store: %w", err) + } else if len(ras) > 0 { + return ras, nil + } + return s.dal.GetReleaseArtefacts(ctx, releaseID) } func (s *hybridRegistry) AddReleaseArtefact(ctx context.Context, key model.DeploymentKey, ra ReleaseArtefact) error { diff --git a/backend/controller/artefacts/internal/sql/querier.go b/backend/controller/artefacts/internal/sql/querier.go index e51f7cef28..ae217a4db5 100644 --- a/backend/controller/artefacts/internal/sql/querier.go +++ b/backend/controller/artefacts/internal/sql/querier.go @@ -17,6 +17,9 @@ type Querier interface { GetArtefactDigests(ctx context.Context, digests [][]byte) ([]GetArtefactDigestsRow, error) // Get all artefacts matching the given digests. GetDeploymentArtefacts(ctx context.Context, deploymentID int64) ([]GetDeploymentArtefactsRow, error) + // Get all artefacts associated with the specified release_id + GetReleaseArtefacts(ctx context.Context, releaseID int64) ([]GetReleaseArtefactsRow, error) + PublishReleaseArtefact(ctx context.Context, arg PublishReleaseArtefactParams) error } var _ Querier = (*Queries)(nil) diff --git a/backend/controller/artefacts/internal/sql/queries.sql b/backend/controller/artefacts/internal/sql/queries.sql index 9938764fc4..e5a40b85b6 100644 --- a/backend/controller/artefacts/internal/sql/queries.sql +++ b/backend/controller/artefacts/internal/sql/queries.sql @@ -26,4 +26,15 @@ WHERE deployment_id = $1; -- name: AssociateArtefactWithDeployment :exec INSERT INTO deployment_artefacts (deployment_id, artefact_id, executable, path) -VALUES ((SELECT id FROM deployments WHERE key = @key::deployment_key), (SELECT id FROM artefacts WHERE digest = @digest::bytea), $3, $4); \ No newline at end of file +VALUES ((SELECT id FROM deployments WHERE key = @key::deployment_key), (SELECT id FROM artefacts WHERE digest = @digest::bytea), $3, $4); + +-- name: GetReleaseArtefacts :many +-- Get all artefacts associated with the specified release_id +SELECT created_at, digest, executable, path +FROM release_artefacts +WHERE release_id = $1; + +-- name: PublishReleaseArtefact :exec +INSERT INTO release_artefacts(release_id, digest, executable, path) +VALUES ((SELECT id FROM deployments WHERE key = @key::deployment_key), $2, $3, $4) + ON CONFLICT (release_id, digest) DO NOTHING; \ No newline at end of file diff --git a/backend/controller/artefacts/internal/sql/queries.sql.go b/backend/controller/artefacts/internal/sql/queries.sql.go index d42d7e1ad1..f4243bbf4c 100644 --- a/backend/controller/artefacts/internal/sql/queries.sql.go +++ b/backend/controller/artefacts/internal/sql/queries.sql.go @@ -145,3 +145,68 @@ func (q *Queries) GetDeploymentArtefacts(ctx context.Context, deploymentID int64 } return items, nil } + +const getReleaseArtefacts = `-- name: GetReleaseArtefacts :many +SELECT created_at, digest, executable, path +FROM release_artefacts +WHERE release_id = $1 +` + +type GetReleaseArtefactsRow struct { + CreatedAt time.Time + Digest []byte + Executable bool + Path string +} + +// Get all artefacts associated with the specified release_id +func (q *Queries) GetReleaseArtefacts(ctx context.Context, releaseID int64) ([]GetReleaseArtefactsRow, error) { + rows, err := q.db.QueryContext(ctx, getReleaseArtefacts, releaseID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetReleaseArtefactsRow + for rows.Next() { + var i GetReleaseArtefactsRow + if err := rows.Scan( + &i.CreatedAt, + &i.Digest, + &i.Executable, + &i.Path, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const publishReleaseArtefact = `-- name: PublishReleaseArtefact :exec +INSERT INTO release_artefacts(release_id, digest, executable, path) +VALUES ((SELECT id FROM deployments WHERE key = $1::deployment_key), $2, $3, $4) + ON CONFLICT (release_id, digest) DO NOTHING +` + +type PublishReleaseArtefactParams struct { + Key model.DeploymentKey + Digest []byte + Executable bool + Path string +} + +func (q *Queries) PublishReleaseArtefact(ctx context.Context, arg PublishReleaseArtefactParams) error { + _, err := q.db.ExecContext(ctx, publishReleaseArtefact, + arg.Key, + arg.Digest, + arg.Executable, + arg.Path, + ) + return err +} diff --git a/backend/controller/artefacts/oci_registry.go b/backend/controller/artefacts/oci_registry.go index f5d46c3246..5125c97f11 100644 --- a/backend/controller/artefacts/oci_registry.go +++ b/backend/controller/artefacts/oci_registry.go @@ -6,9 +6,9 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/TBD54566975/ftl/internal/slices" "io" - "github.com/opencontainers/go-digest" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/errdef" @@ -23,14 +23,14 @@ import ( ) const ( - ModuleBlobsPrefix = "ftl/modules/" + ModuleBlobsPrefix = "ftl/modules" ) type ContainerConfig struct { - Registry string `help:"OCI container registry host:port" env:"FTL_ARTEFACTS_REGISTRY"` - Username string `help:"OCI container registry username" env:"FTL_ARTEFACTS_USER"` - Password string `help:"OCI container registry password" env:"FTL_ARTEFACTS_PWD"` - AllowPlainHTTP bool `help:"Allows OCI container requests to accept plain HTTP responses" env:"FTL_ARTEFACTS_ALLOW_HTTP"` + Registry string `help:"OCI container registry host:port" default:"127.0.0.1:5001" env:"FTL_ARTEFACTS_REGISTRY"` + RegistryUsername string `help:"OCI container registry username" env:"FTL_ARTEFACTS_USER"` + RegistryPassword string `help:"OCI container registry password" env:"FTL_ARTEFACTS_PWD"` + RegistryAllowHTTP bool `help:"Allows OCI container requests to accept plain HTTP responses" default:"true" env:"FTL_ARTEFACTS_ALLOW_HTTP"` } type containerRegistry struct { @@ -42,20 +42,6 @@ type containerRegistry struct { db sql.Querier } -type ArtefactRepository struct { - ModuleDigest sha256.SHA256 - MediaType string - ArtefactType string - RepositoryDigest digest.Digest - Size int64 -} - -type ArtefactBlobs struct { - Digest sha256.SHA256 - MediaType string - Size int64 -} - func newContainerRegistry(c ContainerConfig, conn libdal.Connection) *containerRegistry { // Connect the registry targeting the specified container repoFactory := func() (*remote.Repository, error) { @@ -69,11 +55,11 @@ func newContainerRegistry(c ContainerConfig, conn libdal.Connection) *containerR Client: retry.DefaultClient, Cache: auth.NewCache(), Credential: auth.StaticCredential(c.Registry, auth.Credential{ - Username: c.Username, - Password: c.Password, + Username: c.RegistryUsername, + Password: c.RegistryPassword, }), } - reg.PlainHTTP = c.AllowPlainHTTP + reg.PlainHTTP = c.RegistryAllowHTTP return reg, nil } @@ -148,11 +134,30 @@ func (s *containerRegistry) Download(ctx context.Context, digest sha256.SHA256) } func (s *containerRegistry) GetReleaseArtefacts(ctx context.Context, releaseID int64) ([]ReleaseArtefact, error) { - return getDatabaseReleaseArtefacts(ctx, s.db, releaseID) + rows, err := s.db.GetReleaseArtefacts(ctx, releaseID) + if err != nil { + return nil, fmt.Errorf("unable to get release artefacts: %w", libdal.TranslatePGError(err)) + } + return slices.Map(rows, func(row sql.GetReleaseArtefactsRow) ReleaseArtefact { + return ReleaseArtefact{ + Artefact: ArtefactKey{Digest: sha256.FromBytes(row.Digest)}, + Path: row.Path, + Executable: row.Executable, + } + }), nil } func (s *containerRegistry) AddReleaseArtefact(ctx context.Context, key model.DeploymentKey, ra ReleaseArtefact) error { - return addReleaseArtefacts(ctx, s.db, key, ra) + params := sql.PublishReleaseArtefactParams{ + Key: key, + Digest: ra.Artefact.Digest[:], + Executable: ra.Executable, + Path: ra.Path, + } + if err := s.db.PublishReleaseArtefact(ctx, params); err != nil { + return libdal.TranslatePGError(err) + } + return nil } // createModuleRepositoryPathFromDigest creates the path to the repository, relative to the registries root diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 74b4763491..8a9bae2cd5 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -109,7 +109,7 @@ type Config struct { KMSURI *string `help:"URI for KMS key e.g. with fake-kms:// or aws-kms://arn:aws:kms:ap-southeast-2:12345:key/0000-1111" env:"FTL_KMS_URI"` MaxOpenDBConnections int `help:"Maximum number of database connections." default:"20" env:"FTL_MAX_OPEN_DB_CONNECTIONS"` MaxIdleDBConnections int `help:"Maximum number of idle database connections." default:"20" env:"FTL_MAX_IDLE_DB_CONNECTIONS"` - ContainerConfig artefacts.ContainerConfig + artefacts.ContainerConfig CommonConfig } diff --git a/backend/controller/sql/schema/20241030190730_create_release_artefacts.sql b/backend/controller/sql/schema/20241030190730_create_release_artefacts.sql new file mode 100644 index 0000000000..4d4744fb64 --- /dev/null +++ b/backend/controller/sql/schema/20241030190730_create_release_artefacts.sql @@ -0,0 +1,14 @@ +-- migrate:up + +-- release_artefacts stores references to OCI artefacts (compiled modules) +CREATE TABLE release_artefacts ( + release_id BIGINT NOT NULL REFERENCES deployments (id) ON DELETE CASCADE, + digest BYTEA UNIQUE NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), + executable BOOLEAN NOT NULL, + -- Path relative to the module root. + path TEXT NOT NULL, + + UNIQUE (release_id, digest) +); +-- migrate:down \ No newline at end of file diff --git a/frontend/cli/cmd_release.go b/frontend/cli/cmd_release.go index e166ff4479..da3c2d9b16 100644 --- a/frontend/cli/cmd_release.go +++ b/frontend/cli/cmd_release.go @@ -83,7 +83,7 @@ func createContainerService(release *releaseCmd) (artefacts.Service, error) { conn.SetMaxOpenConns(release.MaxOpenDBConnections) return artefacts.New(artefacts.ContainerConfig{ - Registry: release.Registry, - AllowPlainHTTP: true, + Registry: release.Registry, + RegistryAllowHTTP: true, }, conn), nil }