Skip to content
This repository was archived by the owner on Mar 17, 2026. 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
6 changes: 3 additions & 3 deletions engines/terraform/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (td *TerraformDeployment) resolveInfraResource(infraName string) (cdktf.Ter
if _, ok := td.terraformInfraResources[infraName]; !ok {
pluginRef, err := td.engine.resolvePlugin(resource)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not resolve plugin for infra resource %s: %w", infraName, err)
}

td.createVariablesForIntent(infraName, resource)
Expand All @@ -85,7 +85,7 @@ func (td *TerraformDeployment) resolveEntrypointSugaVar(name string, appSpec *ap
for path, route := range spec.Routes {
intentTarget, ok := appSpec.GetResourceIntent(route.TargetName)
if !ok {
return nil, fmt.Errorf("target %s not found", route.TargetName)
return nil, fmt.Errorf("entrypoint '%s' has route target with name '%s', but no resources found with that name", name, route.TargetName)
}

var intentTargetType string
Expand All @@ -95,7 +95,7 @@ func (td *TerraformDeployment) resolveEntrypointSugaVar(name string, appSpec *ap
case *app_spec_schema.BucketIntent:
intentTargetType = "bucket"
default:
return nil, fmt.Errorf("target %s is not a service or bucket", route.TargetName)
return nil, fmt.Errorf("entrypoint '%s' has target '%s', which is not a service or bucket", name, route.TargetName)
}

hclTarget, ok := td.terraformResources[route.TargetName]
Expand Down
95 changes: 64 additions & 31 deletions engines/terraform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"io"
"maps"
"os"
"regexp"
"slices"
"strings"

Expand All @@ -15,10 +14,34 @@ import (
"gopkg.in/yaml.v3"
)

type libraryID string

func NewLibraryID(team string, library string) libraryID {
return libraryID(fmt.Sprintf("%s/%s", team, library))
}

func (id libraryID) Team() string {
parts := strings.Split(string(id), "/")
if len(parts) != 2 {
return ""
}
return parts[0]
}

func (id libraryID) Name() string {
parts := strings.Split(string(id), "/")
if len(parts) != 2 {
return ""
}
return parts[1]
}

type libraryVersion string

type PlatformSpec struct {
Name string `json:"name" yaml:"name"`

Libraries map[string]string `json:"libraries" yaml:"libraries"`
Libraries map[libraryID]libraryVersion `json:"libraries" yaml:"libraries"`

Variables map[string]Variable `json:"variables" yaml:"variables,omitempty"`

Expand Down Expand Up @@ -48,31 +71,31 @@ type Plugin struct {
Name string `json:"name" yaml:"name"`
}

func (p PlatformSpec) GetLibrary(name string) (*Library, error) {
library, ok := p.Libraries[name]
func (p PlatformSpec) GetLibrary(id libraryID) (*Library, error) {
libVersion, ok := p.Libraries[id]
if !ok {
return nil, fmt.Errorf("library %s not found in platform spec, configured libraries in platform are: %v", name, slices.Collect(maps.Keys(p.Libraries)))
return nil, fmt.Errorf("library %s not found in platform spec, configured libraries in platform are: %v", id, slices.Collect(maps.Keys(p.Libraries)))
}

pattern := `^(?P<team>[^/]+)/(?P<library>[^@]+)@(?P<version>.+)$`
re := regexp.MustCompile(pattern)
// pattern := `^(?P<team>[^/]+)/(?P<library>[^@]+)@(?P<version>.+)$`
// re := regexp.MustCompile(pattern)

matches := re.FindStringSubmatch(library)
if len(matches) == 0 {
return nil, fmt.Errorf("invalid library format: %s, expected format: `<team>/<platform>@<version>`", library)
}
// matches := re.FindStringSubmatch(string(libVersion))
// if len(matches) == 0 {
// return nil, fmt.Errorf("invalid library format: %s, expected format: `<team>/<platform>@<version>`", libVersion)
// }

team := matches[re.SubexpIndex("team")]
libName := matches[re.SubexpIndex("library")]
version := matches[re.SubexpIndex("version")]
// team := matches[re.SubexpIndex("team")]
// libName := matches[re.SubexpIndex("library")]
// version := matches[re.SubexpIndex("version")]

return &Library{Team: team, Name: libName, Version: version}, nil
return &Library{Team: id.Team(), Name: id.Name(), Version: string(libVersion)}, nil
}
Comment thread
jyecusch marked this conversation as resolved.

func (p PlatformSpec) GetLibraries() map[string]*Library {
libraries := map[string]*Library{}
for name, library := range p.Libraries {
libraries[name], _ = p.GetLibrary(library)
func (p PlatformSpec) GetLibraries() map[libraryID]*Library {
libraries := map[libraryID]*Library{}
for id := range p.Libraries {
libraries[id], _ = p.GetLibrary(id)
}
return libraries
}
Expand All @@ -85,7 +108,7 @@ func (p PlatformSpec) GetServiceBlueprint(intentSubType string) (*ServiceBluepri
}

concreteSpec, ok := spec[intentSubType]
if !ok {
if !ok || concreteSpec == nil {
return nil, fmt.Errorf("platform %s does not define a %s type for services, available types: %v", p.Name, intentSubType, slices.Collect(maps.Keys(spec)))
}

Expand Down Expand Up @@ -198,30 +221,40 @@ func PlatformFromId(fs afero.Fs, platformId string, repositories ...PlatformRepo
return nil, fmt.Errorf("platform %s not found in any repository", platformId)
}

type pluginSource struct {
Library libraryID `json:"library" yaml:"library"`
Plugin string `json:"plugin" yaml:"plugin"`
}

func NewPluginSource(library libraryID, plugin string) pluginSource {
return pluginSource{Library: library, Plugin: plugin}
}

type ResourceBlueprint struct {
PluginId string `json:"plugin" yaml:"plugin"`
Source pluginSource `json:"source" yaml:"source"`
Properties map[string]interface{} `json:"properties" yaml:"properties"`
DependsOn []string `json:"depends_on" yaml:"depends_on,omitempty"`
Variables map[string]Variable `json:"variables" yaml:"variables,omitempty"`
}

func (r *ResourceBlueprint) ResolvePlugin(platform *PlatformSpec) (*Plugin, error) {
pluginId := r.PluginId

pluginParts := strings.Split(pluginId, "/")
if len(pluginParts) != 2 {
return nil, fmt.Errorf("invalid plugin id %s, expected format: <library>/<plugin>", pluginId)
if r == nil {
return nil, fmt.Errorf("resource blueprint is nil, this indicates a malformed platform spec")
}
if r.Source.Library == "" {
return nil, fmt.Errorf("no source library specified for resource blueprint, this indicates a malformed platform spec")
}

libraryName := pluginParts[0]
pluginName := pluginParts[1]
if r.Source.Plugin == "" {
return nil, fmt.Errorf("no source plugin specified for resource blueprint, this indicates a malformed platform spec")
}

lib, err := platform.GetLibrary(libraryName)
lib, err := platform.GetLibrary(r.Source.Library)
if err != nil {
return nil, fmt.Errorf("failed to resolve library for plugin %s, %w", pluginId, err)
return nil, fmt.Errorf("failed to resolve library for plugin %s, %w", r.Source.Plugin, err)
}

return &Plugin{Library: *lib, Name: pluginName}, nil
return &Plugin{Library: *lib, Name: r.Source.Plugin}, nil
}

type IdentitiesBlueprint struct {
Expand Down
10 changes: 5 additions & 5 deletions engines/terraform/resource_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (td *TerraformDeployment) processServiceIdentities(appSpec *app_spec_schema
}
plug, err := td.engine.resolvePlugin(spec.ResourceBlueprint)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not resolve plugin for service %s: %w", intentName, err)
}

sugaVar, err := td.resolveService(intentName, serviceIntent, spec, plug)
Expand All @@ -81,7 +81,7 @@ func (td *TerraformDeployment) processServiceResources(appSpec *app_spec_schema.
}
plug, err := td.engine.resolvePlugin(spec)
if err != nil {
return err
return fmt.Errorf("could not resolve plugin for service %s: %w", intentName, err)
}

sugaVar := serviceInputs[intentName]
Expand Down Expand Up @@ -119,7 +119,7 @@ func (td *TerraformDeployment) processBucketResources(appSpec *app_spec_schema.A
}
plug, err := td.engine.resolvePlugin(spec)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not resolve plugin for bucket %s: %w", intentName, err)
}

servicesInput := map[string]any{}
Expand Down Expand Up @@ -173,7 +173,7 @@ func (td *TerraformDeployment) processEntrypointResources(appSpec *app_spec_sche
}
plug, err := td.engine.resolvePlugin(spec)
if err != nil {
return err
return fmt.Errorf("could not resolve plugin for entrypoint %s: %w", intentName, err)
}

sugaVar, err := td.resolveEntrypointSugaVar(intentName, appSpec, entrypointIntent)
Expand Down Expand Up @@ -204,7 +204,7 @@ func (td *TerraformDeployment) processDatabaseResources(appSpec *app_spec_schema
}
plug, err := td.engine.resolvePlugin(spec)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not resolve plugin for database %s: %w", intentName, err)
}

servicesInput := map[string]any{}
Expand Down
7 changes: 5 additions & 2 deletions engines/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func NewPanicError(panicValue interface{}, stackTrace []byte) *PanicError {
}
}


type TerraformEngine struct {
platform *PlatformSpec
repository PluginRepository
Expand All @@ -57,6 +56,10 @@ func (e *TerraformEngine) resolveIdentityPlugin(blueprint *ResourceBlueprint) (*
}

func (e *TerraformEngine) resolvePlugin(blueprint *ResourceBlueprint) (*ResourcePluginManifest, error) {
if blueprint == nil {
return nil, fmt.Errorf("resource blueprint is nil, this indicates a malformed platform spec")
}

pluginRef, err := blueprint.ResolvePlugin(e.platform)
if err != nil {
return nil, err
Expand All @@ -75,7 +78,7 @@ func (e *TerraformEngine) GetPluginManifestsForType(typ string) (map[string]*Res
for blueprintIntent, blueprint := range blueprints {
plug, err := e.resolvePlugin(blueprint)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not resolve plugin for %s %s: %w", typ, blueprintIntent, err)
}
manifests[blueprintIntent] = plug
}
Expand Down