Skip to content

Commit 95cb411

Browse files
change from source service to target service having access key
1 parent e6da56a commit 95cb411

File tree

4 files changed

+184
-37
lines changed

4 files changed

+184
-37
lines changed

cli/internal/simulation/simulation.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,24 @@ func (s *SimulationServer) startServices(output io.Writer) (<-chan service.Servi
277277
servicePorts := make(map[string]netx.ReservedPort)
278278
eventChans := []<-chan service.ServiceEvent{}
279279

280-
for serviceName, serviceIntent := range serviceIntents {
280+
// Helper function for lazy port allocation
281+
getOrAllocatePort := func(serviceName string) (netx.ReservedPort, error) {
282+
if port, exists := servicePorts[serviceName]; exists {
283+
return port, nil
284+
}
281285
port, err := netx.GetNextPort()
282286
if err != nil {
283-
return nil, err
287+
return 0, err
284288
}
285289
servicePorts[serviceName] = port
290+
return port, nil
291+
}
292+
293+
for serviceName, serviceIntent := range serviceIntents {
294+
port, err := getOrAllocatePort(serviceName)
295+
if err != nil {
296+
return nil, err
297+
}
286298

287299
// Clone the service intent to add database connection strings
288300
intentCopy := *serviceIntent
@@ -306,10 +318,15 @@ func (s *SimulationServer) startServices(output io.Writer) (<-chan service.Servi
306318
}
307319
}
308320

309-
// Inject URLs for services this service depends on
310-
if access, ok := intentCopy.GetAccess(); ok {
311-
for targetServiceName := range access {
312-
if targetPort, exists := servicePorts[targetServiceName]; exists {
321+
// Inject URLs for services this service can access
322+
// Check which services grant access to this service and lazily allocate their ports
323+
for targetServiceName, targetServiceIntent := range serviceIntents {
324+
if access, ok := targetServiceIntent.GetAccess(); ok {
325+
if _, hasAccess := access[serviceName]; hasAccess {
326+
targetPort, err := getOrAllocatePort(targetServiceName)
327+
if err != nil {
328+
return nil, err
329+
}
313330
envVarName := fmt.Sprintf("%s_URL", strings.ToUpper(targetServiceName))
314331
intentCopy.Env[envVarName] = fmt.Sprintf("http://localhost:%d", targetPort)
315332
}

cli/pkg/schema/schema_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,3 +597,129 @@ func TestApplication_IsValid_WithSubtypes(t *testing.T) {
597597
violations = app.IsValid(WithRequireSubtypes())
598598
assert.Len(t, violations, 0, "Expected no violations with RequireSubtypes option, got: %v", violations)
599599
}
600+
601+
func TestApplication_IsValid_ServiceAccess_Valid(t *testing.T) {
602+
app := &Application{
603+
Name: "test-app",
604+
Target: "team/platform@1",
605+
ServiceIntents: map[string]*ServiceIntent{
606+
"api": {
607+
Container: Container{
608+
Docker: &Docker{Dockerfile: "Dockerfile"},
609+
},
610+
},
611+
"user_service": {
612+
Container: Container{
613+
Docker: &Docker{Dockerfile: "Dockerfile"},
614+
},
615+
Access: map[string][]string{
616+
"api": {"invoke"}, // user_service grants api access to invoke it
617+
},
618+
},
619+
},
620+
}
621+
622+
violations := app.IsValid()
623+
assert.Len(t, violations, 0, "Expected no violations for valid service access, got: %v", violations)
624+
}
625+
626+
func TestApplication_IsValid_ServiceAccess_InvalidAction(t *testing.T) {
627+
app := &Application{
628+
Name: "test-app",
629+
Target: "team/platform@1",
630+
ServiceIntents: map[string]*ServiceIntent{
631+
"api": {
632+
Container: Container{
633+
Docker: &Docker{Dockerfile: "Dockerfile"},
634+
},
635+
},
636+
"user_service": {
637+
Container: Container{
638+
Docker: &Docker{Dockerfile: "Dockerfile"},
639+
},
640+
Access: map[string][]string{
641+
"api": {"read", "write"}, // Invalid actions for service
642+
},
643+
},
644+
},
645+
}
646+
647+
violations := app.IsValid()
648+
assert.NotEmpty(t, violations, "Expected violations for invalid service actions")
649+
650+
errString := FormatValidationErrors(GetSchemaValidationErrors(violations))
651+
assert.Contains(t, errString, "user_service:")
652+
assert.Contains(t, errString, "Invalid service actions: read, write")
653+
assert.Contains(t, errString, "Valid actions are: invoke")
654+
}
655+
656+
func TestApplication_IsValid_ServiceAccess_NonExistentAccessor(t *testing.T) {
657+
app := &Application{
658+
Name: "test-app",
659+
Target: "team/platform@1",
660+
ServiceIntents: map[string]*ServiceIntent{
661+
"user_service": {
662+
Container: Container{
663+
Docker: &Docker{Dockerfile: "Dockerfile"},
664+
},
665+
Access: map[string][]string{
666+
"non_existent": {"invoke"}, // Accessor service doesn't exist
667+
},
668+
},
669+
},
670+
}
671+
672+
violations := app.IsValid()
673+
assert.NotEmpty(t, violations, "Expected violations for non-existent accessor service")
674+
675+
errString := FormatValidationErrors(GetSchemaValidationErrors(violations))
676+
assert.Contains(t, errString, "user_service:")
677+
assert.Contains(t, errString, "grants access to non-existent service non_existent")
678+
}
679+
680+
func TestApplicationFromYaml_ServiceAccess_TargetBased(t *testing.T) {
681+
yaml := `
682+
name: test-app
683+
description: A test application with service access
684+
target: team/platform@1
685+
services:
686+
api:
687+
container:
688+
docker:
689+
dockerfile: Dockerfile
690+
user_service:
691+
container:
692+
docker:
693+
dockerfile: Dockerfile
694+
access:
695+
api:
696+
- invoke
697+
payment_service:
698+
container:
699+
docker:
700+
dockerfile: Dockerfile
701+
access:
702+
api:
703+
- invoke
704+
user_service:
705+
- invoke
706+
`
707+
708+
app, result, err := ApplicationFromYaml(yaml)
709+
assert.NoError(t, err)
710+
assert.True(t, result.Valid(), "Expected valid result, got validation errors: %v", result.Errors())
711+
712+
violations := app.IsValid()
713+
assert.Len(t, violations, 0, "Expected no violations for valid target-based service access, got: %v", violations)
714+
715+
// Verify the access configuration
716+
userService := app.ServiceIntents["user_service"]
717+
assert.NotNil(t, userService.Access)
718+
assert.Contains(t, userService.Access, "api")
719+
assert.Equal(t, []string{"invoke"}, userService.Access["api"])
720+
721+
paymentService := app.ServiceIntents["payment_service"]
722+
assert.NotNil(t, paymentService.Access)
723+
assert.Contains(t, paymentService.Access, "api")
724+
assert.Contains(t, paymentService.Access, "user_service")
725+
}

cli/pkg/schema/validate.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,19 @@ func (a *Application) checkAccessPermissions() []gojsonschema.ResultError {
101101

102102
for name, intent := range a.ServiceIntents {
103103
if access, ok := intent.GetAccess(); ok {
104-
for targetServiceName, actions := range access {
104+
for accessorServiceName, actions := range access {
105105
// Validate actions
106106
invalidActions, ok := ValidateActions(actions, Service)
107107
if !ok {
108-
key := fmt.Sprintf("services.%s.access.%s", name, targetServiceName)
108+
key := fmt.Sprintf("services.%s.access.%s", name, accessorServiceName)
109109
err := fmt.Sprintf("Invalid service %s: %s. Valid actions are: %s", pluralise("action", len(invalidActions)), strings.Join(invalidActions, ", "), strings.Join(GetValidActions(Service), ", "))
110110
violations = append(violations, newValidationError(key, err))
111111
}
112112

113-
// Validate that the target service exists
114-
if _, exists := a.ServiceIntents[targetServiceName]; !exists {
115-
key := fmt.Sprintf("services.%s.access.%s", name, targetServiceName)
116-
err := fmt.Sprintf("Service %s requires access to non-existent service %s", name, targetServiceName)
113+
// Validate that the accessor service exists
114+
if _, exists := a.ServiceIntents[accessorServiceName]; !exists {
115+
key := fmt.Sprintf("services.%s.access.%s", name, accessorServiceName)
116+
err := fmt.Sprintf("Service %s grants access to non-existent service %s", name, accessorServiceName)
117117
violations = append(violations, newValidationError(key, err))
118118
}
119119
}

engines/terraform/resource_handler.go

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -96,25 +96,23 @@ func (td *TerraformDeployment) processServiceIdentities(appSpec *app_spec_schema
9696
func (td *TerraformDeployment) collectServiceAccessors(appSpec *app_spec_schema.Application) map[string]map[string]interface{} {
9797
serviceAccessors := make(map[string]map[string]interface{})
9898

99-
for targetServiceName := range appSpec.ServiceIntents {
100-
accessors := map[string]interface{}{}
101-
102-
for sourceServiceName, sourceServiceIntent := range appSpec.ServiceIntents {
103-
if access, ok := sourceServiceIntent.GetAccess(); ok {
104-
if actions, needsAccess := access[targetServiceName]; needsAccess {
105-
expandedActions := app_spec_schema.ExpandActions(actions, app_spec_schema.Service)
106-
idMap := td.serviceIdentities[sourceServiceName]
107-
108-
accessors[sourceServiceName] = map[string]interface{}{
109-
"actions": jsii.Strings(expandedActions...),
110-
"identities": idMap,
111-
}
99+
for targetServiceName, targetServiceIntent := range appSpec.ServiceIntents {
100+
if access, ok := targetServiceIntent.GetAccess(); ok {
101+
accessors := map[string]interface{}{}
102+
103+
for accessorServiceName, actions := range access {
104+
expandedActions := app_spec_schema.ExpandActions(actions, app_spec_schema.Service)
105+
idMap := td.serviceIdentities[accessorServiceName]
106+
107+
accessors[accessorServiceName] = map[string]interface{}{
108+
"actions": jsii.Strings(expandedActions...),
109+
"identities": idMap,
112110
}
113111
}
114-
}
115112

116-
if len(accessors) > 0 {
117-
serviceAccessors[targetServiceName] = accessors
113+
if len(accessors) > 0 {
114+
serviceAccessors[targetServiceName] = accessors
115+
}
118116
}
119117
}
120118

@@ -158,19 +156,25 @@ func (td *TerraformDeployment) processServiceResources(appSpec *app_spec_schema.
158156
}
159157

160158
// Add service to service urls
161-
for intentName, serviceIntent := range appSpec.ServiceIntents {
162-
if access, ok := serviceIntent.GetAccess(); ok {
163-
for targetServiceName := range access {
164-
if targetResource, ok := td.terraformResources[targetServiceName]; ok {
165-
envVarName := fmt.Sprintf("%s_URL", strings.ToUpper(targetServiceName))
166-
httpEndpoint := targetResource.Get(jsii.String("suga.http_endpoint"))
167-
serviceEnvs[intentName] = append(serviceEnvs[intentName], map[string]interface{}{
168-
envVarName: httpEndpoint,
169-
})
159+
// Build reverse index: for each accessor service, find which targets grant it access
160+
for accessorServiceName := range appSpec.ServiceIntents {
161+
for targetServiceName, targetServiceIntent := range appSpec.ServiceIntents {
162+
if access, ok := targetServiceIntent.GetAccess(); ok {
163+
if _, hasAccess := access[accessorServiceName]; hasAccess {
164+
if targetResource, ok := td.terraformResources[targetServiceName]; ok {
165+
envVarName := fmt.Sprintf("%s_URL", strings.ToUpper(targetServiceName))
166+
httpEndpoint := targetResource.Get(jsii.String("suga.http_endpoint"))
167+
serviceEnvs[accessorServiceName] = append(serviceEnvs[accessorServiceName], map[string]interface{}{
168+
envVarName: httpEndpoint,
169+
})
170+
}
170171
}
171172
}
172173
}
174+
}
173175

176+
// Merge environment variables for all services
177+
for intentName := range appSpec.ServiceIntents {
174178
sugaVar := serviceInputs[intentName]
175179
mergedEnv := serviceEnvs[intentName]
176180
allEnv := append(mergedEnv, originalEnvs[intentName])

0 commit comments

Comments
 (0)