Skip to content
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
4 changes: 4 additions & 0 deletions cli/internal/devserver/filesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func (fs *NitricFileSync) getApplicationFileContents() (*schema.Application, []b
return nil, contents, fmt.Errorf("Errors parsing application from yaml: %v", schemaResult.Errors())
}

if appSpecErrors := application.IsValid(); len(appSpecErrors) > 0 {
return nil, contents, fmt.Errorf("application is not valid")
}

return application, contents, nil
}

Expand Down
54 changes: 54 additions & 0 deletions cli/pkg/schema/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package schema

import "github.com/samber/lo"

type ResourceType string

const (
Database ResourceType = "database"
Bucket ResourceType = "bucket"
)

const allAccess = "all"

var validActions = map[ResourceType][]string{
Database: {"query", "mutate"},
Bucket: {"read", "write", "delete"},
}

func GetValidActions(resourceType ResourceType) []string {
actions := validActions[resourceType]
if actions == nil {
return []string{}
}

return append(actions, allAccess)
}

// ExpandActions expands 'all' in an actions array whilst maintaining deduplication
func ExpandActions(actions []string, resourceType ResourceType) []string {
expanded := []string{}

for _, action := range actions {
if action == allAccess {
expanded = append(expanded, validActions[resourceType]...)
} else {
expanded = append(expanded, action)
}
}

return lo.Uniq(expanded)
}

// ValidateActions ensures that all the actions are valid
func ValidateActions(actions []string, resourceType ResourceType) ([]string, bool) {
invalidActions := []string{}

for _, action := range actions {
if !lo.Contains(GetValidActions(resourceType), action) {
invalidActions = append(invalidActions, action)
}
}

return invalidActions, len(invalidActions) == 0
}
68 changes: 53 additions & 15 deletions cli/pkg/schema/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,59 @@ import (
"fmt"
"regexp"
"slices"
"strings"

"github.com/xeipuuv/gojsonschema"
)

func (a *Application) checkNoEnvVarCollisions() []gojsonschema.ResultError {
violations := []gojsonschema.ResultError{}
envVarMap := map[string]string{}

for name, intent := range a.DatabaseIntents {
if existingName, ok := envVarMap[intent.EnvVarKey]; ok {
violations = append(violations, newValidationError(fmt.Sprintf("databases.%s", name), fmt.Sprintf("env var %s is already in use by %s", intent.EnvVarKey, existingName)))
continue
}
envVarMap[intent.EnvVarKey] = name
}

return violations
}

// Perform additional validation checks on the application
func (a *Application) IsValid() []gojsonschema.ResultError {
// Check the names of all resources are unique
violations := a.checkNoNameConflicts()
violations = append(violations, a.checkNoReservedNames()...)
violations = append(violations, a.checkSnakeCaseNames()...)
violations = append(violations, a.checkNoEnvVarCollisions()...)
violations = append(violations, a.checkAccessPermissions()...)

return violations
}

func (a *Application) checkAccessPermissions() []gojsonschema.ResultError {
violations := []gojsonschema.ResultError{}

for name, intent := range a.BucketIntents {
for serviceName, actions := range intent.Access {
invalidActions, ok := ValidateActions(actions, Bucket)
if !ok {
key := fmt.Sprintf("buckets.%s.access.%s", name, serviceName)
err := fmt.Sprintf("Invalid bucket %s: %s. Valid actions are: %s", pluralise("action", len(invalidActions)), strings.Join(invalidActions, ", "), strings.Join(GetValidActions(Bucket), ", "))
violations = append(violations, newValidationError(key, err))
}
}
}

for name, intent := range a.DatabaseIntents {
for serviceName, actions := range intent.Access {
invalidActions, ok := ValidateActions(actions, Database)
if !ok {
key := fmt.Sprintf("databases.%s.access.%s", name, serviceName)
err := fmt.Sprintf("Invalid database %s: %s. Valid actions are: %s", pluralise("action", len(invalidActions)), strings.Join(invalidActions, ", "), strings.Join(GetValidActions(Database), ", "))
violations = append(violations, newValidationError(key, err))
}
}
}

return violations
}

func pluralise(word string, count int) string {
output := word
if count > 1 {
output += "s"
}
return output
}

func (a *Application) checkNoNameConflicts() []gojsonschema.ResultError {
resourceNames := map[string]string{}
violations := []gojsonschema.ResultError{}
Expand Down Expand Up @@ -157,3 +180,18 @@ func (a *Application) checkSnakeCaseNames() []gojsonschema.ResultError {

return violations
}

func (a *Application) checkNoEnvVarCollisions() []gojsonschema.ResultError {
violations := []gojsonschema.ResultError{}
envVarMap := map[string]string{}

for name, intent := range a.DatabaseIntents {
if existingName, ok := envVarMap[intent.EnvVarKey]; ok {
violations = append(violations, newValidationError(fmt.Sprintf("databases.%s", name), fmt.Sprintf("env var %s is already in use by %s", intent.EnvVarKey, existingName)))
continue
}
envVarMap[intent.EnvVarKey] = name
}

return violations
}
8 changes: 6 additions & 2 deletions engines/terraform/resource_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,15 @@ func (td *TerraformDeployment) processBucketResources(appSpec *app_spec_schema.A
servicesInput := map[string]any{}
if access, ok := bucketIntent.GetAccess(); ok {
for serviceName, actions := range access {
expandedActions := app_spec_schema.ExpandActions(actions, app_spec_schema.Bucket)

idMap, ok := td.serviceIdentities[serviceName]
if !ok {
return nil, fmt.Errorf("could not give access to bucket %s: service %s not found", intentName, serviceName)
}

servicesInput[serviceName] = map[string]interface{}{
"actions": jsii.Strings(actions...),
"actions": jsii.Strings(expandedActions...),
"identities": idMap,
}
}
Expand Down Expand Up @@ -205,13 +207,15 @@ func (td *TerraformDeployment) processDatabaseResources(appSpec *app_spec_schema
servicesInput := map[string]any{}
if access, ok := databaseIntent.GetAccess(); ok {
for serviceName, actions := range access {
expandedActions := app_spec_schema.ExpandActions(actions, app_spec_schema.Database)

idMap, ok := td.serviceIdentities[serviceName]
if !ok {
return nil, fmt.Errorf("could not give access to database %s: service %s not found", intentName, serviceName)
}

servicesInput[serviceName] = map[string]interface{}{
"actions": jsii.Strings(actions...),
"actions": jsii.Strings(expandedActions...),
"identities": idMap,
}
}
Expand Down
Loading