Skip to content

Commit 1625041

Browse files
committed
feat(engines): add usable_by for filtering valid service subtypes for resource access
1 parent 9c73ca0 commit 1625041

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

engines/terraform/platform.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type PlatformSpec struct {
5151
TopicBlueprints map[string]*ResourceBlueprint `json:"topics,omitempty" yaml:"topics,omitempty"`
5252
DatabaseBlueprints map[string]*ResourceBlueprint `json:"databases,omitempty" yaml:"databases,omitempty"`
5353
EntrypointBlueprints map[string]*ResourceBlueprint `json:"entrypoints" yaml:"entrypoints"`
54-
InfraSpecs map[string]*ResourceBlueprint `json:"infra" yaml:"infra"`
54+
InfraSpecs map[string]*ResourceBlueprint `json:"infra" yaml:"infra"`
5555

5656
libraryReplacements map[libraryID]libraryVersion
5757
}
@@ -371,6 +371,7 @@ type ResourceBlueprint struct {
371371
Properties map[string]interface{} `json:"properties" yaml:"properties"`
372372
DependsOn []string `json:"depends_on" yaml:"depends_on,omitempty"`
373373
Variables map[string]Variable `json:"variables" yaml:"variables,omitempty"`
374+
UsableBy []string `json:"usable_by,omitempty" yaml:"usable_by,omitempty"`
374375
}
375376

376377
func (r *ResourceBlueprint) ResolvePlugin(platform *PlatformSpec) (*Plugin, error) {
@@ -393,6 +394,22 @@ func (r *ResourceBlueprint) ResolvePlugin(platform *PlatformSpec) (*Plugin, erro
393394
return &Plugin{Library: *lib, Name: r.Source.Plugin}, nil
394395
}
395396

397+
func (r *ResourceBlueprint) ValidateServiceAccess(serviceSubtype, resourceName, resourceType string) error {
398+
// All service types are allowed if useable_by is empty (backward compatible)
399+
if len(r.UsableBy) == 0 {
400+
return nil
401+
}
402+
403+
if !slices.Contains(r.UsableBy, serviceSubtype) {
404+
return fmt.Errorf(
405+
"%s '%s' cannot be accessed by service subtype '%s': this %s blueprint is only usable by service types: %v",
406+
resourceType, resourceName, serviceSubtype, resourceType, r.UsableBy,
407+
)
408+
}
409+
410+
return nil
411+
}
412+
396413
type IdentitiesBlueprint struct {
397414
Identities []ResourceBlueprint `json:"identities" yaml:"identities"`
398415
}

engines/terraform/platform_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package terraform
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestResourceBlueprint_ValidateServiceAccess(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
usableBy []string
13+
serviceSubtype string
14+
resourceName string
15+
resourceType string
16+
expectError bool
17+
errorContains string
18+
}{
19+
{
20+
name: "empty usable_by allows all service types",
21+
usableBy: []string{},
22+
serviceSubtype: "lambda",
23+
resourceName: "my_bucket",
24+
resourceType: "bucket",
25+
expectError: false,
26+
},
27+
{
28+
name: "nil usable_by allows all service types",
29+
usableBy: nil,
30+
serviceSubtype: "fargate",
31+
resourceName: "my_database",
32+
resourceType: "database",
33+
expectError: false,
34+
},
35+
{
36+
name: "service subtype in allowed list succeeds",
37+
usableBy: []string{"web", "worker", "api"},
38+
serviceSubtype: "web",
39+
resourceName: "users_db",
40+
resourceType: "database",
41+
expectError: false,
42+
},
43+
{
44+
name: "service subtype not in allowed list fails",
45+
usableBy: []string{"web", "worker"},
46+
serviceSubtype: "lambda",
47+
resourceName: "users_db",
48+
resourceType: "database",
49+
expectError: true,
50+
errorContains: "database 'users_db' cannot be accessed by service subtype 'lambda'",
51+
},
52+
{
53+
name: "single allowed service type succeeds",
54+
usableBy: []string{"fargate"},
55+
serviceSubtype: "fargate",
56+
resourceName: "assets",
57+
resourceType: "bucket",
58+
expectError: false,
59+
},
60+
{
61+
name: "single allowed service type with different subtype fails",
62+
usableBy: []string{"fargate"},
63+
serviceSubtype: "lambda",
64+
resourceName: "assets",
65+
resourceType: "bucket",
66+
expectError: true,
67+
errorContains: "bucket 'assets' cannot be accessed by service subtype 'lambda'",
68+
},
69+
{
70+
name: "error message includes allowed types list",
71+
usableBy: []string{"web", "worker", "cron"},
72+
serviceSubtype: "lambda",
73+
resourceName: "cache_db",
74+
resourceType: "database",
75+
expectError: true,
76+
errorContains: "only usable by service types: [web worker cron]",
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
blueprint := &ResourceBlueprint{
83+
UsableBy: tt.usableBy,
84+
}
85+
86+
err := blueprint.ValidateServiceAccess(tt.serviceSubtype, tt.resourceName, tt.resourceType)
87+
88+
if tt.expectError {
89+
assert.Error(t, err)
90+
if tt.errorContains != "" {
91+
assert.Contains(t, err.Error(), tt.errorContains)
92+
}
93+
} else {
94+
assert.NoError(t, err)
95+
}
96+
})
97+
}
98+
}

engines/terraform/resource_handler.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ func (td *TerraformDeployment) processBucketResources(appSpec *app_spec_schema.A
152152
return nil, fmt.Errorf("could not give access to bucket %s: service %s not found", intentName, serviceName)
153153
}
154154

155+
// Validate that this service subtype is allowed to access the bucket
156+
serviceIntent, ok := appSpec.ServiceIntents[serviceName]
157+
if !ok {
158+
return nil, fmt.Errorf("could not validate access to bucket %s: service %s not found in application spec", intentName, serviceName)
159+
}
160+
if err := spec.ValidateServiceAccess(serviceIntent.GetSubType(), intentName, "bucket"); err != nil {
161+
return nil, err
162+
}
163+
155164
servicesInput[serviceName] = map[string]interface{}{
156165
"actions": jsii.Strings(expandedActions...),
157166
"identities": idMap,
@@ -237,6 +246,15 @@ func (td *TerraformDeployment) processDatabaseResources(appSpec *app_spec_schema
237246
return nil, fmt.Errorf("could not give access to database %s: service %s not found", intentName, serviceName)
238247
}
239248

249+
// Validate that this service subtype is allowed to access the database
250+
serviceIntent, ok := appSpec.ServiceIntents[serviceName]
251+
if !ok {
252+
return nil, fmt.Errorf("could not validate access to database %s: service %s not found in application spec", intentName, serviceName)
253+
}
254+
if err := spec.ValidateServiceAccess(serviceIntent.GetSubType(), intentName, "database"); err != nil {
255+
return nil, err
256+
}
257+
240258
servicesInput[serviceName] = map[string]interface{}{
241259
"actions": jsii.Strings(expandedActions...),
242260
"identities": idMap,

0 commit comments

Comments
 (0)