Skip to content
This repository was archived by the owner on Aug 17, 2025. It is now read-only.
Merged
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
12 changes: 8 additions & 4 deletions backend/provisioner/oci_image_provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ import (
"github.com/block/ftl/internal/oci"
)

func NewOCIImageProvisioner(storage *oci.ImageService, astorage *oci.ArtefactService, defaultImage string) *InMemProvisioner {
type OCIImageProvisionerConfig struct {
Env map[string]string `toml:"env"`
}

func NewOCIImageProvisioner(storage *oci.ImageService, astorage *oci.ArtefactService, defaultImage string, cfg OCIImageProvisionerConfig) *InMemProvisioner {
return NewEmbeddedProvisioner(map[schema.ResourceType]InMemResourceProvisionerFn{
schema.ResourceTypeImage: provisionOCIImage(storage, astorage, defaultImage),
schema.ResourceTypeImage: provisionOCIImage(storage, astorage, defaultImage, cfg),
}, map[schema.ResourceType]InMemResourceProvisionerFn{})
}

func provisionOCIImage(storage *oci.ImageService, astorage *oci.ArtefactService, defaultImage string) InMemResourceProvisionerFn {
func provisionOCIImage(storage *oci.ImageService, astorage *oci.ArtefactService, defaultImage string, cfg OCIImageProvisionerConfig) InMemResourceProvisionerFn {
return func(ctx context.Context, changeset key.Changeset, deployment key.Deployment, rc schema.Provisioned, moduleSch *schema.Module) (*schema.RuntimeElement, error) {
logger := log.FromContext(ctx)
variants := goslices.Collect(slices.FilterVariants[*schema.MetadataArtefact](moduleSch.Metadata))
Expand Down Expand Up @@ -56,7 +60,7 @@ func provisionOCIImage(storage *oci.ImageService, astorage *oci.ArtefactService,
}

target := storage.Image(deployment.Payload.Realm, deployment.Payload.Module, tag)
err = storage.BuildOCIImageFromRemote(ctx, astorage, image, target, tempDir, moduleSch, deployment, variants, oci.WithRemotePush())
err = storage.BuildOCIImageFromRemote(ctx, astorage, image, target, tempDir, moduleSch, deployment, variants, cfg.Env, oci.WithRemotePush())
if err != nil {
return nil, errors.Wrap(err, "failed to build image")
}
Expand Down
17 changes: 14 additions & 3 deletions backend/provisioner/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strings"

"github.com/BurntSushi/toml"
"github.com/alecthomas/errors"

"github.com/block/ftl/backend/protos/xyz/block/ftl/admin/v1/adminpbconnect"
Expand All @@ -25,6 +26,7 @@ type provisionerPluginConfig struct {
Plugins []struct {
ID string `toml:"id"`
Resources []schema.ResourceType `toml:"resources"`
Config *toml.Primitive `toml:"config"`
} `toml:"plugins"`
}

Expand Down Expand Up @@ -70,15 +72,15 @@ func (reg *ProvisionerRegistry) listBindings() []*ProvisionerBinding {

type pluginProcesses map[string]Plugin

func registryFromConfig(ctx context.Context, workingDir string, cfg *provisionerPluginConfig, runnerScaling scaling.RunnerScaling, adminClient adminpbconnect.AdminServiceClient, imageService *oci.ImageService, artifactService *oci.ArtefactService) (*ProvisionerRegistry, error) {
func registryFromConfig(ctx context.Context, workingDir string, cfg *provisionerPluginConfig, md *toml.MetaData, runnerScaling scaling.RunnerScaling, adminClient adminpbconnect.AdminServiceClient, imageService *oci.ImageService, artifactService *oci.ArtefactService) (*ProvisionerRegistry, error) {
logger := log.FromContext(ctx)
result := &ProvisionerRegistry{}
if err := cfg.Validate(); err != nil {
return nil, errors.Wrap(err, "error validating provisioner config")
}
processes := pluginProcesses{}
for _, plugin := range cfg.Plugins {
provisioner, err := provisionerIDToProvisioner(ctx, plugin.ID, workingDir, runnerScaling, adminClient, imageService, artifactService, processes)
provisioner, err := provisionerIDToProvisioner(ctx, plugin.ID, plugin.Config, md, workingDir, runnerScaling, adminClient, imageService, artifactService, processes)
if err != nil {
return nil, errors.WithStack(err)
}
Expand All @@ -91,6 +93,8 @@ func registryFromConfig(ctx context.Context, workingDir string, cfg *provisioner
func provisionerIDToProvisioner(
ctx context.Context,
id string,
config *toml.Primitive,
md *toml.MetaData,
workingDir string,
scaling scaling.RunnerScaling,
adminClient adminpbconnect.AdminServiceClient,
Expand All @@ -108,7 +112,14 @@ func provisionerIDToProvisioner(
case "noop":
return NewPluginClient(&NoopProvisioner{}), nil
case "oci-image":
return NewOCIImageProvisioner(imageService, artifactService, "ftl0/ftl-runner"), nil
cfg := OCIImageProvisionerConfig{}
if config != nil {
if err := md.PrimitiveDecode(*config, &cfg); err != nil {
return nil, errors.Wrap(err, "error unmarshalling oci-image provisioner config")
}
}

return NewOCIImageProvisioner(imageService, artifactService, "ftl0/ftl-runner", cfg), nil
default:
if _, ok := processes[id]; ok {
return processes[id], nil
Expand Down
6 changes: 4 additions & 2 deletions backend/provisioner/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,13 @@ func RegistryFromConfigFile(ctx context.Context, workingDir string, file *os.Fil
if err != nil {
return nil, errors.Wrapf(err, "error reading plugin configuration from %s", file.Name())
}
if err := toml.Unmarshal(bytes, &config); err != nil {

md, err := toml.Decode(string(bytes), &config)
if err != nil {
return nil, errors.Wrap(err, "error parsing plugin configuration")
}

registry, err := registryFromConfig(ctx, workingDir, &config, scaling, adminClient, imageService, artifactService)
registry, err := registryFromConfig(ctx, workingDir, &config, &md, scaling, adminClient, imageService, artifactService)
if err != nil {
return nil, errors.Wrap(err, "error creating provisioner registry")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/ftl-provisioner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func main() {
if _, ok := slices.Find(registry.Bindings, func(binding *provisioner.ProvisionerBinding) bool {
return slices.Contains(binding.Types, schema.ResourceTypeImage)
}); !ok {
ociProvisioner := provisioner.NewOCIImageProvisioner(imageService, artefactService, cli.DefaultRunnerImage)
ociProvisioner := provisioner.NewOCIImageProvisioner(imageService, artefactService, cli.DefaultRunnerImage, provisioner.OCIImageProvisionerConfig{})
runnerBinding := registry.Register("oci-image", ociProvisioner, schema.ResourceTypeImage)
logger.Debugf("Registered provisioner %s as fallback for image", runnerBinding)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/ftl/cmd_image_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (b *imageBuildCmd) Run(
// TODO: we need to properly sync the deployment with the actual deployment key
// this is just a hack to get the module and realm to the runner
deployment := key.NewDeploymentKey(projConfig.Name, moduleSch.Name)
err := imageService.BuildOCIImage(ctx, image, tgt, tmpDeployDir, deployment, artifacts, targets...)
err := imageService.BuildOCIImage(ctx, image, tgt, tmpDeployDir, deployment, artifacts, nil, targets...)
if err != nil {
return errors.Wrapf(err, "failed to build image")
}
Expand Down
7 changes: 6 additions & 1 deletion internal/oci/image_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func (s *ImageService) BuildOCIImageFromRemote(
module *schema.Module,
deployment key.Deployment,
artifacts []*schema.MetadataArtefact,
envVars map[string]string,
targets ...ImageTarget,
) error {
target, err := os.MkdirTemp(tempDir, "ftl-image-")
Expand Down Expand Up @@ -194,7 +195,7 @@ func (s *ImageService) BuildOCIImageFromRemote(
if err != nil {
return errors.Wrapf(err, "failed to download artifacts")
}
return s.BuildOCIImage(ctx, baseImage, targetImage, target, deployment, artifacts, targets...)
return s.BuildOCIImage(ctx, baseImage, targetImage, target, deployment, artifacts, envVars, targets...)

}

Expand All @@ -205,6 +206,7 @@ func (s *ImageService) BuildOCIImage(
apath string,
deployment key.Deployment,
allArtifacts []*schema.MetadataArtefact,
envVars map[string]string,
targets ...ImageTarget,
) error {
var artifacts []*schema.MetadataArtefact
Expand Down Expand Up @@ -281,6 +283,9 @@ func (s *ImageService) BuildOCIImage(
cfg.Config.Env = append(cfg.Config.Env, fmt.Sprintf("FTL_DEPLOYMENT=%s", deployment.String()))
cfg.Config.Env = append(cfg.Config.Env, "LOG_LEVEL=DEBUG")
cfg.Config.Env = append(cfg.Config.Env, "LOG_FORMAT=json")
for k, v := range envVars {
cfg.Config.Env = append(cfg.Config.Env, fmt.Sprintf("%s=%s", k, v))
}
cfg.Config.Labels[SchemaLabel] = schDigest.String()
newImg, err = mutate.Config(newImg, cfg.Config)
if err != nil {
Expand Down
Loading