From 8c042fec598469e5c53337709cb79926a3215148 Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Fri, 5 Apr 2024 14:59:39 +1100 Subject: [PATCH] refactor: remove runtime binaries from main deployment package paths * remove runtime binaries from main deployment package paths. * make GCP deployment state externally readable. * make aws state fields accessible. * make stack details public * make azure resource state public. * enable resource extensions using pulumi inputs. * use pulumi resource wrapper in Pre. * give access to utility clients * fix map type in aws deploy * add common config * add missing license file --------- Co-authored-by: Ryan Cartwright --- cloud/aws/Makefile | 16 +-- cloud/aws/cmd/deploy/main.go | 3 +- cloud/aws/deploy/api.go | 20 ++-- cloud/aws/deploy/bucket.go | 12 +- cloud/aws/deploy/deploy.go | 134 +++++++++------------- cloud/aws/deploy/httpproxy.go | 10 +- cloud/aws/deploy/keyvalue.go | 4 +- cloud/aws/deploy/policy.go | 14 +-- cloud/aws/deploy/queue.go | 4 +- cloud/aws/deploy/runtime/runtime.go | 28 +++++ cloud/aws/deploy/schedule.go | 4 +- cloud/aws/deploy/secret.go | 10 +- cloud/aws/deploy/service.go | 47 ++++---- cloud/aws/deploy/topic.go | 18 +-- cloud/aws/deploy/websocket.go | 12 +- cloud/azure/Makefile | 16 +-- cloud/azure/cmd/deploy/main.go | 3 +- cloud/azure/deploy/api.go | 20 ++-- cloud/azure/deploy/bucket.go | 12 +- cloud/azure/deploy/containerenv.go | 34 +++--- cloud/azure/deploy/deploy.go | 128 +++++++++------------ cloud/azure/deploy/httpproxy.go | 20 ++-- cloud/azure/deploy/keyvalue.go | 6 +- cloud/azure/deploy/policy.go | 44 ++++---- cloud/azure/deploy/queue.go | 6 +- cloud/azure/deploy/runtime/runtime.go | 28 +++++ cloud/azure/deploy/schedule.go | 6 +- cloud/azure/deploy/service.go | 64 ++++++----- cloud/azure/deploy/topic.go | 12 +- cloud/common/deploy/attributes.go | 8 +- cloud/common/deploy/provider/provider.go | 5 +- cloud/common/deploy/provider/pulumi.go | 39 +++++-- cloud/common/deploy/provider/runtime.go | 18 +++ cloud/common/deploy/pulumix/resource.go | 57 ++++++++++ cloud/gcp/Makefile | 16 +-- cloud/gcp/cmd/deploy/main.go | 3 +- cloud/gcp/deploy/api.go | 16 +-- cloud/gcp/deploy/bucket.go | 12 +- cloud/gcp/deploy/deploy.go | 136 +++++++++-------------- cloud/gcp/deploy/httpproxy.go | 10 +- cloud/gcp/deploy/policy.go | 14 +-- cloud/gcp/deploy/queue.go | 8 +- cloud/gcp/deploy/runtime/runtime.go | 28 +++++ cloud/gcp/deploy/schedule.go | 4 +- cloud/gcp/deploy/secret.go | 6 +- cloud/gcp/deploy/service.go | 39 +++---- cloud/gcp/deploy/topic.go | 8 +- 47 files changed, 631 insertions(+), 531 deletions(-) create mode 100644 cloud/aws/deploy/runtime/runtime.go create mode 100644 cloud/azure/deploy/runtime/runtime.go create mode 100644 cloud/common/deploy/provider/runtime.go create mode 100644 cloud/common/deploy/pulumix/resource.go create mode 100644 cloud/gcp/deploy/runtime/runtime.go diff --git a/cloud/aws/Makefile b/cloud/aws/Makefile index cc2123f6a..21599061b 100644 --- a/cloud/aws/Makefile +++ b/cloud/aws/Makefile @@ -8,9 +8,9 @@ GOLANGCI_LINT ?= GOLANGCI_LINT_CACHE=$(GOLANGCI_LINT_CACHE) go run github.com/go binaries: deploybin sec: - @touch deploy/runtime-aws + @touch deploy/runtime/runtime-aws @go run github.com/securego/gosec/v2/cmd/gosec@latest -exclude-dir=tools ./... - @rm deploy/runtime-aws + @rm deploy/runtime/runtime-aws # build runtime binary directly into the deploy director so it can be embedded directly into the deployment engine binary # We only build a linux amd64 binary here to be packaged for cloud runtimes with docker @@ -20,12 +20,12 @@ runtimebin: @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/runtime-aws -ldflags="-s -w -extldflags=-static" ./cmd/runtime predeploybin: runtimebin - @cp bin/runtime-aws deploy/runtime-aws + @cp bin/runtime-aws deploy/runtime/runtime-aws deploybin: predeploybin @echo Building AWS Deployment Server @CGO_ENABLED=0 go build -o bin/deploy-aws -ldflags="-s -w -extldflags=-static" -ldflags="-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=ignore" ./cmd/deploy - @rm deploy/runtime-aws + @rm deploy/runtime/runtime-aws .PHONY: install install: deploybin @@ -42,15 +42,15 @@ sourcefiles := $(shell find . -type f -name "*.go" -o -name "*.dockerfile") fmt: @go run github.com/google/addlicense -c "Nitric Technologies Pty Ltd." -y "2021" $(sourcefiles) - @touch deploy/runtime-aws + @touch deploy/runtime/runtime-aws $(GOLANGCI_LINT) run --fix - @rm deploy/runtime-aws + @rm deploy/runtime/runtime-aws lint: @go run github.com/google/addlicense -check -c "Nitric Technologies Pty Ltd." -y "2021" $(sourcefiles) - @touch deploy/runtime-aws + @touch deploy/runtime/runtime-aws $(GOLANGCI_LINT) run - @rm deploy/runtime-aws + @rm deploy/runtime/runtime-aws test: generate-mocks @echo Running unit tests diff --git a/cloud/aws/cmd/deploy/main.go b/cloud/aws/cmd/deploy/main.go index 1c01162fa..91bea088e 100644 --- a/cloud/aws/cmd/deploy/main.go +++ b/cloud/aws/cmd/deploy/main.go @@ -18,6 +18,7 @@ package main import ( "github.com/nitrictech/nitric/cloud/aws/deploy" + "github.com/nitrictech/nitric/cloud/aws/deploy/runtime" "github.com/nitrictech/nitric/cloud/common/deploy/provider" ) @@ -25,7 +26,7 @@ import ( func main() { awsStack := deploy.NewNitricAwsProvider() - providerServer := provider.NewPulumiProviderServer(awsStack) + providerServer := provider.NewPulumiProviderServer(awsStack, runtime.NitricAwsRuntime) providerServer.Start() } diff --git a/cloud/aws/deploy/api.go b/cloud/aws/deploy/api.go index 2414ec7f7..b3d700733 100644 --- a/cloud/aws/deploy/api.go +++ b/cloud/aws/deploy/api.go @@ -72,7 +72,7 @@ type nameArnPair struct { func (a *NitricAwsPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Api) error { opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - nameArnPairs := make([]interface{}, 0, len(a.lambdas)) + nameArnPairs := make([]interface{}, 0, len(a.Lambdas)) if config.GetOpenapi() == "" { return fmt.Errorf("aws provider can only deploy OpenAPI specs") @@ -114,7 +114,7 @@ func (a *NitricAwsPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc if v, ok := op.Extensions["x-nitric-target"]; ok { if targetMap, isMap := v.(map[string]any); isMap { serviceName := targetMap["name"].(string) - nitricServiceTargets[serviceName] = a.lambdas[serviceName] + nitricServiceTargets[serviceName] = a.Lambdas[serviceName] } } } @@ -133,7 +133,7 @@ func (a *NitricAwsPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc })) } - apiGatewayTags := tags.Tags(a.stackId, name, resources.API) + apiGatewayTags := tags.Tags(a.StackId, name, resources.API) doc := pulumi.All(nameArnPairs...).ApplyT(func(pairs []interface{}) (string, error) { naps := make(map[string]string) @@ -175,7 +175,7 @@ func (a *NitricAwsPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc return string(b), nil }).(pulumi.StringOutput) - a.apis[name], err = apigatewayv2.NewApi(ctx, name, &apigatewayv2.ApiArgs{ + a.Apis[name], err = apigatewayv2.NewApi(ctx, name, &apigatewayv2.ApiArgs{ Body: doc, // Name fixed to title in the spec, if these mismatch the name will change on the second deployment. Name: pulumi.String(openapiDoc.Info.Title), @@ -190,7 +190,7 @@ func (a *NitricAwsPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc apiStage, err := apigatewayv2.NewStage(ctx, name+"DefaultStage", &apigatewayv2.StageArgs{ AutoDeploy: pulumi.BoolPtr(true), Name: pulumi.String("$default"), - ApiId: a.apis[name].ID(), + ApiId: a.Apis[name].ID(), // Tags: pulumi.ToStringMap(common.Tags(args.StackID, name+"DefaultStage", resources.API)), }, opts...) if err != nil { @@ -203,23 +203,23 @@ func (a *NitricAwsPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc Function: fun.Name, Action: pulumi.String("lambda:InvokeFunction"), Principal: pulumi.String("apigateway.amazonaws.com"), - SourceArn: pulumi.Sprintf("%s/*/*/*", a.apis[name].ExecutionArn), + SourceArn: pulumi.Sprintf("%s/*/*/*", a.Apis[name].ExecutionArn), }, opts...) if err != nil { return err } } - endPoint := a.apis[name].ApiEndpoint.ApplyT(func(ep string) string { + endPoint := a.Apis[name].ApiEndpoint.ApplyT(func(ep string) string { return ep }).(pulumi.StringInput) - if a.config.Apis[name] != nil { + if a.AwsConfig.Apis[name] != nil { // For each specified domain name - for _, domainName := range a.config.Apis[name].Domains { + for _, domainName := range a.AwsConfig.Apis[name].Domains { _, err := newDomainName(ctx, name, domainNameArgs{ domainName: domainName, - api: a.apis[name], + api: a.Apis[name], stage: apiStage, }) if err != nil { diff --git a/cloud/aws/deploy/bucket.go b/cloud/aws/deploy/bucket.go index ea1330820..6dbabf62c 100644 --- a/cloud/aws/deploy/bucket.go +++ b/cloud/aws/deploy/bucket.go @@ -108,28 +108,28 @@ func (a *NitricAwsPulumiProvider) Bucket(ctx *pulumi.Context, parent pulumi.Reso opts := []pulumi.ResourceOption{pulumi.Parent(parent)} bucket, err := s3.NewBucket(ctx, name, &s3.BucketArgs{ - Tags: pulumi.ToStringMap(common.Tags(a.stackId, name, resources.Bucket)), + Tags: pulumi.ToStringMap(common.Tags(a.StackId, name, resources.Bucket)), }, opts...) if err != nil { return err } - a.buckets[name] = bucket + a.Buckets[name] = bucket if len(config.Listeners) > 0 { notificationName := fmt.Sprintf("notification-%s", name) notification, err := createNotification(ctx, notificationName, &S3NotificationArgs{ - StackID: a.stackId, - Location: a.region, + StackID: a.StackId, + Location: a.Region, Bucket: bucket, - Lambdas: a.lambdas, + Lambdas: a.Lambdas, Listeners: config.Listeners, }, opts...) if err != nil { return err } - a.bucketNotifications[name] = notification + a.BucketNotifications[name] = notification } return nil diff --git a/cloud/aws/deploy/deploy.go b/cloud/aws/deploy/deploy.go index 64687ac3b..35d305fdc 100644 --- a/cloud/aws/deploy/deploy.go +++ b/cloud/aws/deploy/deploy.go @@ -30,8 +30,8 @@ import ( "github.com/nitrictech/nitric/cloud/aws/common" "github.com/nitrictech/nitric/cloud/common/deploy" "github.com/nitrictech/nitric/cloud/common/deploy/provider" + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" "github.com/nitrictech/nitric/cloud/common/deploy/tags" - deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/apigatewayv2" "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/dynamodb" "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/ecr" @@ -49,48 +49,37 @@ import ( ) type NitricAwsPulumiProvider struct { - stackId string - projectName string - stackName string - - fullStackName string - - config *AwsConfig - region string - - ecrAuthToken *ecr.GetAuthorizationTokenResult - lambdas map[string]*lambda.Function - lambdaRoles map[string]*iam.Role - httpProxies map[string]*apigatewayv2.Api - apis map[string]*apigatewayv2.Api - secrets map[string]*secretsmanager.Secret - buckets map[string]*s3.Bucket - bucketNotifications map[string]*s3.BucketNotification - topics map[string]*topic - queues map[string]*sqs.Queue - websockets map[string]*apigatewayv2.Api - keyValueStores map[string]*dynamodb.Table + *deploy.CommonStackDetails + + StackId string + AwsConfig *AwsConfig + + EcrAuthToken *ecr.GetAuthorizationTokenResult + Lambdas map[string]*lambda.Function + LambdaRoles map[string]*iam.Role + HttpProxies map[string]*apigatewayv2.Api + Apis map[string]*apigatewayv2.Api + Secrets map[string]*secretsmanager.Secret + Buckets map[string]*s3.Bucket + BucketNotifications map[string]*s3.BucketNotification + Topics map[string]*topic + Queues map[string]*sqs.Queue + Websockets map[string]*apigatewayv2.Api + KeyValueStores map[string]*dynamodb.Table provider.NitricDefaultOrder - resourceTaggingClient *resourcegroupstaggingapi.ResourceGroupsTaggingAPI - lambdaClient lambdaiface.LambdaAPI + ResourceTaggingClient *resourcegroupstaggingapi.ResourceGroupsTaggingAPI + LambdaClient lambdaiface.LambdaAPI } -// Embeds the runtime directly into the deploytime binary -// This way the versions will always match as they're always built and versioned together (as a single artifact) -// This should also help with docker build speeds as the runtime has already been "downloaded" -// -//go:embed runtime-aws -var runtime []byte - var _ provider.NitricPulumiProvider = (*NitricAwsPulumiProvider)(nil) const pulumiAwsVersion = "6.6.0" func (a *NitricAwsPulumiProvider) Config() (auto.ConfigMap, error) { return auto.ConfigMap{ - "aws:region": auto.ConfigValue{Value: a.region}, + "aws:region": auto.ConfigValue{Value: a.Region}, "aws:version": auto.ConfigValue{Value: pulumiAwsVersion}, "docker:version": auto.ConfigValue{Value: deploy.PulumiDockerVersion}, }, nil @@ -99,43 +88,20 @@ func (a *NitricAwsPulumiProvider) Config() (auto.ConfigMap, error) { func (a *NitricAwsPulumiProvider) Init(attributes map[string]interface{}) error { var err error - region, ok := attributes["region"].(string) - if !ok { - return fmt.Errorf("Missing region attribute") + a.CommonStackDetails, err = deploy.CommonStackDetailsFromAttributes(attributes) + if err != nil { + return status.Errorf(codes.InvalidArgument, err.Error()) } - a.region = region - - a.config, err = ConfigFromAttributes(attributes) + a.AwsConfig, err = ConfigFromAttributes(attributes) if err != nil { return status.Errorf(codes.InvalidArgument, "Bad stack configuration: %s", err) } - var isString bool - - iProject, hasProject := attributes["project"] - a.projectName, isString = iProject.(string) - if !hasProject || !isString || a.projectName == "" { - // need a valid project name - return fmt.Errorf("project is not set or invalid") - } - - iStack, hasStack := attributes["stack"] - a.stackName, isString = iStack.(string) - if !hasStack || !isString || a.stackName == "" { - // need a valid stack name - return fmt.Errorf("stack is not set or invalid") - } - - // Backwards compatible stack name - // The existing providers in the CLI - // Use the combined project and stack name - a.fullStackName = fmt.Sprintf("%s-%s", a.projectName, a.stackName) - return nil } -func (a *NitricAwsPulumiProvider) Pre(ctx *pulumi.Context, resources []*deploymentspb.Resource) error { +func (a *NitricAwsPulumiProvider) Pre(ctx *pulumi.Context, resources []*pulumix.NitricPulumiResource[any]) error { // make our random stackId stackRandId, err := random.NewRandomString(ctx, fmt.Sprintf("%s-stack-name", ctx.Stack()), &random.RandomStringArgs{ Special: pulumi.Bool(false), @@ -154,30 +120,30 @@ func (a *NitricAwsPulumiProvider) Pre(ctx *pulumi.Context, resources []*deployme return id }) - a.stackId = <-stackIdChan + a.StackId = <-stackIdChan sess := session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, })) - a.resourceTaggingClient = resourcegroupstaggingapi.New(sess) + a.ResourceTaggingClient = resourcegroupstaggingapi.New(sess) - a.lambdaClient = lambdaClient.New(sess, &aws.Config{Region: aws.String(a.region)}) + a.LambdaClient = lambdaClient.New(sess, &aws.Config{Region: aws.String(a.Region)}) - a.ecrAuthToken, err = ecr.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenArgs{}) + a.EcrAuthToken, err = ecr.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenArgs{}) if err != nil { return err } // Create AWS Resource groups with our tags _, err = resourcegroups.NewGroup(ctx, "stack-resource-group", &resourcegroups.GroupArgs{ - Name: pulumi.String(a.fullStackName), - Description: pulumi.Sprintf("All deployed resources for the %s nitric stack", a.fullStackName), + Name: pulumi.String(a.FullStackName), + Description: pulumi.Sprintf("All deployed resources for the %s nitric stack", a.FullStackName), ResourceQuery: &resourcegroups.GroupResourceQueryArgs{ Query: pulumi.Sprintf(`{ "ResourceTypeFilters":["AWS::AllSupported"], "TagFilters":[{"Key":"%s"}] - }`, tags.GetResourceNameKey(a.stackId)), + }`, tags.GetResourceNameKey(a.StackId)), }, }) @@ -192,31 +158,31 @@ func (a *NitricAwsPulumiProvider) Result(ctx *pulumi.Context) (pulumi.StringOutp outputs := []interface{}{} // Add APIs outputs - if len(a.apis) > 0 { + if len(a.Apis) > 0 { outputs = append(outputs, pulumi.Sprintf("API Endpoints:\n──────────────")) - for apiName, api := range a.apis { + for apiName, api := range a.Apis { outputs = append(outputs, pulumi.Sprintf("%s: %s", apiName, api.ApiEndpoint)) } } // Add HTTP Proxy outputs - if len(a.httpProxies) > 0 { + if len(a.HttpProxies) > 0 { if len(outputs) > 0 { outputs = append(outputs, "\n") } outputs = append(outputs, pulumi.Sprintf("HTTP Proxies:\n──────────────")) - for proxyName, proxy := range a.httpProxies { + for proxyName, proxy := range a.HttpProxies { outputs = append(outputs, pulumi.Sprintf("%s: %s", proxyName, proxy.ApiEndpoint)) } } // Add Websocket outputs - if len(a.websockets) > 0 { + if len(a.Websockets) > 0 { if len(outputs) > 0 { outputs = append(outputs, "\n") } outputs = append(outputs, pulumi.Sprintf("Websockets:\n──────────────")) - for wsName, ws := range a.websockets { + for wsName, ws := range a.Websockets { outputs = append(outputs, pulumi.Sprintf("%s: %s/%s", wsName, ws.ApiEndpoint, common.DefaultWsStageName)) } } @@ -239,16 +205,16 @@ func (a *NitricAwsPulumiProvider) Result(ctx *pulumi.Context) (pulumi.StringOutp func NewNitricAwsProvider() *NitricAwsPulumiProvider { return &NitricAwsPulumiProvider{ - lambdas: make(map[string]*lambda.Function), - lambdaRoles: make(map[string]*iam.Role), - apis: make(map[string]*apigatewayv2.Api), - httpProxies: make(map[string]*apigatewayv2.Api), - secrets: make(map[string]*secretsmanager.Secret), - buckets: make(map[string]*s3.Bucket), - bucketNotifications: make(map[string]*s3.BucketNotification), - websockets: make(map[string]*apigatewayv2.Api), - topics: make(map[string]*topic), - queues: make(map[string]*sqs.Queue), - keyValueStores: make(map[string]*dynamodb.Table), + Lambdas: make(map[string]*lambda.Function), + LambdaRoles: make(map[string]*iam.Role), + Apis: make(map[string]*apigatewayv2.Api), + HttpProxies: make(map[string]*apigatewayv2.Api), + Secrets: make(map[string]*secretsmanager.Secret), + Buckets: make(map[string]*s3.Bucket), + BucketNotifications: make(map[string]*s3.BucketNotification), + Websockets: make(map[string]*apigatewayv2.Api), + Topics: make(map[string]*topic), + Queues: make(map[string]*sqs.Queue), + KeyValueStores: make(map[string]*dynamodb.Table), } } diff --git a/cloud/aws/deploy/httpproxy.go b/cloud/aws/deploy/httpproxy.go index 6b239780a..f5cff60f4 100644 --- a/cloud/aws/deploy/httpproxy.go +++ b/cloud/aws/deploy/httpproxy.go @@ -30,9 +30,9 @@ func (a *NitricAwsPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resour var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - targetLambda := a.lambdas[http.Target.GetService()] + targetLambda := a.Lambdas[http.Target.GetService()] - httpProxyGatewayTags := common.Tags(a.stackId, name, resources.HttpProxy) + httpProxyGatewayTags := common.Tags(a.StackId, name, resources.HttpProxy) doc := targetLambda.InvokeArn.ApplyT(func(invokeArn string) (string, error) { spec := newApiSpec(name, invokeArn, httpProxyGatewayTags) @@ -45,7 +45,7 @@ func (a *NitricAwsPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resour return string(b), nil }).(pulumi.StringOutput) - a.httpProxies[name], err = apigatewayv2.NewApi(ctx, name, &apigatewayv2.ApiArgs{ + a.HttpProxies[name], err = apigatewayv2.NewApi(ctx, name, &apigatewayv2.ApiArgs{ Body: doc, ProtocolType: pulumi.String("HTTP"), Tags: pulumi.ToStringMap(httpProxyGatewayTags), @@ -58,7 +58,7 @@ func (a *NitricAwsPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resour _, err = apigatewayv2.NewStage(ctx, name+"DefaultStage", &apigatewayv2.StageArgs{ AutoDeploy: pulumi.BoolPtr(true), Name: pulumi.String("$default"), - ApiId: a.httpProxies[name].ID(), + ApiId: a.HttpProxies[name].ID(), }, opts...) if err != nil { return err @@ -69,7 +69,7 @@ func (a *NitricAwsPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resour Function: targetLambda.Name, Action: pulumi.String("lambda:InvokeFunction"), Principal: pulumi.String("apigateway.amazonaws.com"), - SourceArn: pulumi.Sprintf("%s/*/*/*", a.httpProxies[name].ExecutionArn), + SourceArn: pulumi.Sprintf("%s/*/*/*", a.HttpProxies[name].ExecutionArn), }, opts...) if err != nil { return err diff --git a/cloud/aws/deploy/keyvalue.go b/cloud/aws/deploy/keyvalue.go index fb74c2945..d820f569b 100644 --- a/cloud/aws/deploy/keyvalue.go +++ b/cloud/aws/deploy/keyvalue.go @@ -41,7 +41,7 @@ func (n *NitricAwsPulumiProvider) KeyValueStore(ctx *pulumi.Context, parent pulu var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - n.keyValueStores[name], err = dynamodb.NewTable(ctx, name, &dynamodb.TableArgs{ + n.KeyValueStores[name], err = dynamodb.NewTable(ctx, name, &dynamodb.TableArgs{ Attributes: dynamodb.TableAttributeArray{ &dynamodb.TableAttributeArgs{ Name: pulumi.String("_pk"), @@ -55,7 +55,7 @@ func (n *NitricAwsPulumiProvider) KeyValueStore(ctx *pulumi.Context, parent pulu HashKey: pulumi.String("_pk"), RangeKey: pulumi.String("_sk"), BillingMode: pulumi.String("PAY_PER_REQUEST"), - Tags: pulumi.ToStringMap(tags.Tags(n.stackId, name, resources.Collection)), + Tags: pulumi.ToStringMap(tags.Tags(n.StackId, name, resources.Collection)), }, opts...) return err diff --git a/cloud/aws/deploy/policy.go b/cloud/aws/deploy/policy.go index 36ad1df0c..73d311e2c 100644 --- a/cloud/aws/deploy/policy.go +++ b/cloud/aws/deploy/policy.go @@ -107,27 +107,27 @@ func actionsToAwsActions(actions []resourcespb.Action) []string { func (a *NitricAwsPulumiProvider) arnForResource(resource *deploymentspb.Resource) ([]interface{}, error) { switch resource.Id.Type { case resourcespb.ResourceType_Bucket: - if b, ok := a.buckets[resource.Id.Name]; ok { + if b, ok := a.Buckets[resource.Id.Name]; ok { return []interface{}{b.Arn, pulumi.Sprintf("%s/*", b.Arn)}, nil } case resourcespb.ResourceType_Topic: - if t, ok := a.topics[resource.Id.Name]; ok { + if t, ok := a.Topics[resource.Id.Name]; ok { return []interface{}{t.sns.Arn, t.sfn.Arn}, nil } case resourcespb.ResourceType_Queue: - if q, ok := a.queues[resource.Id.Name]; ok { + if q, ok := a.Queues[resource.Id.Name]; ok { return []interface{}{q.Arn}, nil } case resourcespb.ResourceType_KeyValueStore: - if c, ok := a.keyValueStores[resource.Id.Name]; ok { + if c, ok := a.KeyValueStores[resource.Id.Name]; ok { return []interface{}{c.Arn}, nil } case resourcespb.ResourceType_Secret: - if s, ok := a.secrets[resource.Id.Name]; ok { + if s, ok := a.Secrets[resource.Id.Name]; ok { return []interface{}{s.Arn}, nil } case resourcespb.ResourceType_Websocket: - if w, ok := a.websockets[resource.Id.Name]; ok { + if w, ok := a.Websockets[resource.Id.Name]; ok { return []interface{}{pulumi.Sprintf("%s/*", w.ExecutionArn)}, nil } default: @@ -141,7 +141,7 @@ func (a *NitricAwsPulumiProvider) arnForResource(resource *deploymentspb.Resourc func (a *NitricAwsPulumiProvider) roleForPrincipal(resource *deploymentspb.Resource) (*iam.Role, error) { switch resource.Id.Type { case resourcespb.ResourceType_Service: - if f, ok := a.lambdaRoles[resource.Id.Name]; ok { + if f, ok := a.LambdaRoles[resource.Id.Name]; ok { return f, nil } default: diff --git a/cloud/aws/deploy/queue.go b/cloud/aws/deploy/queue.go index f75354f3c..79c4777d3 100644 --- a/cloud/aws/deploy/queue.go +++ b/cloud/aws/deploy/queue.go @@ -29,13 +29,13 @@ func (a *NitricAwsPulumiProvider) Queue(ctx *pulumi.Context, parent pulumi.Resou opts := []pulumi.ResourceOption{pulumi.Parent(parent)} queue, err := sqs.NewQueue(ctx, name, &sqs.QueueArgs{ - Tags: pulumi.ToStringMap(common.Tags(a.stackId, name, resources.Queue)), + Tags: pulumi.ToStringMap(common.Tags(a.StackId, name, resources.Queue)), }, opts...) if err != nil { return err } - a.queues[name] = queue + a.Queues[name] = queue return nil } diff --git a/cloud/aws/deploy/runtime/runtime.go b/cloud/aws/deploy/runtime/runtime.go new file mode 100644 index 000000000..0cfedbb13 --- /dev/null +++ b/cloud/aws/deploy/runtime/runtime.go @@ -0,0 +1,28 @@ +// Copyright 2021 Nitric Technologies Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtime + +import _ "embed" + +// Embeds the runtime directly into the deploytime binary +// This way the versions will always match as they're always built and versioned together (as a single artifact) +// This should also help with docker build speeds as the runtime has already been "downloaded" +// +//go:embed runtime-aws +var runtime []byte + +func NitricAwsRuntime() []byte { + return runtime +} diff --git a/cloud/aws/deploy/schedule.go b/cloud/aws/deploy/schedule.go index fc557d323..23eab794a 100644 --- a/cloud/aws/deploy/schedule.go +++ b/cloud/aws/deploy/schedule.go @@ -60,7 +60,7 @@ func (a *NitricAwsPulumiProvider) Schedule(ctx *pulumi.Context, parent pulumi.Re return err } - target, ok := a.lambdas[config.Target.GetService()] + target, ok := a.Lambdas[config.Target.GetService()] if !ok { return fmt.Errorf("unable to find target lambda: %s", config.Target.GetService()) } @@ -76,7 +76,7 @@ func (a *NitricAwsPulumiProvider) Schedule(ctx *pulumi.Context, parent pulumi.Re // Create a new eventbridge schedule _, err = scheduler.NewSchedule(ctx, name, &scheduler.ScheduleArgs{ ScheduleExpression: pulumi.String(awsScheduleExpression), - ScheduleExpressionTimezone: pulumi.String(a.config.ScheduleTimezone), + ScheduleExpressionTimezone: pulumi.String(a.AwsConfig.ScheduleTimezone), FlexibleTimeWindow: &scheduler.ScheduleFlexibleTimeWindowArgs{ Mode: pulumi.String("OFF"), }, diff --git a/cloud/aws/deploy/secret.go b/cloud/aws/deploy/secret.go index 65a023fb5..563a7a8f9 100644 --- a/cloud/aws/deploy/secret.go +++ b/cloud/aws/deploy/secret.go @@ -71,18 +71,18 @@ func createSecret(ctx *pulumi.Context, name string, tags map[string]string) (*se // Secret - Implements deployments of Nitric Secrets using AWS Secrets Manager func (a *NitricAwsPulumiProvider) Secret(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Secret) error { - awsTags := common.Tags(a.stackId, name, resources.Secret) + awsTags := common.Tags(a.StackId, name, resources.Secret) var err error var secret *secretsmanager.Secret importArn := "" - if a.config.Import.Secrets != nil { - importArn = a.config.Import.Secrets[name] + if a.AwsConfig.Import.Secrets != nil { + importArn = a.AwsConfig.Import.Secrets[name] } if importArn != "" { - secret, err = tagSecret(ctx, name, importArn, awsTags, a.resourceTaggingClient) + secret, err = tagSecret(ctx, name, importArn, awsTags, a.ResourceTaggingClient) } else { secret, err = createSecret(ctx, name, awsTags) } @@ -91,7 +91,7 @@ func (a *NitricAwsPulumiProvider) Secret(ctx *pulumi.Context, parent pulumi.Reso return err } - a.secrets[name] = secret + a.Secrets[name] = secret return nil } diff --git a/cloud/aws/deploy/service.go b/cloud/aws/deploy/service.go index c2e7aef72..6ccb596a4 100644 --- a/cloud/aws/deploy/service.go +++ b/cloud/aws/deploy/service.go @@ -24,10 +24,11 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/lambda" "github.com/nitrictech/nitric/cloud/common/deploy/image" + "github.com/nitrictech/nitric/cloud/common/deploy/provider" + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" "github.com/nitrictech/nitric/cloud/common/deploy/resources" "github.com/nitrictech/nitric/cloud/common/deploy/tags" "github.com/nitrictech/nitric/cloud/common/deploy/telemetry" - deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/ecr" "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam" awslambda "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/lambda" @@ -41,7 +42,7 @@ func createEcrRepository(ctx *pulumi.Context, parent pulumi.Resource, stackId st }, pulumi.Parent(parent)) } -func createImage(ctx *pulumi.Context, parent pulumi.Resource, name string, authToken *ecr.GetAuthorizationTokenResult, repo *ecr.Repository, typeConfig *AwsConfigItem, config *deploymentspb.Service) (*image.Image, error) { +func createImage(ctx *pulumi.Context, parent pulumi.Resource, name string, authToken *ecr.GetAuthorizationTokenResult, repo *ecr.Repository, typeConfig *AwsConfigItem, config *pulumix.NitricPulumiServiceConfig, runtime provider.RuntimeProvider) (*image.Image, error) { if config.GetImage() == nil { return nil, fmt.Errorf("aws provider can only deploy service with an image source") } @@ -60,7 +61,7 @@ func createImage(ctx *pulumi.Context, parent pulumi.Resource, name string, authT Server: pulumi.String(authToken.ProxyEndpoint), Username: pulumi.String(authToken.UserName), Password: pulumi.String(authToken.Password), - Runtime: runtime, + Runtime: runtime(), Telemetry: &telemetry.TelemetryConfigArgs{ TraceSampling: typeConfig.Telemetry, TraceName: "awsxray", @@ -70,11 +71,11 @@ func createImage(ctx *pulumi.Context, parent pulumi.Resource, name string, authT }, pulumi.Parent(parent), pulumi.DependsOn([]pulumi.Resource{repo})) } -func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Service) error { +func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Resource, name string, config *pulumix.NitricPulumiServiceConfig, runtime provider.RuntimeProvider) error { opts := []pulumi.ResourceOption{pulumi.Parent(parent)} // Create the ECR repository to push the image to - repo, err := createEcrRepository(ctx, parent, a.stackId, name) + repo, err := createEcrRepository(ctx, parent, a.StackId, name) if err != nil { return err } @@ -91,12 +92,12 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res config.Type = "default" } - typeConfig, hasConfig := a.config.Config[config.Type] + typeConfig, hasConfig := a.AwsConfig.Config[config.Type] if !hasConfig { - return fmt.Errorf("could not find config for type %s in %+v", config.Type, a.config) + return fmt.Errorf("could not find config for type %s in %+v", config.Type, a.AwsConfig) } - image, err := createImage(ctx, parent, name, a.ecrAuthToken, repo, typeConfig, config) + image, err := createImage(ctx, parent, name, a.EcrAuthToken, repo, typeConfig, config, runtime) if err != nil { return err } @@ -120,9 +121,9 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res return err } - a.lambdaRoles[name], err = iam.NewRole(ctx, name+"LambdaRole", &iam.RoleArgs{ + a.LambdaRoles[name], err = iam.NewRole(ctx, name+"LambdaRole", &iam.RoleArgs{ AssumeRolePolicy: pulumi.String(tmpJSON), - Tags: pulumi.ToStringMap(tags.Tags(a.stackId, name+"LambdaRole", resources.Service)), + Tags: pulumi.ToStringMap(tags.Tags(a.StackId, name+"LambdaRole", resources.Service)), }, opts...) if err != nil { return err @@ -130,7 +131,7 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res _, err = iam.NewRolePolicyAttachment(ctx, name+"LambdaBasicExecution", &iam.RolePolicyAttachmentArgs{ PolicyArn: iam.ManagedPolicyAWSLambdaBasicExecutionRole, - Role: a.lambdaRoles[name].ID(), + Role: a.LambdaRoles[name].ID(), }, opts...) if err != nil { return err @@ -185,7 +186,7 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res } _, err = iam.NewRolePolicy(ctx, name+"ListAccess", &iam.RolePolicyArgs{ - Role: a.lambdaRoles[name].ID(), + Role: a.LambdaRoles[name].ID(), Policy: pulumi.String(tmpJSON), }, opts...) if err != nil { @@ -196,12 +197,12 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res envVars := pulumi.StringMap{ "NITRIC_ENVIRONMENT": pulumi.String("cloud"), - "NITRIC_STACK_ID": pulumi.String(a.stackId), + "NITRIC_STACK_ID": pulumi.String(a.StackId), "MIN_WORKERS": pulumi.String(fmt.Sprint(config.Workers)), "NITRIC_HTTP_PROXY_PORT": pulumi.String(fmt.Sprint(3000)), } - for k, v := range config.Env { - envVars[k] = pulumi.String(v) + for k, v := range config.Env() { + envVars[k] = v } var vpcConfig *awslambda.FunctionVpcConfigArgs = nil @@ -214,14 +215,14 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res // Create a policy attachment for VPC access _, err = iam.NewRolePolicyAttachment(ctx, name+"VPCAccessExecutionRole", &iam.RolePolicyAttachmentArgs{ PolicyArn: iam.ManagedPolicyAWSLambdaVPCAccessExecutionRole, - Role: a.lambdaRoles[name].ID(), + Role: a.LambdaRoles[name].ID(), }, opts...) if err != nil { return err } } - a.lambdas[name], err = awslambda.NewFunction(ctx, name, &awslambda.FunctionArgs{ + a.Lambdas[name], err = awslambda.NewFunction(ctx, name, &awslambda.FunctionArgs{ // Use repository to generate the URI, instead of the image, using the image results in errors when the same project is torn down and redeployed. // This appears to be because the local image ends up with multiple repositories and the wrong one is selected. // XXX: Reverted change for the above comment as lambda image deployments were not rolling forward (under tag latest) @@ -230,8 +231,8 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res MemorySize: pulumi.IntPtr(typeConfig.Lambda.Memory), Timeout: pulumi.IntPtr(typeConfig.Lambda.Timeout), PackageType: pulumi.String("Image"), - Role: a.lambdaRoles[name].Arn, - Tags: pulumi.ToStringMap(tags.Tags(a.stackId, name, resources.Service)), + Role: a.LambdaRoles[name].Arn, + Tags: pulumi.ToStringMap(tags.Tags(a.StackId, name, resources.Service)), VpcConfig: vpcConfig, Environment: awslambda.FunctionEnvironmentArgs{Variables: envVars}, // since we only rely on the repository to determine the ImageUri, the image must be added as a dependency to avoid a race. @@ -242,9 +243,9 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res if typeConfig.Lambda.ProvisionedConcurreny > 0 { _, err := awslambda.NewProvisionedConcurrencyConfig(ctx, name, &awslambda.ProvisionedConcurrencyConfigArgs{ - FunctionName: a.lambdas[name].Arn, + FunctionName: a.Lambdas[name].Arn, ProvisionedConcurrentExecutions: pulumi.Int(typeConfig.Lambda.ProvisionedConcurreny), - Qualifier: a.lambdas[name].Name, + Qualifier: a.Lambdas[name].Name, }) if err != nil { return err @@ -252,13 +253,13 @@ func (a *NitricAwsPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res } // ensure that the lambda was deployed successfully - _ = a.lambdas[name].Arn.ApplyT(func(arn string) (bool, error) { + _ = a.Lambdas[name].Arn.ApplyT(func(arn string) (bool, error) { payload, _ := json.Marshal(map[string]interface{}{ "x-nitric-healthcheck": true, }) err := retry.Do(func() error { - _, err := a.lambdaClient.Invoke(&lambda.InvokeInput{ + _, err := a.LambdaClient.Invoke(&lambda.InvokeInput{ FunctionName: aws.String(arn), Payload: payload, }) diff --git a/cloud/aws/deploy/topic.go b/cloud/aws/deploy/topic.go index e04ba18a3..d93e720fe 100644 --- a/cloud/aws/deploy/topic.go +++ b/cloud/aws/deploy/topic.go @@ -67,13 +67,13 @@ func createSubscription(ctx *pulumi.Context, parent pulumi.Resource, name string func (a *NitricAwsPulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Topic) error { var err error - a.topics[name] = &topic{} + a.Topics[name] = &topic{} opts := []pulumi.ResourceOption{pulumi.Parent(parent)} // create the SNS topic - a.topics[name].sns, err = sns.NewTopic(ctx, name, &sns.TopicArgs{ - Tags: pulumi.ToStringMap(common.Tags(a.stackId, name, resources.Topic)), + a.Topics[name].sns, err = sns.NewTopic(ctx, name, &sns.TopicArgs{ + Tags: pulumi.ToStringMap(common.Tags(a.StackId, name, resources.Topic)), }, opts...) if err != nil { return err @@ -104,7 +104,7 @@ func (a *NitricAwsPulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Resou return errors.WithMessage(err, "topic delay controller role") } - policy := a.topics[name].sns.Arn.ApplyT(func(arn string) (string, error) { + policy := a.Topics[name].sns.Arn.ApplyT(func(arn string) (string, error) { rp, err := json.Marshal(map[string]interface{}{ "Version": "2012-10-17", "Statement": []map[string]interface{}{ @@ -129,7 +129,7 @@ func (a *NitricAwsPulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Resou return errors.WithMessage(err, "topic delay controller role policy") } - sfnDef := a.topics[name].sns.Arn.ApplyT(func(arn string) (string, error) { + sfnDef := a.Topics[name].sns.Arn.ApplyT(func(arn string) (string, error) { def, err := json.Marshal(map[string]interface{}{ "Comment": "", "StartAt": "Wait", @@ -156,10 +156,10 @@ func (a *NitricAwsPulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Resou // Deploy a delay manager using AWS step functions // This will enable runtime delaying of event - a.topics[name].sfn, err = sfn.NewStateMachine(ctx, name, &sfn.StateMachineArgs{ + a.Topics[name].sfn, err = sfn.NewStateMachine(ctx, name, &sfn.StateMachineArgs{ RoleArn: sfnRole.Arn, // Apply the same name as the topic to the state machine - Tags: pulumi.ToStringMap(common.Tags(a.stackId, name, resources.Topic)), + Tags: pulumi.ToStringMap(common.Tags(a.StackId, name, resources.Topic)), Definition: sfnDef, }, opts...) if err != nil { @@ -167,12 +167,12 @@ func (a *NitricAwsPulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Resou } for _, sub := range config.Subscriptions { - targetLambda, ok := a.lambdas[sub.GetService()] + targetLambda, ok := a.Lambdas[sub.GetService()] if !ok { return fmt.Errorf("unable to find lambda %s for subscription", sub.GetService()) } - err := createSubscription(ctx, parent, name, a.topics[name].sns, targetLambda) + err := createSubscription(ctx, parent, name, a.Topics[name].sns, targetLambda) if err != nil { return err } diff --git a/cloud/aws/deploy/websocket.go b/cloud/aws/deploy/websocket.go index 8bb7c2ba2..6ef7a49c1 100644 --- a/cloud/aws/deploy/websocket.go +++ b/cloud/aws/deploy/websocket.go @@ -29,15 +29,15 @@ import ( ) func (a *NitricAwsPulumiProvider) Websocket(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Websocket) error { - defaultTarget := a.lambdas[config.MessageTarget.GetService()] - connectTarget := a.lambdas[config.ConnectTarget.GetService()] - disconnectTarget := a.lambdas[config.DisconnectTarget.GetService()] + defaultTarget := a.Lambdas[config.MessageTarget.GetService()] + connectTarget := a.Lambdas[config.ConnectTarget.GetService()] + disconnectTarget := a.Lambdas[config.DisconnectTarget.GetService()] opts := []pulumi.ResourceOption{pulumi.Parent(parent)} websocketApi, err := apigatewayv2.NewApi(ctx, name, &apigatewayv2.ApiArgs{ ProtocolType: pulumi.String("WEBSOCKET"), - Tags: pulumi.ToStringMap(tags.Tags(a.stackId, name, resources.Websocket)), + Tags: pulumi.ToStringMap(tags.Tags(a.StackId, name, resources.Websocket)), // This isn't used (see AWS docs), but it is required. Instead we use the $default route RouteSelectionExpression: pulumi.String("$request.body.action"), }, opts...) @@ -45,7 +45,7 @@ func (a *NitricAwsPulumiProvider) Websocket(ctx *pulumi.Context, parent pulumi.R return err } - a.websockets[name] = websocketApi + a.Websockets[name] = websocketApi // Create the API integrations integrationDefault, err := apigatewayv2.NewIntegration(ctx, fmt.Sprintf("%s-default-integration", name), &apigatewayv2.IntegrationArgs{ @@ -148,7 +148,7 @@ func (a *NitricAwsPulumiProvider) Websocket(ctx *pulumi.Context, parent pulumi.R AutoDeploy: pulumi.BoolPtr(true), Name: pulumi.String(common.DefaultWsStageName), ApiId: websocketApi.ID(), - Tags: pulumi.ToStringMap(tags.Tags(a.stackId, name+"DefaultStage", resources.Websocket)), + Tags: pulumi.ToStringMap(tags.Tags(a.StackId, name+"DefaultStage", resources.Websocket)), }, opts...) if err != nil { return err diff --git a/cloud/azure/Makefile b/cloud/azure/Makefile index b46542596..a2e6d87af 100644 --- a/cloud/azure/Makefile +++ b/cloud/azure/Makefile @@ -8,21 +8,21 @@ GOLANGCI_LINT ?= GOLANGCI_LINT_CACHE=$(GOLANGCI_LINT_CACHE) go run github.com/go binaries: deploybin sec: - @touch deploy/runtime-azure + @touch deploy/runtime/runtime-azure @go run github.com/securego/gosec/v2/cmd/gosec@latest -exclude-dir=tools ./... - @rm deploy/runtime-azure + @rm deploy/runtime/runtime-azure runtimebin: @echo Building Azure Runtime Server @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/runtime-azure -ldflags="-s -w -extldflags=-static" ./cmd/runtime predeploybin: runtimebin - @cp bin/runtime-azure deploy/runtime-azure + @cp bin/runtime-azure deploy/runtime/runtime-azure deploybin: predeploybin @echo Building Azure Deployment Server @CGO_ENABLED=0 go build -o bin/deploy-azure -ldflags="-s -w -extldflags=-static" -ldflags="-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=ignore" ./cmd/deploy - @rm deploy/runtime-azure + @rm deploy/runtime/runtime-azure .PHONY: install install: deploybin @@ -39,15 +39,15 @@ sourcefiles := $(shell find . -type f -name "*.go" -o -name "*.dockerfile") fmt: @go run github.com/google/addlicense -c "Nitric Technologies Pty Ltd." -y "2021" $(sourcefiles) - @touch deploy/runtime-azure + @touch deploy/runtime/runtime-azure $(GOLANGCI_LINT) run --fix - @rm deploy/runtime-azure + @rm deploy/runtime/runtime-azure lint: @go run github.com/google/addlicense -check -c "Nitric Technologies Pty Ltd." -y "2021" $(sourcefiles) - @touch deploy/runtime-azure + @touch deploy/runtime/runtime-azure $(GOLANGCI_LINT) run - @rm deploy/runtime-azure + @rm deploy/runtime/runtime-azure test: generate-mocks @echo Running unit tests diff --git a/cloud/azure/cmd/deploy/main.go b/cloud/azure/cmd/deploy/main.go index 61976ffe6..ad5d18069 100644 --- a/cloud/azure/cmd/deploy/main.go +++ b/cloud/azure/cmd/deploy/main.go @@ -18,6 +18,7 @@ package main import ( "github.com/nitrictech/nitric/cloud/azure/deploy" + "github.com/nitrictech/nitric/cloud/azure/deploy/runtime" "github.com/nitrictech/nitric/cloud/common/deploy/provider" ) @@ -25,7 +26,7 @@ import ( func main() { azureStack := deploy.NewNitricAzurePulumiProvider() - providerServer := provider.NewPulumiProviderServer(azureStack) + providerServer := provider.NewPulumiProviderServer(azureStack, runtime.NitricAzureRuntime) providerServer.Start() } diff --git a/cloud/azure/deploy/api.go b/cloud/azure/deploy/api.go index 5ef10965d..26eeb7afb 100644 --- a/cloud/azure/deploy/api.go +++ b/cloud/azure/deploy/api.go @@ -87,16 +87,16 @@ func (p *NitricAzurePulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resou return nil } - managedIdentities := p.containerEnv.ManagedUser.ID().ToStringOutput().ApplyT(func(id string) apimanagement.UserIdentityPropertiesMapOutput { + managedIdentities := p.ContainerEnv.ManagedUser.ID().ToStringOutput().ApplyT(func(id string) apimanagement.UserIdentityPropertiesMapOutput { return apimanagement.UserIdentityPropertiesMap{ id: nil, }.ToUserIdentityPropertiesMapOutput() }).(apimanagement.UserIdentityPropertiesMapOutput) mgmtService, err := apimanagement.NewApiManagementService(ctx, ResourceName(ctx, name, ApiManagementServiceRT), &apimanagement.ApiManagementServiceArgs{ - ResourceGroupName: p.resourceGroup.Name, - PublisherEmail: pulumi.String(p.config.AdminEmail), - PublisherName: pulumi.String(p.config.Org), + ResourceGroupName: p.ResourceGroup.Name, + PublisherEmail: pulumi.String(p.AzureConfig.AdminEmail), + PublisherName: pulumi.String(p.AzureConfig.Org), Sku: apimanagement.ApiManagementServiceSkuPropertiesArgs{ Name: pulumi.String("Consumption"), Capacity: pulumi.Int(0), @@ -105,7 +105,7 @@ func (p *NitricAzurePulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resou Type: pulumi.String("UserAssigned"), UserAssignedIdentities: managedIdentities, }, - Tags: pulumi.ToStringMap(common.Tags(p.stackId, name, resources.API)), + Tags: pulumi.ToStringMap(common.Tags(p.StackId, name, resources.API)), }, opts...) if err != nil { return err @@ -127,7 +127,7 @@ func (p *NitricAzurePulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resou ApiId: pulumi.String(name), Format: pulumi.String("openapi+json"), Path: pulumi.String("/"), - ResourceGroupName: p.resourceGroup.Name, + ResourceGroupName: p.ResourceGroup.Name, SubscriptionRequired: pulumi.Bool(false), ServiceName: mgmtService.Name, // No need to transform the original spec, the mapping occurs as part of the operation policies below @@ -137,7 +137,7 @@ func (p *NitricAzurePulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resou return err } - p.apis[name] = ApiResources{ + p.Apis[name] = ApiResources{ Api: api, ApiManagementService: mgmtService, } @@ -201,7 +201,7 @@ func (p *NitricAzurePulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resou return fmt.Errorf("operation: %s has malformed x-nitric-target annotation", op.OperationID) } - app, ok := p.containerApps[target] + app, ok := p.ContainerApps[target] if !ok { return fmt.Errorf("Unable to find container app for service: %s", target) } @@ -216,13 +216,13 @@ func (p *NitricAzurePulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resou _ = ctx.Log.Info("op policy "+op.OperationID+" , name "+name, &pulumi.LogArgs{Ephemeral: true}) _, err = apimanagement.NewApiOperationPolicy(ctx, ResourceName(ctx, name+"-"+op.OperationID, ApiOperationPolicyRT), &apimanagement.ApiOperationPolicyArgs{ - ResourceGroupName: p.resourceGroup.Name, + ResourceGroupName: p.ResourceGroup.Name, ApiId: apiId, ServiceName: mgmtService.Name, OperationId: pulumi.String(op.OperationID), PolicyId: pulumi.String("policy"), Format: pulumi.String("xml"), - Value: pulumi.Sprintf(policyTemplate, pulumi.Sprintf("%s%s%s", app.App.LatestRevisionFqdn, "/x-nitric-api/", name), jwtTemplateString, p.containerEnv.ManagedUser.ClientId, p.containerEnv.ManagedUser.ClientId), + Value: pulumi.Sprintf(policyTemplate, pulumi.Sprintf("%s%s%s", app.App.LatestRevisionFqdn, "/x-nitric-api/", name), jwtTemplateString, p.ContainerEnv.ManagedUser.ClientId, p.ContainerEnv.ManagedUser.ClientId), }, pulumi.Parent(api)) if err != nil { return errors.WithMessage(err, "NewApiOperationPolicy "+op.OperationID) diff --git a/cloud/azure/deploy/bucket.go b/cloud/azure/deploy/bucket.go index 49d6cdb81..c0ca5fa79 100644 --- a/cloud/azure/deploy/bucket.go +++ b/cloud/azure/deploy/bucket.go @@ -42,12 +42,12 @@ func removeWildcard(prefixFilter string) string { } func (p *NitricAzurePulumiProvider) newAzureBucketNotification(ctx *pulumi.Context, parent pulumi.Resource, bucketName string, config *deploymentspb.BucketListener) error { - target, ok := p.containerApps[config.GetService()] + target, ok := p.ContainerApps[config.GetService()] if !ok { return fmt.Errorf("target container app %s not found", config.GetService()) } - bucket, ok := p.buckets[bucketName] + bucket, ok := p.Buckets[bucketName] if !ok { return fmt.Errorf("target bucket %s not found", bucketName) } @@ -60,7 +60,7 @@ func (p *NitricAzurePulumiProvider) newAzureBucketNotification(ctx *pulumi.Conte } _, err = pulumiEventgrid.NewEventSubscription(ctx, ResourceName(ctx, bucketName+target.Name, EventSubscriptionRT), &pulumiEventgrid.EventSubscriptionArgs{ - Scope: p.storageAccount.ID(), + Scope: p.StorageAccount.ID(), WebhookEndpoint: pulumiEventgrid.EventSubscriptionWebhookEndpointArgs{ Url: pulumi.Sprintf("%s/%s/x-nitric-notification/bucket/%s", hostUrl, target.EventToken, bucketName), // Only send one event per batch to avoid a single failure nacking multiple events. @@ -88,9 +88,9 @@ func (p *NitricAzurePulumiProvider) Bucket(ctx *pulumi.Context, parent pulumi.Re var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - p.buckets[name], err = storage.NewBlobContainer(ctx, ResourceName(ctx, name, StorageContainerRT), &storage.BlobContainerArgs{ - ResourceGroupName: p.resourceGroup.Name, - AccountName: p.storageAccount.Name, + p.Buckets[name], err = storage.NewBlobContainer(ctx, ResourceName(ctx, name, StorageContainerRT), &storage.BlobContainerArgs{ + ResourceGroupName: p.ResourceGroup.Name, + AccountName: p.StorageAccount.Name, }, opts...) if err != nil { return err diff --git a/cloud/azure/deploy/containerenv.go b/cloud/azure/deploy/containerenv.go index 650f03017..467895d0b 100644 --- a/cloud/azure/deploy/containerenv.go +++ b/cloud/azure/deploy/containerenv.go @@ -64,8 +64,8 @@ func (p *NitricAzurePulumiProvider) newContainerEnv(ctx *pulumi.Context, name st } res.ManagedUser, err = managedidentity.NewUserAssignedIdentity(ctx, "managed-identity", &managedidentity.UserAssignedIdentityArgs{ - Location: p.resourceGroup.Location, - ResourceGroupName: p.resourceGroup.Name, + Location: p.ResourceGroup.Location, + ResourceGroupName: p.ResourceGroup.Name, ResourceName: pulumi.String("managed-identity"), }, pulumi.Parent(res)) if err != nil { @@ -74,25 +74,25 @@ func (p *NitricAzurePulumiProvider) newContainerEnv(ctx *pulumi.Context, name st env := app.EnvironmentVarArray{} - if p.storageAccount != nil { + if p.StorageAccount != nil { env = append(env, app.EnvironmentVarArgs{ Name: pulumi.String("AZURE_STORAGE_ACCOUNT_NAME"), - Value: p.storageAccount.Name, + Value: p.StorageAccount.Name, }) env = append(env, app.EnvironmentVarArgs{ Name: pulumi.String("AZURE_STORAGE_ACCOUNT_BLOB_ENDPOINT"), - Value: p.storageAccount.PrimaryEndpoints.Blob(), + Value: p.StorageAccount.PrimaryEndpoints.Blob(), }) env = append(env, app.EnvironmentVarArgs{ Name: pulumi.String("AZURE_STORAGE_ACCOUNT_QUEUE_ENDPOINT"), - Value: p.storageAccount.PrimaryEndpoints.Queue(), + Value: p.StorageAccount.PrimaryEndpoints.Queue(), }) } - if p.keyVault != nil { + if p.KeyVault != nil { env = append(env, app.EnvironmentVarArgs{ Name: pulumi.String("KVAULT_NAME"), - Value: p.keyVault.Name, + Value: p.KeyVault.Name, }) } @@ -111,8 +111,8 @@ func (p *NitricAzurePulumiProvider) newContainerEnv(ctx *pulumi.Context, name st res.Env = env res.Registry, err = containerregistry.NewRegistry(ctx, ResourceName(ctx, name, RegistryRT), &containerregistry.RegistryArgs{ - ResourceGroupName: p.resourceGroup.Name, - Location: p.resourceGroup.Location, + ResourceGroupName: p.ResourceGroup.Name, + Location: p.ResourceGroup.Location, AdminUserEnabled: pulumi.BoolPtr(true), Sku: containerregistry.SkuArgs{ Name: pulumi.String("Basic"), @@ -123,8 +123,8 @@ func (p *NitricAzurePulumiProvider) newContainerEnv(ctx *pulumi.Context, name st } aw, err := operationalinsights.NewWorkspace(ctx, ResourceName(ctx, name, AnalyticsWorkspaceRT), &operationalinsights.WorkspaceArgs{ - Location: p.resourceGroup.Location, - ResourceGroupName: p.resourceGroup.Name, + Location: p.ResourceGroup.Location, + ResourceGroupName: p.ResourceGroup.Name, Sku: &operationalinsights.WorkspaceSkuArgs{ Name: pulumi.String("PerGB2018"), }, @@ -135,13 +135,13 @@ func (p *NitricAzurePulumiProvider) newContainerEnv(ctx *pulumi.Context, name st } sharedKeys := operationalinsights.GetSharedKeysOutput(ctx, operationalinsights.GetSharedKeysOutputArgs{ - ResourceGroupName: p.resourceGroup.Name, + ResourceGroupName: p.ResourceGroup.Name, WorkspaceName: aw.Name, }) res.ManagedEnv, err = app.NewManagedEnvironment(ctx, ResourceName(ctx, name, KubeRT), &app.ManagedEnvironmentArgs{ - Location: p.resourceGroup.Location, - ResourceGroupName: p.resourceGroup.Name, + Location: p.ResourceGroup.Location, + ResourceGroupName: p.ResourceGroup.Name, AppLogsConfiguration: app.AppLogsConfigurationArgs{ Destination: pulumi.String("log-analytics"), LogAnalyticsConfiguration: app.LogAnalyticsConfigurationArgs{ @@ -149,13 +149,13 @@ func (p *NitricAzurePulumiProvider) newContainerEnv(ctx *pulumi.Context, name st CustomerId: aw.CustomerId, }, }, - Tags: pulumi.ToStringMap(common.Tags(p.stackId, ctx.Stack()+"Kube", resources.Service)), + Tags: pulumi.ToStringMap(common.Tags(p.StackId, ctx.Stack()+"Kube", resources.Service)), }, pulumi.Parent(res)) if err != nil { return nil, err } - creds := pulumi.All(p.resourceGroup.Name, res.Registry.Name).ApplyT(func(args []interface{}) (*containerregistry.ListRegistryCredentialsResult, error) { + creds := pulumi.All(p.ResourceGroup.Name, res.Registry.Name).ApplyT(func(args []interface{}) (*containerregistry.ListRegistryCredentialsResult, error) { rgName := args[0].(string) regName := args[1].(string) diff --git a/cloud/azure/deploy/deploy.go b/cloud/azure/deploy/deploy.go index 55f4365e2..694a00e8d 100644 --- a/cloud/azure/deploy/deploy.go +++ b/cloud/azure/deploy/deploy.go @@ -24,10 +24,10 @@ import ( "github.com/nitrictech/nitric/cloud/common/deploy" "github.com/nitrictech/nitric/cloud/common/deploy/provider" + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" commonresources "github.com/nitrictech/nitric/cloud/common/deploy/resources" "github.com/nitrictech/nitric/cloud/common/deploy/tags" "github.com/nitrictech/nitric/core/pkg/logger" - deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" resourcespb "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" "github.com/pkg/errors" apimanagement "github.com/pulumi/pulumi-azure-native-sdk/apimanagement/v20201201" @@ -43,46 +43,41 @@ import ( "google.golang.org/grpc/status" ) -//go:embed runtime-azure -var runtime []byte - type ApiResources struct { ApiManagementService *apimanagement.ApiManagementService Api *apimanagement.Api } type NitricAzurePulumiProvider struct { - stackId string - projectName string - stackName string - fullStackName string - resources []*deploymentspb.Resource + *deploy.CommonStackDetails + + StackId string + resources []*pulumix.NitricPulumiResource[any] - config *AzureConfig - region string + AzureConfig *AzureConfig - clientConfig *authorization.GetClientConfigResult + ClientConfig *authorization.GetClientConfigResult - resourceGroup *resources.ResourceGroup - keyVault *keyvault.Vault - storageAccount *storage.StorageAccount + ResourceGroup *resources.ResourceGroup + KeyVault *keyvault.Vault + StorageAccount *storage.StorageAccount - containerEnv *ContainerEnv + ContainerEnv *ContainerEnv - apis map[string]ApiResources - httpProxies map[string]ApiResources - buckets map[string]*storage.BlobContainer + Apis map[string]ApiResources + HttpProxies map[string]ApiResources + Buckets map[string]*storage.BlobContainer - queues map[string]*storage.Queue + Queues map[string]*storage.Queue - principals map[resourcespb.ResourceType]map[string]*ServicePrincipal + Principals map[resourcespb.ResourceType]map[string]*ServicePrincipal - containerApps map[string]*ContainerApp - topics map[string]*eventgrid.Topic + ContainerApps map[string]*ContainerApp + Topics map[string]*eventgrid.Topic - keyValueStores map[string]*storage.Table + KeyValueStores map[string]*storage.Table - roles *Roles + Roles *Roles provider.NitricDefaultOrder } @@ -95,8 +90,8 @@ const ( func (a *NitricAzurePulumiProvider) Config() (auto.ConfigMap, error) { return auto.ConfigMap{ - "azure-native:location": auto.ConfigValue{Value: a.region}, - "azure:location": auto.ConfigValue{Value: a.region}, + "azure-native:location": auto.ConfigValue{Value: a.Region}, + "azure:location": auto.ConfigValue{Value: a.Region}, "azure-native:version": auto.ConfigValue{Value: pulumiAzureNativeVersion}, "azure:version": auto.ConfigValue{Value: pulumiAzureVersion}, "docker:version": auto.ConfigValue{Value: deploy.PulumiDockerVersion}, @@ -106,39 +101,16 @@ func (a *NitricAzurePulumiProvider) Config() (auto.ConfigMap, error) { func (a *NitricAzurePulumiProvider) Init(attributes map[string]interface{}) error { var err error - region, ok := attributes["region"].(string) - if !ok { - return fmt.Errorf("Missing region attribute") + a.CommonStackDetails, err = deploy.CommonStackDetailsFromAttributes(attributes) + if err != nil { + return status.Errorf(codes.InvalidArgument, err.Error()) } - a.region = region - - a.config, err = ConfigFromAttributes(attributes) + a.AzureConfig, err = ConfigFromAttributes(attributes) if err != nil { return status.Errorf(codes.InvalidArgument, "Bad stack configuration: %s", err) } - var isString bool - - iProject, hasProject := attributes["project"] - a.projectName, isString = iProject.(string) - if !hasProject || !isString || a.projectName == "" { - // need a valid project name - return fmt.Errorf("project is not set or invalid") - } - - iStack, hasStack := attributes["stack"] - a.stackName, isString = iStack.(string) - if !hasStack || !isString || a.stackName == "" { - // need a valid stack name - return fmt.Errorf("stack is not set or invalid") - } - - // Backwards compatible stack name - // The existing providers in the CLI - // Use the combined project and stack name - a.fullStackName = fmt.Sprintf("%s-%s", a.projectName, a.stackName) - return nil } @@ -186,9 +158,9 @@ func createStorageAccount(ctx *pulumi.Context, group *resources.ResourceGroup, t return storageAccount, nil } -func hasResourceType(resources []*deploymentspb.Resource, resourceType resourcespb.ResourceType) bool { +func hasResourceType(resources []*pulumix.NitricPulumiResource[any], resourceType resourcespb.ResourceType) bool { for _, r := range resources { - if r.GetId().GetType() == resourceType { + if r.Id.GetType() == resourceType { return true } } @@ -196,7 +168,7 @@ func hasResourceType(resources []*deploymentspb.Resource, resourceType resources return false } -func (a *NitricAzurePulumiProvider) Pre(ctx *pulumi.Context, nitricResources []*deploymentspb.Resource) error { +func (a *NitricAzurePulumiProvider) Pre(ctx *pulumi.Context, nitricResources []*pulumix.NitricPulumiResource[any]) error { a.resources = nitricResources // make our random stackId @@ -218,16 +190,16 @@ func (a *NitricAzurePulumiProvider) Pre(ctx *pulumi.Context, nitricResources []* return id }) - a.stackId = <-stackIdChan + a.StackId = <-stackIdChan - a.clientConfig, err = authorization.GetClientConfig(ctx) + a.ClientConfig, err = authorization.GetClientConfig(ctx) if err != nil { return err } - a.resourceGroup, err = resources.NewResourceGroup(ctx, ResourceName(ctx, "", ResourceGroupRT), &resources.ResourceGroupArgs{ - Location: pulumi.String(a.region), - Tags: pulumi.ToStringMap(tags.Tags(a.stackId, ctx.Stack(), commonresources.Stack)), + a.ResourceGroup, err = resources.NewResourceGroup(ctx, ResourceName(ctx, "", ResourceGroupRT), &resources.ResourceGroupArgs{ + Location: pulumi.String(a.Region), + Tags: pulumi.ToStringMap(tags.Tags(a.StackId, ctx.Stack(), commonresources.Stack)), }) if err != nil { return errors.WithMessage(err, "resource group create") @@ -238,7 +210,7 @@ func (a *NitricAzurePulumiProvider) Pre(ctx *pulumi.Context, nitricResources []* // This means we need to create a keyvault for each stack. if hasResourceType(nitricResources, resourcespb.ResourceType_Secret) { logger.Info("Stack declares one or more secrets, creating stack level Azure Key Vault") - a.keyVault, err = createKeyVault(ctx, a.resourceGroup, a.clientConfig.TenantId, tags.Tags(a.stackId, ctx.Stack(), commonresources.Stack)) + a.KeyVault, err = createKeyVault(ctx, a.ResourceGroup, a.ClientConfig.TenantId, tags.Tags(a.StackId, ctx.Stack(), commonresources.Stack)) if err != nil { return errors.WithMessage(err, "keyvault create") } @@ -253,19 +225,19 @@ func (a *NitricAzurePulumiProvider) Pre(ctx *pulumi.Context, nitricResources []* // This means we need to create a storage account for each stack, before buckets can be created. if hasBuckets || hasKvStores || hasQueues { logger.Info("Stack declares bucket(s), key/value store(s) or queue(s), creating stack level Azure Storage Account") - a.storageAccount, err = createStorageAccount(ctx, a.resourceGroup, tags.Tags(a.stackId, ctx.Stack(), commonresources.Stack)) + a.StorageAccount, err = createStorageAccount(ctx, a.ResourceGroup, tags.Tags(a.StackId, ctx.Stack(), commonresources.Stack)) if err != nil { return errors.WithMessage(err, "storage account create") } } - a.containerEnv, err = a.newContainerEnv(ctx, a.stackId, map[string]string{}) + a.ContainerEnv, err = a.newContainerEnv(ctx, a.StackId, map[string]string{}) if err != nil { return err } // Greedily create all the roles for consistency. Could be reduced to required roles only in future. - a.roles, err = CreateRoles(ctx, a.stackId, a.clientConfig.SubscriptionId, a.resourceGroup.Name) + a.Roles, err = CreateRoles(ctx, a.StackId, a.ClientConfig.SubscriptionId, a.ResourceGroup.Name) if err != nil { return err } @@ -281,20 +253,20 @@ func (a *NitricAzurePulumiProvider) Result(ctx *pulumi.Context) (pulumi.StringOu outputs := []interface{}{} // Add APIs outputs - if len(a.apis) > 0 { + if len(a.Apis) > 0 { outputs = append(outputs, pulumi.Sprintf("API Endpoints:\n──────────────")) - for apiName, api := range a.apis { + for apiName, api := range a.Apis { outputs = append(outputs, pulumi.Sprintf("%s: %s", apiName, api.ApiManagementService.GatewayUrl)) } } // Add HTTP Proxy outputs - if len(a.httpProxies) > 0 { + if len(a.HttpProxies) > 0 { if len(outputs) > 0 { outputs = append(outputs, "\n") } outputs = append(outputs, pulumi.Sprintf("HTTP Proxies:\n──────────────")) - for proxyName, proxy := range a.httpProxies { + for proxyName, proxy := range a.HttpProxies { outputs = append(outputs, pulumi.Sprintf("%s: %s", proxyName, proxy.ApiManagementService.GatewayUrl)) } } @@ -321,13 +293,13 @@ func NewNitricAzurePulumiProvider() *NitricAzurePulumiProvider { principalsMap[resourcespb.ResourceType_Service] = map[string]*ServicePrincipal{} return &NitricAzurePulumiProvider{ - apis: map[string]ApiResources{}, - httpProxies: map[string]ApiResources{}, - buckets: make(map[string]*storage.BlobContainer), - queues: make(map[string]*storage.Queue), - containerApps: map[string]*ContainerApp{}, - topics: map[string]*eventgrid.Topic{}, - principals: principalsMap, - keyValueStores: map[string]*storage.Table{}, + Apis: map[string]ApiResources{}, + HttpProxies: map[string]ApiResources{}, + Buckets: make(map[string]*storage.BlobContainer), + Queues: make(map[string]*storage.Queue), + ContainerApps: map[string]*ContainerApp{}, + Topics: map[string]*eventgrid.Topic{}, + Principals: principalsMap, + KeyValueStores: map[string]*storage.Table{}, } } diff --git a/cloud/azure/deploy/httpproxy.go b/cloud/azure/deploy/httpproxy.go index 0a7aa4c59..26c8778a2 100644 --- a/cloud/azure/deploy/httpproxy.go +++ b/cloud/azure/deploy/httpproxy.go @@ -69,16 +69,16 @@ func (p *NitricAzurePulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Reso var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - managedIdentities := p.containerEnv.ManagedUser.ID().ToStringOutput().ApplyT(func(id string) apimanagement.UserIdentityPropertiesMapOutput { + managedIdentities := p.ContainerEnv.ManagedUser.ID().ToStringOutput().ApplyT(func(id string) apimanagement.UserIdentityPropertiesMapOutput { return apimanagement.UserIdentityPropertiesMap{ id: nil, }.ToUserIdentityPropertiesMapOutput() }).(apimanagement.UserIdentityPropertiesMapOutput) mgmtService, err := apimanagement.NewApiManagementService(ctx, ResourceName(ctx, name, ApiManagementProxyRT), &apimanagement.ApiManagementServiceArgs{ - ResourceGroupName: p.resourceGroup.Name, - PublisherEmail: pulumi.String(p.config.AdminEmail), - PublisherName: pulumi.String(p.config.Org), + ResourceGroupName: p.ResourceGroup.Name, + PublisherEmail: pulumi.String(p.AzureConfig.AdminEmail), + PublisherName: pulumi.String(p.AzureConfig.Org), Sku: apimanagement.ApiManagementServiceSkuPropertiesArgs{ Name: pulumi.String("Consumption"), Capacity: pulumi.Int(0), @@ -87,7 +87,7 @@ func (p *NitricAzurePulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Reso Type: pulumi.String("UserAssigned"), UserAssignedIdentities: managedIdentities, }, - Tags: pulumi.ToStringMap(common.Tags(p.stackId, name, resources.HttpProxy)), + Tags: pulumi.ToStringMap(common.Tags(p.StackId, name, resources.HttpProxy)), }, opts...) if err != nil { return err @@ -108,7 +108,7 @@ func (p *NitricAzurePulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Reso ApiId: apiId, Format: pulumi.String("openapi+json"), Path: pulumi.String("/"), - ResourceGroupName: p.resourceGroup.Name, + ResourceGroupName: p.ResourceGroup.Name, SubscriptionRequired: pulumi.Bool(false), ServiceName: mgmtService.Name, Value: pulumi.String(string(b)), @@ -117,23 +117,23 @@ func (p *NitricAzurePulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Reso return err } - p.httpProxies[name] = ApiResources{ + p.HttpProxies[name] = ApiResources{ Api: proxyApi, ApiManagementService: mgmtService, } - targetContainerApp := p.containerApps[http.GetTarget().GetService()] + targetContainerApp := p.ContainerApps[http.GetTarget().GetService()] for _, path := range spec.Paths { for _, op := range path.Operations() { _, err = apimanagement.NewApiOperationPolicy(ctx, ResourceName(ctx, name+"-"+op.OperationID, ApiOperationPolicyRT), &apimanagement.ApiOperationPolicyArgs{ - ResourceGroupName: p.resourceGroup.Name, + ResourceGroupName: p.ResourceGroup.Name, ApiId: apiId, ServiceName: mgmtService.Name, OperationId: pulumi.String(op.OperationID), PolicyId: pulumi.String("policy"), Format: pulumi.String("xml"), - Value: pulumi.Sprintf(proxyTemplate, targetContainerApp.App.LatestRevisionFqdn, p.containerEnv.ManagedUser.ClientId, p.containerEnv.ManagedUser.ClientId), + Value: pulumi.Sprintf(proxyTemplate, targetContainerApp.App.LatestRevisionFqdn, p.ContainerEnv.ManagedUser.ClientId, p.ContainerEnv.ManagedUser.ClientId), }, pulumi.Parent(proxyApi), pulumi.DependsOn([]pulumi.Resource{proxyApi})) if err != nil { return errors.WithMessage(err, "NewApiOperationPolicy proxy") diff --git a/cloud/azure/deploy/keyvalue.go b/cloud/azure/deploy/keyvalue.go index 1f9431057..95ad3dbf3 100644 --- a/cloud/azure/deploy/keyvalue.go +++ b/cloud/azure/deploy/keyvalue.go @@ -24,9 +24,9 @@ func (p *NitricAzurePulumiProvider) KeyValueStore(ctx *pulumi.Context, parent pu var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - p.keyValueStores[name], err = storage.NewTable(ctx, name, &storage.TableArgs{ - AccountName: p.storageAccount.Name, - ResourceGroupName: p.resourceGroup.Name, + p.KeyValueStores[name], err = storage.NewTable(ctx, name, &storage.TableArgs{ + AccountName: p.StorageAccount.Name, + ResourceGroupName: p.ResourceGroup.Name, TableName: pulumi.String(name), }, opts...) diff --git a/cloud/azure/deploy/policy.go b/cloud/azure/deploy/policy.go index 274ca4c2e..f87a7566b 100644 --- a/cloud/azure/deploy/policy.go +++ b/cloud/azure/deploy/policy.go @@ -59,7 +59,7 @@ type resourceScope struct { func (p *NitricAzurePulumiProvider) scopeFromResource(resource *deploymentspb.Resource) (*resourceScope, error) { switch resource.Id.Type { case resourcespb.ResourceType_Topic: - topic, ok := p.topics[resource.Id.Name] + topic, ok := p.Topics[resource.Id.Name] if !ok { return nil, fmt.Errorf("topic %s not found", resource.Id.Name) } @@ -67,13 +67,13 @@ func (p *NitricAzurePulumiProvider) scopeFromResource(resource *deploymentspb.Re return &resourceScope{ scope: pulumi.Sprintf( "subscriptions/%s/resourceGroups/%s/providers/Microsoft.EventGrid/topics/%s", - p.clientConfig.SubscriptionId, - p.resourceGroup.Name, + p.ClientConfig.SubscriptionId, + p.ResourceGroup.Name, topic.Name, ), }, nil case resourcespb.ResourceType_KeyValueStore: - kv, ok := p.keyValueStores[resource.Id.Name] + kv, ok := p.KeyValueStores[resource.Id.Name] if !ok { return nil, fmt.Errorf("key value store %s not found", resource.Id.Name) } @@ -81,14 +81,14 @@ func (p *NitricAzurePulumiProvider) scopeFromResource(resource *deploymentspb.Re return &resourceScope{ scope: pulumi.Sprintf( "subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/tableServices/default/tables/%s", - p.clientConfig.SubscriptionId, - p.resourceGroup.Name, - p.storageAccount.Name, + p.ClientConfig.SubscriptionId, + p.ResourceGroup.Name, + p.StorageAccount.Name, kv.Name, ), }, nil case resourcespb.ResourceType_Bucket: - bucket, ok := p.buckets[resource.Id.Name] + bucket, ok := p.Buckets[resource.Id.Name] if !ok { return nil, fmt.Errorf("bucket %s not found", resource.Id.Name) } @@ -102,14 +102,14 @@ func (p *NitricAzurePulumiProvider) scopeFromResource(resource *deploymentspb.Re return &resourceScope{ scope: pulumi.Sprintf( "subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/blobServices/default/containers/%s", - p.clientConfig.SubscriptionId, - p.resourceGroup.Name, - p.storageAccount.Name, + p.ClientConfig.SubscriptionId, + p.ResourceGroup.Name, + p.StorageAccount.Name, bucket.Name, ), }, nil case resourcespb.ResourceType_Queue: - queue, ok := p.queues[resource.Id.Name] + queue, ok := p.Queues[resource.Id.Name] if !ok { return nil, fmt.Errorf("queue %s not found", resource.Id.Name) } @@ -117,23 +117,23 @@ func (p *NitricAzurePulumiProvider) scopeFromResource(resource *deploymentspb.Re return &resourceScope{ scope: pulumi.Sprintf( "subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/queueServices/default/queues/%s", - p.clientConfig.SubscriptionId, - p.resourceGroup.Name, - p.storageAccount.Name, + p.ClientConfig.SubscriptionId, + p.ResourceGroup.Name, + p.StorageAccount.Name, queue.Name, ), }, nil case resourcespb.ResourceType_Secret: - if p.keyVault == nil { + if p.KeyVault == nil { return nil, fmt.Errorf("secret %s not found", resource.Id.Name) } return &resourceScope{ scope: pulumi.Sprintf( "subscriptions/%s/resourcegroups/%s/providers/Microsoft.KeyVault/vaults/%s/secrets/%s", - p.clientConfig.SubscriptionId, - p.resourceGroup.Name, - p.keyVault.Name, + p.ClientConfig.SubscriptionId, + p.ResourceGroup.Name, + p.KeyVault.Name, resource.Id.Name, ), // condition: pulumi.Sprintf("@Resource[Microsoft.KeyVault/vaults/secrets].name equals %s'", resource.Name), @@ -149,12 +149,12 @@ func (p *NitricAzurePulumiProvider) Policy(ctx *pulumi.Context, parent pulumi.Re for _, resource := range policy.Resources { for _, principal := range policy.Principals { // The roles we need to assign - roles := actionsToAzureRoleDefinitions(p.roles.RoleDefinitions, policy.Actions) + roles := actionsToAzureRoleDefinitions(p.Roles.RoleDefinitions, policy.Actions) if len(roles) == 0 { - return fmt.Errorf("policy contained not assignable actions %+v, %+v", policy, p.roles.RoleDefinitions) + return fmt.Errorf("policy contained not assignable actions %+v, %+v", policy, p.Roles.RoleDefinitions) } - sp, ok := p.principals[principal.Id.Type][principal.Id.Name] + sp, ok := p.Principals[principal.Id.Type][principal.Id.Name] if !ok { return fmt.Errorf("principal %s of type %s not found", principal.Id.Name, principal.Id.Type) } diff --git a/cloud/azure/deploy/queue.go b/cloud/azure/deploy/queue.go index 77a1c4eef..476a4da2e 100644 --- a/cloud/azure/deploy/queue.go +++ b/cloud/azure/deploy/queue.go @@ -10,9 +10,9 @@ func (a *NitricAzurePulumiProvider) Queue(ctx *pulumi.Context, parent pulumi.Res var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - a.queues[name], err = storage.NewQueue(ctx, ResourceName(ctx, name, StorageQueueRT), &storage.QueueArgs{ - AccountName: a.storageAccount.Name, - ResourceGroupName: a.resourceGroup.Name, + a.Queues[name], err = storage.NewQueue(ctx, ResourceName(ctx, name, StorageQueueRT), &storage.QueueArgs{ + AccountName: a.StorageAccount.Name, + ResourceGroupName: a.ResourceGroup.Name, }, opts...) return err diff --git a/cloud/azure/deploy/runtime/runtime.go b/cloud/azure/deploy/runtime/runtime.go new file mode 100644 index 000000000..e68c316d1 --- /dev/null +++ b/cloud/azure/deploy/runtime/runtime.go @@ -0,0 +1,28 @@ +// Copyright 2021 Nitric Technologies Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtime + +import _ "embed" + +// Embeds the runtime directly into the deploytime binary +// This way the versions will always match as they're always built and versioned together (as a single artifact) +// This should also help with docker build speeds as the runtime has already been "downloaded" +// +//go:embed runtime-azure +var runtime []byte + +func NitricAzureRuntime() []byte { + return runtime +} diff --git a/cloud/azure/deploy/schedule.go b/cloud/azure/deploy/schedule.go index 98f71b96d..bc34491d3 100644 --- a/cloud/azure/deploy/schedule.go +++ b/cloud/azure/deploy/schedule.go @@ -42,7 +42,7 @@ func (p *NitricAzurePulumiProvider) Schedule(ctx *pulumi.Context, parent pulumi. var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - target := p.containerApps[config.GetTarget().GetService()] + target := p.ContainerApps[config.GetTarget().GetService()] normalizedName := strings.ToLower(strings.ReplaceAll(name, " ", "-")) cronExpression := "" @@ -73,8 +73,8 @@ func (p *NitricAzurePulumiProvider) Schedule(ctx *pulumi.Context, parent pulumi. } _, err = app.NewDaprComponent(ctx, normalizedName, &app.DaprComponentArgs{ - ResourceGroupName: p.resourceGroup.Name, - EnvironmentName: p.containerEnv.ManagedEnv.Name, + ResourceGroupName: p.ResourceGroup.Name, + EnvironmentName: p.ContainerEnv.ManagedEnv.Name, ComponentName: pulumi.String(normalizedName), ComponentType: pulumi.String("bindings.cron"), Version: pulumi.String("v1"), diff --git a/cloud/azure/deploy/service.go b/cloud/azure/deploy/service.go index 2bcbeee3b..0ca3ff062 100644 --- a/cloud/azure/deploy/service.go +++ b/cloud/azure/deploy/service.go @@ -23,6 +23,8 @@ import ( "github.com/nitrictech/nitric/cloud/azure/runtime/resource" "github.com/nitrictech/nitric/cloud/common/deploy/image" + "github.com/nitrictech/nitric/cloud/common/deploy/provider" + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" "github.com/nitrictech/nitric/cloud/common/deploy/resources" "github.com/Azure/azure-sdk-for-go/profiles/latest/eventgrid/eventgrid" @@ -147,7 +149,7 @@ var RoleDefinitions = map[string]string{ "TagContributor": "4a9ae827-6dc8-4573-8ac7-8239d42aa03f", } -func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Resource, name string, service *deploymentspb.Service) error { +func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Resource, name string, service *pulumix.NitricPulumiServiceConfig, runtime provider.RuntimeProvider) error { opts := []pulumi.ResourceOption{pulumi.Parent(parent)} res := &ContainerApp{ @@ -159,15 +161,15 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R return err } - repositoryUrl := pulumi.Sprintf("%s/%s-%s-%s", p.containerEnv.Registry.LoginServer, p.projectName, name, "azure") + repositoryUrl := pulumi.Sprintf("%s/%s-%s-%s", p.ContainerEnv.Registry.LoginServer, p.ProjectName, name, "azure") image, err := image.NewImage(ctx, name, &image.ImageArgs{ SourceImage: service.GetImage().Uri, RepositoryUrl: repositoryUrl, - Username: p.containerEnv.RegistryUser.Elem(), - Password: p.containerEnv.RegistryPass.Elem(), - Server: p.containerEnv.Registry.LoginServer, - Runtime: runtime, + Username: p.ContainerEnv.RegistryUser.Elem(), + Password: p.ContainerEnv.RegistryPass.Elem(), + Server: p.ContainerEnv.Registry.LoginServer, + Runtime: runtime(), }, opts...) if err != nil { return err @@ -177,7 +179,7 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R service.Type = "default" } - serviceConfig := p.config.Config[service.Type] + serviceConfig := p.AzureConfig.Config[service.Type] if serviceConfig.ContainerApps == nil { return fmt.Errorf("invalid container app config type: %s", service.Type) @@ -201,10 +203,10 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R if err != nil { return err } - p.principals[resourcespb.ResourceType_Service][name] = principal + p.Principals[resourcespb.ResourceType_Service][name] = principal res.Sp = principal - scope := pulumi.Sprintf("subscriptions/%s/resourceGroups/%s", p.clientConfig.SubscriptionId, p.resourceGroup.Name) + scope := pulumi.Sprintf("subscriptions/%s/resourceGroups/%s", p.ClientConfig.SubscriptionId, p.ResourceGroup.Name) // Assign roles to the new SP for defName, id := range RoleDefinitions { @@ -213,7 +215,7 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R _, err = authorization.NewRoleAssignment(ctx, ResourceName(ctx, name+defName, AssignmentRT), &authorization.RoleAssignmentArgs{ PrincipalId: res.Sp.ServicePrincipalId, PrincipalType: pulumi.StringPtr("ServicePrincipal"), - RoleDefinitionId: pulumi.Sprintf("/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s", p.clientConfig.SubscriptionId, id), + RoleDefinitionId: pulumi.Sprintf("/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s", p.ClientConfig.SubscriptionId, id), Scope: scope, }, pulumi.Parent(res)) if err != nil { @@ -232,7 +234,7 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R }, app.EnvironmentVarArgs{ Name: pulumi.String(resource.NITRIC_STACK_ID), - Value: pulumi.String(p.stackId), + Value: pulumi.String(p.StackId), }, app.EnvironmentVarArgs{ Name: pulumi.String("MIN_WORKERS"), @@ -240,11 +242,11 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R }, app.EnvironmentVarArgs{ Name: pulumi.String("AZURE_SUBSCRIPTION_ID"), - Value: pulumi.String(p.clientConfig.SubscriptionId), + Value: pulumi.String(p.ClientConfig.SubscriptionId), }, app.EnvironmentVarArgs{ Name: pulumi.String("AZURE_RESOURCE_GROUP"), - Value: p.resourceGroup.Name, + Value: p.ResourceGroup.Name, }, app.EnvironmentVarArgs{ Name: pulumi.String("AZURE_CLIENT_ID"), @@ -272,14 +274,14 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R // }, } - for k, v := range service.Env { + for k, v := range service.Env() { env = append(env, app.EnvironmentVarArgs{ Name: pulumi.String(k), - Value: pulumi.String(v), + Value: v, }) } - env = append(env, p.containerEnv.Env...) + env = append(env, p.ContainerEnv.Env...) // if len(args.Env) > 0 { // env = append(env, args.Env...) @@ -287,11 +289,13 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R // If this instance contains a schedule set the minimum instances to 1 // schedules rely on the Dapr Runtime to trigger the function, without a running instance the Dapr Runtime will not execute, so the schedule won't trigger. - _, schedulesFound := lo.Find(p.resources, func(item *deploy.Resource) bool { - if item.GetSchedule() == nil { - return false + _, schedulesFound := lo.Find(p.resources, func(item *pulumix.NitricPulumiResource[any]) bool { + switch t := item.Config.(type) { + case *deploymentspb.Schedule: + return t.Target.GetService() == name } - return item.GetSchedule().Target.GetService() == name + + return false }) minReplicas := serviceConfig.ContainerApps.MinReplicas @@ -302,9 +306,9 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R appName := ResourceName(ctx, name, ContainerAppRT) res.App, err = app.NewContainerApp(ctx, appName, &app.ContainerAppArgs{ - ResourceGroupName: p.resourceGroup.Name, - Location: p.resourceGroup.Location, - ManagedEnvironmentId: p.containerEnv.ManagedEnv.ID(), + ResourceGroupName: p.ResourceGroup.Name, + Location: p.ResourceGroup.Location, + ManagedEnvironmentId: p.ContainerEnv.ManagedEnv.ID(), Configuration: app.ConfigurationArgs{ ActiveRevisionsMode: pulumi.String("Single"), Ingress: app.IngressArgs{ @@ -313,8 +317,8 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R }, Registries: app.RegistryCredentialsArray{ app.RegistryCredentialsArgs{ - Server: p.containerEnv.Registry.LoginServer, - Username: p.containerEnv.RegistryUser, + Server: p.ContainerEnv.Registry.LoginServer, + Username: p.ContainerEnv.RegistryUser, PasswordSecretRef: pulumi.String("pwd"), }, }, @@ -327,7 +331,7 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R Secrets: app.SecretArray{ app.SecretArgs{ Name: pulumi.String("pwd"), - Value: p.containerEnv.RegistryPass, + Value: p.ContainerEnv.RegistryPass, }, app.SecretArgs{ Name: pulumi.String("client-id"), @@ -343,7 +347,7 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R }, }, }, - Tags: pulumi.ToStringMap(common.Tags(p.stackId, name, resources.Service)), + Tags: pulumi.ToStringMap(common.Tags(p.StackId, name, resources.Service)), Template: app.TemplateArgs{ Scale: app.ScaleArgs{ MaxReplicas: pulumi.Int(serviceConfig.ContainerApps.MaxReplicas), @@ -383,14 +387,14 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R OpenIdIssuer: pulumi.Sprintf("https://sts.windows.net/%s/v2.0", res.Sp.TenantID), }, Validation: &app.AzureActiveDirectoryValidationArgs{ - AllowedAudiences: pulumi.StringArray{p.containerEnv.ManagedUser.ClientId}, + AllowedAudiences: pulumi.StringArray{p.ContainerEnv.ManagedUser.ClientId}, }, }, }, Platform: &app.AuthPlatformArgs{ Enabled: pulumi.Bool(true), }, - ResourceGroupName: p.resourceGroup.Name, + ResourceGroupName: p.ResourceGroup.Name, }, pulumi.Parent(res.App)) if err != nil { return err @@ -401,7 +405,7 @@ func (p *NitricAzurePulumiProvider) Service(ctx *pulumi.Context, parent pulumi.R "containerApp": res.App, }) - p.containerApps[name] = res + p.ContainerApps[name] = res return err } diff --git a/cloud/azure/deploy/topic.go b/cloud/azure/deploy/topic.go index aca486333..4e118bd4c 100644 --- a/cloud/azure/deploy/topic.go +++ b/cloud/azure/deploy/topic.go @@ -43,12 +43,12 @@ type AzureEventGridTopicArgs struct { func (p *NitricAzurePulumiProvider) newEventGridTopicSubscription(ctx *pulumi.Context, parent pulumi.Resource, topicName string, config *deploymentspb.SubscriptionTarget) error { opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - target, ok := p.containerApps[config.GetService()] + target, ok := p.ContainerApps[config.GetService()] if !ok { return fmt.Errorf("Unable to find container app for service: %s", config.GetService()) } - topic, ok := p.topics[topicName] + topic, ok := p.Topics[topicName] if !ok { return fmt.Errorf("Unable to find topic: %s", topicName) } @@ -82,10 +82,10 @@ func (p *NitricAzurePulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Res var err error opts := []pulumi.ResourceOption{pulumi.Parent(parent)} - p.topics[name], err = eventgrid.NewTopic(ctx, ResourceName(ctx, name, EventGridRT), &eventgrid.TopicArgs{ - ResourceGroupName: p.resourceGroup.Name, - Location: p.resourceGroup.Location, - Tags: pulumi.ToStringMap(tags.Tags(p.stackId, name, nitricresources.Topic)), + p.Topics[name], err = eventgrid.NewTopic(ctx, ResourceName(ctx, name, EventGridRT), &eventgrid.TopicArgs{ + ResourceGroupName: p.ResourceGroup.Name, + Location: p.ResourceGroup.Location, + Tags: pulumi.ToStringMap(tags.Tags(p.StackId, name, nitricresources.Topic)), }, opts...) if err != nil { return err diff --git a/cloud/common/deploy/attributes.go b/cloud/common/deploy/attributes.go index 542c84c48..31a8ce208 100644 --- a/cloud/common/deploy/attributes.go +++ b/cloud/common/deploy/attributes.go @@ -17,9 +17,9 @@ package deploy import "fmt" type CommonStackDetails struct { - Project string + ProjectName string FullStackName string - Stack string + StackName string Region string } @@ -51,9 +51,9 @@ func CommonStackDetailsFromAttributes(attributes map[string]interface{}) (*Commo fullStackName := fmt.Sprintf("%s-%s", project, stack) return &CommonStackDetails{ - Project: project, + ProjectName: project, FullStackName: fullStackName, Region: region, - Stack: stack, + StackName: stack, }, nil } diff --git a/cloud/common/deploy/provider/provider.go b/cloud/common/deploy/provider/provider.go index 5463a87e0..dde45ded9 100644 --- a/cloud/common/deploy/provider/provider.go +++ b/cloud/common/deploy/provider/provider.go @@ -15,6 +15,7 @@ package provider import ( + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" resourcespb "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" "github.com/pulumi/pulumi/sdk/v3/go/auto" @@ -26,7 +27,7 @@ type NitricPulumiProvider interface { // Init - Initialize the provider with the given attributes, prior to any resource creation or Pulumi Context creation Init(attributes map[string]interface{}) error // Pre - Called prior to any resource creation, after the Pulumi Context has been established - Pre(ctx *pulumi.Context, resources []*deploymentspb.Resource) error + Pre(ctx *pulumi.Context, resources []*pulumix.NitricPulumiResource[any]) error // Config - Return the Pulumi ConfigMap for the provider Config() (auto.ConfigMap, error) @@ -42,7 +43,7 @@ type NitricPulumiProvider interface { // Bucket - Deploy a Storage Bucket Bucket(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Bucket) error // Service - Deploy an service (Service) - Service(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Service) error + Service(ctx *pulumi.Context, parent pulumi.Resource, name string, config *pulumix.NitricPulumiServiceConfig, runtimeProvider RuntimeProvider) error // Topic - Deploy a Pub/Sub Topic Topic(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Topic) error // Queue - Deploy a Queue diff --git a/cloud/common/deploy/provider/pulumi.go b/cloud/common/deploy/provider/pulumi.go index 4992a9453..9030f6d2d 100644 --- a/cloud/common/deploy/provider/pulumi.go +++ b/cloud/common/deploy/provider/pulumi.go @@ -35,17 +35,36 @@ import ( type PulumiProviderServer struct { provider NitricPulumiProvider + runtime RuntimeProvider } -func NewPulumiProviderServer(provider NitricPulumiProvider) *PulumiProviderServer { +func NewPulumiProviderServer(provider NitricPulumiProvider, runtime RuntimeProvider) *PulumiProviderServer { return &PulumiProviderServer{ provider: provider, + runtime: runtime, } } const resultCtxKey = "nitric:stack:result" -func createPulumiProgramForNitricProvider(req *deploymentspb.DeploymentUpRequest, nitricProvider NitricPulumiProvider) func(*pulumi.Context) error { +func nitricResourceToPulumiResource(res *deploymentspb.Resource) *pulumix.NitricPulumiResource[any] { + switch t := res.Config.(type) { + case *deploymentspb.Resource_Service: + return &pulumix.NitricPulumiResource[any]{ + Id: res.Id, + Config: &pulumix.NitricPulumiServiceConfig{ + Service: t.Service, + }, + } + default: + return &pulumix.NitricPulumiResource[any]{ + Id: res.Id, + Config: res.Config, + } + } +} + +func createPulumiProgramForNitricProvider(req *deploymentspb.DeploymentUpRequest, nitricProvider NitricPulumiProvider, runtime RuntimeProvider) func(*pulumi.Context) error { return func(ctx *pulumi.Context) (err error) { defer func() { if r := recover(); r != nil { @@ -54,21 +73,27 @@ func createPulumiProgramForNitricProvider(req *deploymentspb.DeploymentUpRequest } }() + // Need to convert the Nitric resources to Pulumi resources, this will allow us to extend their configurations with pulumi inputs/outputs + pulumiResources := make([]*pulumix.NitricPulumiResource[any], 0, len(req.Spec.Resources)) + for _, res := range nitricProvider.Order(req.Spec.Resources) { + pulumiResources = append(pulumiResources, nitricResourceToPulumiResource(res)) + } + // Pre-deployment Hook, used for validation, extension, spec modification, etc. - err = nitricProvider.Pre(ctx, req.Spec.Resources) + err = nitricProvider.Pre(ctx, pulumiResources) if err != nil { return err } - for _, res := range nitricProvider.Order(req.Spec.Resources) { + for _, res := range pulumiResources { parent, err := pulumix.ParentResourceFromResourceId(ctx, res.Id) if err != nil { return err } switch t := res.Config.(type) { - case *deploymentspb.Resource_Service: - err = nitricProvider.Service(ctx, parent, res.Id.Name, t.Service) + case *pulumix.NitricPulumiServiceConfig: + err = nitricProvider.Service(ctx, parent, res.Id.Name, t, runtime) case *deploymentspb.Resource_Secret: err = nitricProvider.Secret(ctx, parent, res.Id.Name, t.Secret) case *deploymentspb.Resource_Topic: @@ -181,7 +206,7 @@ func (s *PulumiProviderServer) Up(req *deploymentspb.DeploymentUpRequest, stream return err } - pulumiProgram := createPulumiProgramForNitricProvider(req, s.provider) + pulumiProgram := createPulumiProgramForNitricProvider(req, s.provider, s.runtime) autoStack, err := auto.UpsertStackInlineSource(context.TODO(), fmt.Sprintf("%s-%s", projectName, stackName), projectName, pulumiProgram) if err != nil { diff --git a/cloud/common/deploy/provider/runtime.go b/cloud/common/deploy/provider/runtime.go new file mode 100644 index 000000000..f87da6010 --- /dev/null +++ b/cloud/common/deploy/provider/runtime.go @@ -0,0 +1,18 @@ +// Copyright 2021 Nitric Technologies Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package provider + +// A function that returns the runtime for a nitric provider +type RuntimeProvider func() []byte diff --git a/cloud/common/deploy/pulumix/resource.go b/cloud/common/deploy/pulumix/resource.go new file mode 100644 index 000000000..42c7c013f --- /dev/null +++ b/cloud/common/deploy/pulumix/resource.go @@ -0,0 +1,57 @@ +// Copyright 2021 Nitric Technologies Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pulumix + +import ( + deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" + resourcespb "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type NitricPulumiResource[T any] struct { + Id *resourcespb.ResourceIdentifier + Config T +} + +type NitricPulumiServiceResource = NitricPulumiResource[*NitricPulumiServiceConfig] + +type NitricPulumiServiceConfig struct { + *deploymentspb.Service + + // Allow for pulumi strings to be used as service environment variables + env pulumi.StringMap +} + +func (n *NitricPulumiServiceConfig) SetEnv(key string, value pulumi.StringInput) { + if n.env == nil { + n.env = pulumi.StringMap{} + } + + n.env[key] = value +} + +func (n *NitricPulumiServiceConfig) Env() pulumi.StringMap { + envMap := pulumi.StringMap{} + + for k, v := range n.Service.GetEnv() { + envMap[k] = pulumi.String(v) + } + + for k, v := range n.env { + envMap[k] = v + } + + return envMap +} diff --git a/cloud/gcp/Makefile b/cloud/gcp/Makefile index 72b08e6ac..19f0cbc17 100644 --- a/cloud/gcp/Makefile +++ b/cloud/gcp/Makefile @@ -13,18 +13,18 @@ runtimebin: @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/runtime-gcp -ldflags="-s -w -extldflags=-static" ./cmd/runtime predeploybin: runtimebin - @cp bin/runtime-gcp deploy/runtime-gcp + @cp bin/runtime-gcp deploy/runtime/runtime-gcp sec: - @touch deploy/runtime-gcp + @touch deploy/runtime/runtime-gcp @go run github.com/securego/gosec/v2/cmd/gosec@latest -exclude-dir=tools ./... - @rm deploy/runtime-gcp + @rm deploy/runtime/runtime-gcp # There appears to be an old namespace conflict with the protobuf definitions deploybin: predeploybin @echo Building GCP Deployment Server @CGO_ENABLED=0 go build -o bin/deploy-gcp -ldflags="-s -w -extldflags=-static" -ldflags="-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=ignore" ./cmd/deploy - @rm deploy/runtime-gcp + @rm deploy/runtime/runtime-gcp .PHONY: install install: deploybin @@ -37,15 +37,15 @@ sourcefiles := $(shell find . -type f -name "*.go" -o -name "*.dockerfile") fmt: @go run github.com/google/addlicense -c "Nitric Technologies Pty Ltd." -y "2021" $(sourcefiles) - @touch deploy/runtime-gcp + @touch deploy/runtime/runtime-gcp $(GOLANGCI_LINT) run --fix - @rm deploy/runtime-gcp + @rm deploy/runtime/runtime-gcp lint: @go run github.com/google/addlicense -check -c "Nitric Technologies Pty Ltd." -y "2021" $(sourcefiles) - @touch deploy/runtime-gcp + @touch deploy/runtime/runtime-gcp $(GOLANGCI_LINT) run - @rm deploy/runtime-gcp + @rm deploy/runtime/runtime-gcp license-check: runtimebin @echo Checking GCP Membrane OSS Licenses diff --git a/cloud/gcp/cmd/deploy/main.go b/cloud/gcp/cmd/deploy/main.go index c73f30ca0..60149f3bb 100644 --- a/cloud/gcp/cmd/deploy/main.go +++ b/cloud/gcp/cmd/deploy/main.go @@ -19,13 +19,14 @@ package main import ( "github.com/nitrictech/nitric/cloud/common/deploy/provider" "github.com/nitrictech/nitric/cloud/gcp/deploy" + "github.com/nitrictech/nitric/cloud/gcp/deploy/runtime" ) // Start the deployment server func main() { gcpStack := deploy.NewNitricGcpProvider() - providerServer := provider.NewPulumiProviderServer(gcpStack) + providerServer := provider.NewPulumiProviderServer(gcpStack, runtime.NitricGcpRuntime) providerServer.Start() } diff --git a/cloud/gcp/deploy/api.go b/cloud/gcp/deploy/api.go index c69b1463b..59a97b92a 100644 --- a/cloud/gcp/deploy/api.go +++ b/cloud/gcp/deploy/api.go @@ -97,7 +97,7 @@ func (p *NitricGcpPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc } // Get service targets for IAM binding - services := p.cloudRunServices + services := p.CloudRunServices for _, pi := range v2doc.Paths { for _, m := range []string{http.MethodGet, http.MethodPatch, http.MethodDelete, http.MethodPost, http.MethodPut} { @@ -110,11 +110,11 @@ func (p *NitricGcpPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc return fmt.Errorf("found operation missing nitric target property: %+v", pi.GetOperation(m).Extensions) } - if _, ok := p.cloudRunServices[name]; !ok { - return fmt.Errorf("unable to find target service %s in %+v", name, p.cloudRunServices) + if _, ok := p.CloudRunServices[name]; !ok { + return fmt.Errorf("unable to find target service %s in %+v", name, p.CloudRunServices) } - services[name] = p.cloudRunServices[name] + services[name] = p.CloudRunServices[name] break } @@ -171,7 +171,7 @@ func (p *NitricGcpPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc return base64.StdEncoding.EncodeToString(b), nil }).(pulumi.StringOutput) - resourceLabels := common.Tags(p.stackId, name, resources.API) + resourceLabels := common.Tags(p.StackId, name, resources.API) api, err := apigateway.NewApi(ctx, name, &apigateway.ApiArgs{ ApiId: pulumi.String(name), @@ -205,7 +205,7 @@ func (p *NitricGcpPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc // Deploy the config config, err := apigateway.NewApiConfig(ctx, name+"-config", &apigateway.ApiConfigArgs{ - Project: pulumi.String(p.config.ProjectId), + Project: pulumi.String(p.GcpConfig.ProjectId), Api: api.ApiId, DisplayName: pulumi.String(name + "-config"), OpenapiDocuments: apigateway.ApiConfigOpenapiDocumentArray{ @@ -229,10 +229,10 @@ func (p *NitricGcpPulumiProvider) Api(ctx *pulumi.Context, parent pulumi.Resourc } // Deploy the gateway - p.apiGateways[name], err = apigateway.NewGateway(ctx, name+"-gateway", &apigateway.GatewayArgs{ + p.ApiGateways[name], err = apigateway.NewGateway(ctx, name+"-gateway", &apigateway.GatewayArgs{ DisplayName: pulumi.String(name + "-gateway"), GatewayId: pulumi.String(name + "-gateway"), - ApiConfig: pulumi.Sprintf("projects/%s/locations/global/apis/%s/configs/%s", p.config.ProjectId, api.ApiId, config.ApiConfigId), + ApiConfig: pulumi.Sprintf("projects/%s/locations/global/apis/%s/configs/%s", p.GcpConfig.ProjectId, api.ApiId, config.ApiConfigId), Labels: pulumi.ToStringMap(resourceLabels), }, p.WithDefaultResourceOptions(opts...)...) if err != nil { diff --git a/cloud/gcp/deploy/bucket.go b/cloud/gcp/deploy/bucket.go index 058ddd067..cb12f79bc 100644 --- a/cloud/gcp/deploy/bucket.go +++ b/cloud/gcp/deploy/bucket.go @@ -33,10 +33,10 @@ func (p *NitricGcpPulumiProvider) Bucket(ctx *pulumi.Context, parent pulumi.Reso var err error opts := append([]pulumi.ResourceOption{}, pulumi.Parent(parent)) - resourceLabels := common.Tags(p.stackId, name, resources.Bucket) + resourceLabels := common.Tags(p.StackId, name, resources.Bucket) - p.buckets[name], err = storage.NewBucket(ctx, name, &storage.BucketArgs{ - Location: pulumi.String(p.region), + p.Buckets[name], err = storage.NewBucket(ctx, name, &storage.BucketArgs{ + Location: pulumi.String(p.Region), Labels: pulumi.ToStringMap(resourceLabels), }, p.WithDefaultResourceOptions(opts...)...) if err != nil { @@ -63,18 +63,18 @@ func (p *NitricGcpPulumiProvider) newCloudStorageNotification(ctx *pulumi.Contex } topic, err := pubsub.NewTopic(ctx, name+"-topic", &pubsub.TopicArgs{ - Labels: pulumi.ToStringMap(common.Tags(p.stackId, name, resources.Bucket)), + Labels: pulumi.ToStringMap(common.Tags(p.StackId, name, resources.Bucket)), }, opts...) if err != nil { return err } - targetService, ok := p.cloudRunServices[listener.GetService()] + targetService, ok := p.CloudRunServices[listener.GetService()] if !ok { return fmt.Errorf("unable to find target service for bucket listener: %s", listener.GetService()) } - targetBucket, ok := p.buckets[bucketName] + targetBucket, ok := p.Buckets[bucketName] if !ok { return fmt.Errorf("unable to find target bucket for bucket listener: %s", bucketName) } diff --git a/cloud/gcp/deploy/deploy.go b/cloud/gcp/deploy/deploy.go index 3a4c034df..e3d7aa1f6 100644 --- a/cloud/gcp/deploy/deploy.go +++ b/cloud/gcp/deploy/deploy.go @@ -28,6 +28,7 @@ import ( "cloud.google.com/go/firestore/apiv1/admin/adminpb" "github.com/nitrictech/nitric/cloud/common/deploy" "github.com/nitrictech/nitric/cloud/common/deploy/provider" + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" "github.com/pkg/errors" "github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/apigateway" @@ -50,46 +51,36 @@ import ( ) type NitricGcpPulumiProvider struct { - stackId string - projectName string - stackName string - fullStackName string - - config *GcpConfig - region string - - delayQueue *cloudtasks.Queue - authToken *oauth2.Token - baseComputeRole *projects.IAMCustomRole - - project *Project - apiGateways map[string]*apigateway.Gateway - httpProxies map[string]*apigateway.Gateway - cloudRunServices map[string]*NitricCloudRunService - buckets map[string]*storage.Bucket - topics map[string]*pubsub.Topic - queues map[string]*pubsub.Topic - queueSubscriptions map[string]*pubsub.Subscription - secrets map[string]*secretmanager.Secret + *deploy.CommonStackDetails + + StackId string + GcpConfig *GcpConfig + + DelayQueue *cloudtasks.Queue + AuthToken *oauth2.Token + BaseComputeRole *projects.IAMCustomRole + + Project *Project + ApiGateways map[string]*apigateway.Gateway + HttpProxies map[string]*apigateway.Gateway + CloudRunServices map[string]*NitricCloudRunService + Buckets map[string]*storage.Bucket + Topics map[string]*pubsub.Topic + Queues map[string]*pubsub.Topic + QueueSubscriptions map[string]*pubsub.Subscription + Secrets map[string]*secretmanager.Secret provider.NitricDefaultOrder } -// Embeds the runtime directly into the deploytime binary -// This way the versions will always match as they're always built and versioned together (as a single artifact) -// This should also help with docker build speeds as the runtime has already been "downloaded" -// -//go:embed runtime-gcp -var runtime []byte - var _ provider.NitricPulumiProvider = (*NitricGcpPulumiProvider)(nil) const pulumiGcpVersion = "6.67.0" func (a *NitricGcpPulumiProvider) Config() (auto.ConfigMap, error) { return auto.ConfigMap{ - "gcp:region": auto.ConfigValue{Value: a.region}, - "gcp:project": auto.ConfigValue{Value: a.config.ProjectId}, + "gcp:region": auto.ConfigValue{Value: a.Region}, + "gcp:project": auto.ConfigValue{Value: a.GcpConfig.ProjectId}, "gcp:version": auto.ConfigValue{Value: pulumiGcpVersion}, "docker:version": auto.ConfigValue{Value: deploy.PulumiDockerVersion}, }, nil @@ -97,7 +88,7 @@ func (a *NitricGcpPulumiProvider) Config() (auto.ConfigMap, error) { func (a *NitricGcpPulumiProvider) WithDefaultResourceOptions(opts ...pulumi.ResourceOption) []pulumi.ResourceOption { defaultOptions := []pulumi.ResourceOption{ - pulumi.DependsOn([]pulumi.Resource{a.project}), + pulumi.DependsOn([]pulumi.Resource{a.Project}), } return append(defaultOptions, opts...) @@ -106,39 +97,16 @@ func (a *NitricGcpPulumiProvider) WithDefaultResourceOptions(opts ...pulumi.Reso func (a *NitricGcpPulumiProvider) Init(attributes map[string]interface{}) error { var err error - region, ok := attributes["region"].(string) - if !ok { - return fmt.Errorf("Missing region attribute") + a.CommonStackDetails, err = deploy.CommonStackDetailsFromAttributes(attributes) + if err != nil { + return status.Errorf(codes.InvalidArgument, err.Error()) } - a.region = region - - a.config, err = ConfigFromAttributes(attributes) + a.GcpConfig, err = ConfigFromAttributes(attributes) if err != nil { return status.Errorf(codes.InvalidArgument, "Bad stack configuration: %s", err) } - var isString bool - - iProject, hasProject := attributes["project"] - a.projectName, isString = iProject.(string) - if !hasProject || !isString || a.projectName == "" { - // need a valid project name - return fmt.Errorf("project is not set or invalid") - } - - iStack, hasStack := attributes["stack"] - a.stackName, isString = iStack.(string) - if !hasStack || !isString || a.stackName == "" { - // need a valid stack name - return fmt.Errorf("stack is not set or invalid") - } - - // Backwards compatible stack name - // The existing providers in the CLI - // Use the combined project and stack name - a.fullStackName = fmt.Sprintf("%s-%s", a.projectName, a.stackName) - return nil } @@ -170,7 +138,7 @@ var baseComputePermissions []string = []string{ "monitoring.timeSeries.create", } -func (a *NitricGcpPulumiProvider) Pre(ctx *pulumi.Context, resources []*deploymentspb.Resource) error { +func (a *NitricGcpPulumiProvider) Pre(ctx *pulumi.Context, resources []*pulumix.NitricPulumiResource[any]) error { // make our random stackId stackRandId, err := random.NewRandomString(ctx, fmt.Sprintf("%s-stack-name", ctx.Stack()), &random.RandomStringArgs{ Special: pulumi.Bool(false), @@ -190,49 +158,49 @@ func (a *NitricGcpPulumiProvider) Pre(ctx *pulumi.Context, resources []*deployme return id }) - a.stackId = <-stackIdChan + a.StackId = <-stackIdChan project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{ - ProjectId: &a.config.ProjectId, + ProjectId: &a.GcpConfig.ProjectId, }, nil) if err != nil { return err } - a.project, err = NewProject(ctx, "project", &ProjectArgs{ - ProjectId: a.config.ProjectId, + a.Project, err = NewProject(ctx, "project", &ProjectArgs{ + ProjectId: a.GcpConfig.ProjectId, ProjectNumber: project.Number, }) if err != nil { return err } - a.delayQueue, err = cloudtasks.NewQueue(ctx, "delay-queue", &cloudtasks.QueueArgs{ - Location: pulumi.String(a.region), + a.DelayQueue, err = cloudtasks.NewQueue(ctx, "delay-queue", &cloudtasks.QueueArgs{ + Location: pulumi.String(a.Region), }) if err != nil { return err } // Deploy all services - a.authToken, err = getGCPToken(ctx) + a.AuthToken, err = getGCPToken(ctx) if err != nil { return err } - baseCustomRoleId, err := random.NewRandomString(ctx, fmt.Sprintf("%s-base-role", a.fullStackName), &random.RandomStringArgs{ + baseCustomRoleId, err := random.NewRandomString(ctx, fmt.Sprintf("%s-base-role", a.FullStackName), &random.RandomStringArgs{ Special: pulumi.Bool(false), Length: pulumi.Int(8), Keepers: pulumi.ToMap(map[string]interface{}{ - "stack-name": a.fullStackName, + "stack-name": a.FullStackName, }), }) if err != nil { return errors.WithMessage(err, "base customRole id") } - a.baseComputeRole, err = projects.NewIAMCustomRole(ctx, "base-role", &projects.IAMCustomRoleArgs{ - Title: pulumi.String(a.fullStackName + "-functions-base-role"), + a.BaseComputeRole, err = projects.NewIAMCustomRole(ctx, "base-role", &projects.IAMCustomRoleArgs{ + Title: pulumi.String(a.FullStackName + "-functions-base-role"), Permissions: pulumi.ToStringArray(baseComputePermissions), RoleId: baseCustomRoleId.ID(), }) @@ -241,13 +209,13 @@ func (a *NitricGcpPulumiProvider) Pre(ctx *pulumi.Context, resources []*deployme } // Check if a key value store exists, if so get/create a (default) firestore database - kvStoreExists := lo.SomeBy(resources, func(res *deploymentspb.Resource) bool { + kvStoreExists := lo.SomeBy(resources, func(res *pulumix.NitricPulumiResource[any]) bool { _, ok := res.Config.(*deploymentspb.Resource_KeyValueStore) return ok }) if kvStoreExists { - err := createFirestoreDatabase(ctx, *project.ProjectId, a.region) + err := createFirestoreDatabase(ctx, *project.ProjectId, a.Region) if err != nil { return err } @@ -314,20 +282,20 @@ func (a *NitricGcpPulumiProvider) Result(ctx *pulumi.Context) (pulumi.StringOutp outputs := []interface{}{} // Add APIs outputs - if len(a.apiGateways) > 0 { + if len(a.ApiGateways) > 0 { outputs = append(outputs, pulumi.Sprintf("API Endpoints:\n──────────────")) - for apiName, api := range a.apiGateways { + for apiName, api := range a.ApiGateways { outputs = append(outputs, pulumi.Sprintf("%s: https://%s", apiName, api.DefaultHostname)) } } // Add HTTP Proxy outputs - if len(a.httpProxies) > 0 { + if len(a.HttpProxies) > 0 { if len(outputs) > 0 { outputs = append(outputs, "\n") } outputs = append(outputs, pulumi.Sprintf("HTTP Proxies:\n──────────────")) - for proxyName, proxy := range a.httpProxies { + for proxyName, proxy := range a.HttpProxies { outputs = append(outputs, pulumi.Sprintf("%s: https://%s", proxyName, proxy.DefaultHostname)) } } @@ -350,14 +318,14 @@ func (a *NitricGcpPulumiProvider) Result(ctx *pulumi.Context) (pulumi.StringOutp func NewNitricGcpProvider() *NitricGcpPulumiProvider { return &NitricGcpPulumiProvider{ - httpProxies: make(map[string]*apigateway.Gateway), - apiGateways: make(map[string]*apigateway.Gateway), - cloudRunServices: make(map[string]*NitricCloudRunService), - buckets: make(map[string]*storage.Bucket), - topics: make(map[string]*pubsub.Topic), - queues: make(map[string]*pubsub.Topic), - queueSubscriptions: make(map[string]*pubsub.Subscription), - secrets: make(map[string]*secretmanager.Secret), + HttpProxies: make(map[string]*apigateway.Gateway), + ApiGateways: make(map[string]*apigateway.Gateway), + CloudRunServices: make(map[string]*NitricCloudRunService), + Buckets: make(map[string]*storage.Bucket), + Topics: make(map[string]*pubsub.Topic), + Queues: make(map[string]*pubsub.Topic), + QueueSubscriptions: make(map[string]*pubsub.Subscription), + Secrets: make(map[string]*secretmanager.Secret), } } diff --git a/cloud/gcp/deploy/httpproxy.go b/cloud/gcp/deploy/httpproxy.go index a0ac2effe..1648a8a1d 100644 --- a/cloud/gcp/deploy/httpproxy.go +++ b/cloud/gcp/deploy/httpproxy.go @@ -35,9 +35,9 @@ import ( func (p *NitricGcpPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Http) error { opts := append([]pulumi.ResourceOption{}, pulumi.Parent(parent)) - resourceLabels := common.Tags(p.stackId, name, resources.HttpProxy) + resourceLabels := common.Tags(p.StackId, name, resources.HttpProxy) - targetService := p.cloudRunServices[config.Target.GetService()] + targetService := p.CloudRunServices[config.Target.GetService()] // normalise the name to match the required format // ^projects/([a-z0-9-]+)/locations/([a-z0-9-]+)(/([a-z]+)/([a-z0-9-.]+))+$ @@ -70,7 +70,7 @@ func (p *NitricGcpPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resour // Deploy the config apiConfig, err := apigateway.NewApiConfig(ctx, normalizedName+"-config", &apigateway.ApiConfigArgs{ - Project: pulumi.String(p.config.ProjectId), + Project: pulumi.String(p.GcpConfig.ProjectId), Api: api.ApiId, DisplayName: pulumi.String(normalizedName + "-config"), OpenapiDocuments: apigateway.ApiConfigOpenapiDocumentArray{ @@ -94,10 +94,10 @@ func (p *NitricGcpPulumiProvider) Http(ctx *pulumi.Context, parent pulumi.Resour } // Deploy the gateway - p.httpProxies[name], err = apigateway.NewGateway(ctx, normalizedName+"-gateway", &apigateway.GatewayArgs{ + p.HttpProxies[name], err = apigateway.NewGateway(ctx, normalizedName+"-gateway", &apigateway.GatewayArgs{ DisplayName: pulumi.String(normalizedName + "-gateway"), GatewayId: pulumi.String(normalizedName + "-gateway"), - ApiConfig: pulumi.Sprintf("projects/%s/locations/global/apis/%s/configs/%s", p.config.ProjectId, api.ApiId, apiConfig.ApiConfigId), + ApiConfig: pulumi.Sprintf("projects/%s/locations/global/apis/%s/configs/%s", p.GcpConfig.ProjectId, api.ApiId, apiConfig.ApiConfigId), Labels: pulumi.ToStringMap(resourceLabels), }, p.WithDefaultResourceOptions(opts...)...) if err != nil { diff --git a/cloud/gcp/deploy/policy.go b/cloud/gcp/deploy/policy.go index 3011f2c84..86d79b9cd 100644 --- a/cloud/gcp/deploy/policy.go +++ b/cloud/gcp/deploy/policy.go @@ -159,7 +159,7 @@ func actionsToGcpActions(actions []v1.Action) []string { func (a *NitricGcpPulumiProvider) serviceAccountForPrincipal(resource *deploymentspb.Resource) (*serviceaccount.Account, error) { switch resource.Id.Type { case resourcespb.ResourceType_Service: - if f, ok := a.cloudRunServices[resource.Id.Name]; ok { + if f, ok := a.CloudRunServices[resource.Id.Name]; ok { return f.ServiceAccount, nil } default: @@ -190,7 +190,7 @@ func (p *NitricGcpPulumiProvider) Policy(ctx *pulumi.Context, parent pulumi.Reso switch resource.Id.Type { case v1.ResourceType_Bucket: - b := p.buckets[resource.Id.Name] + b := p.Buckets[resource.Id.Name] _, err = gcpstorage.NewBucketIAMMember(ctx, memberName, &gcpstorage.BucketIAMMemberArgs{ Bucket: b.Name, @@ -211,14 +211,14 @@ func (p *NitricGcpPulumiProvider) Policy(ctx *pulumi.Context, parent pulumi.Reso _, err = projects.NewIAMMember(ctx, memberName, &projects.IAMMemberArgs{ Member: memberId, - Project: pulumi.String(p.config.ProjectId), + Project: pulumi.String(p.GcpConfig.ProjectId), Role: collRole.Name, }, opts...) if err != nil { return err } case v1.ResourceType_Topic: - t := p.topics[resource.Id.Name] + t := p.Topics[resource.Id.Name] _, err = pubsub.NewTopicIAMMember(ctx, memberName, &pubsub.TopicIAMMemberArgs{ Topic: t.Name, @@ -229,7 +229,7 @@ func (p *NitricGcpPulumiProvider) Policy(ctx *pulumi.Context, parent pulumi.Reso return err } case v1.ResourceType_Queue: - q := p.queues[resource.Id.Name] + q := p.Queues[resource.Id.Name] _, err = pubsub.NewTopicIAMMember(ctx, memberName, &pubsub.TopicIAMMemberArgs{ Topic: q.Name, @@ -250,7 +250,7 @@ func (p *NitricGcpPulumiProvider) Policy(ctx *pulumi.Context, parent pulumi.Reso } if needSubConsume { - subscription := p.queueSubscriptions[resource.Id.Name] + subscription := p.QueueSubscriptions[resource.Id.Name] subRolePolicy, err := NewCustomRole(ctx, name+"subscription", []string{"pubsub.subscriptions.consume"}, opts...) if err != nil { return err @@ -266,7 +266,7 @@ func (p *NitricGcpPulumiProvider) Policy(ctx *pulumi.Context, parent pulumi.Reso } } case v1.ResourceType_Secret: - s := p.secrets[resource.Id.Name] + s := p.Secrets[resource.Id.Name] _, err = secretmanager.NewSecretIamMember(ctx, memberName, &secretmanager.SecretIamMemberArgs{ SecretId: s.SecretId, diff --git a/cloud/gcp/deploy/queue.go b/cloud/gcp/deploy/queue.go index a28703ea2..b6ae22888 100644 --- a/cloud/gcp/deploy/queue.go +++ b/cloud/gcp/deploy/queue.go @@ -45,17 +45,17 @@ func (p *NitricGcpPulumiProvider) Queue(ctx *pulumi.Context, parent pulumi.Resou var err error opts := append([]pulumi.ResourceOption{}, pulumi.Parent(parent)) - resourceLabels := common.Tags(p.stackId, name, resources.Queue) + resourceLabels := common.Tags(p.StackId, name, resources.Queue) - p.queues[name], err = pubsub.NewTopic(ctx, fmt.Sprintf("%s-queue", name), &pubsub.TopicArgs{ + p.Queues[name], err = pubsub.NewTopic(ctx, fmt.Sprintf("%s-queue", name), &pubsub.TopicArgs{ Labels: pulumi.ToStringMap(resourceLabels), }, p.WithDefaultResourceOptions(opts...)...) if err != nil { return err } - p.queueSubscriptions[name], err = pubsub.NewSubscription(ctx, fmt.Sprintf("%s-nitricqueue", name), &pubsub.SubscriptionArgs{ - Topic: p.queues[name].Name, + p.QueueSubscriptions[name], err = pubsub.NewSubscription(ctx, fmt.Sprintf("%s-nitricqueue", name), &pubsub.SubscriptionArgs{ + Topic: p.Queues[name].Name, Labels: pulumi.ToStringMap(resourceLabels), ExpirationPolicy: &pubsub.SubscriptionExpirationPolicyArgs{ Ttl: pulumi.String(""), diff --git a/cloud/gcp/deploy/runtime/runtime.go b/cloud/gcp/deploy/runtime/runtime.go new file mode 100644 index 000000000..b77ac4f25 --- /dev/null +++ b/cloud/gcp/deploy/runtime/runtime.go @@ -0,0 +1,28 @@ +// Copyright 2021 Nitric Technologies Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtime + +import _ "embed" + +// Embeds the runtime directly into the deploytime binary +// This way the versions will always match as they're always built and versioned together (as a single artifact) +// This should also help with docker build speeds as the runtime has already been "downloaded" +// +//go:embed runtime-gcp +var runtime []byte + +func NitricGcpRuntime() []byte { + return runtime +} diff --git a/cloud/gcp/deploy/schedule.go b/cloud/gcp/deploy/schedule.go index 1101436f2..29221759a 100644 --- a/cloud/gcp/deploy/schedule.go +++ b/cloud/gcp/deploy/schedule.go @@ -70,12 +70,12 @@ func (p *NitricGcpPulumiProvider) Schedule(ctx *pulumi.Context, parent pulumi.Re return err } - targetService := p.cloudRunServices[config.Target.GetService()] + targetService := p.CloudRunServices[config.Target.GetService()] payload := base64.StdEncoding.EncodeToString(eventJSON) _, err = cloudscheduler.NewJob(ctx, name, &cloudscheduler.JobArgs{ - TimeZone: pulumi.String(p.config.ScheduleTimezone), + TimeZone: pulumi.String(p.GcpConfig.ScheduleTimezone), HttpTarget: &cloudscheduler.JobHttpTargetArgs{ Uri: pulumi.Sprintf("%s/x-nitric-schedule/%s?token=%s", targetService.Url, name, targetService.EventToken), OidcToken: &cloudscheduler.JobHttpTargetOidcTokenArgs{ diff --git a/cloud/gcp/deploy/secret.go b/cloud/gcp/deploy/secret.go index e3102318c..2689098ef 100644 --- a/cloud/gcp/deploy/secret.go +++ b/cloud/gcp/deploy/secret.go @@ -35,14 +35,14 @@ func (p *NitricGcpPulumiProvider) Secret(ctx *pulumi.Context, parent pulumi.Reso var err error opts := append([]pulumi.ResourceOption{}, pulumi.Parent(parent)) - secId := pulumi.Sprintf("%s-%s", p.stackName, name) + secId := pulumi.Sprintf("%s-%s", p.StackName, name) - p.secrets[name], err = secretmanager.NewSecret(ctx, name, &secretmanager.SecretArgs{ + p.Secrets[name], err = secretmanager.NewSecret(ctx, name, &secretmanager.SecretArgs{ Replication: secretmanager.SecretReplicationArgs{ Automatic: pulumi.Bool(true), }, SecretId: secId, - Labels: pulumi.ToStringMap(common.Tags(p.stackId, name, resources.Secret)), + Labels: pulumi.ToStringMap(common.Tags(p.StackId, name, resources.Secret)), }, p.WithDefaultResourceOptions(opts...)...) if err != nil { return err diff --git a/cloud/gcp/deploy/service.go b/cloud/gcp/deploy/service.go index ea2ef0159..172d152d7 100644 --- a/cloud/gcp/deploy/service.go +++ b/cloud/gcp/deploy/service.go @@ -22,8 +22,9 @@ import ( "strings" "github.com/nitrictech/nitric/cloud/common/deploy/image" + "github.com/nitrictech/nitric/cloud/common/deploy/provider" + "github.com/nitrictech/nitric/cloud/common/deploy/pulumix" "github.com/nitrictech/nitric/cloud/common/deploy/telemetry" - deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" "github.com/pkg/errors" "github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/cloudrun" "github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/projects" @@ -44,7 +45,7 @@ type NitricCloudRunService struct { EventToken pulumi.StringOutput } -func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Service) error { +func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Resource, name string, config *pulumix.NitricPulumiServiceConfig, runtime provider.RuntimeProvider) error { opts := []pulumi.ResourceOption{pulumi.Parent(parent)} res := &NitricCloudRunService{ @@ -63,13 +64,13 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res } // get config for service - unitConfig, hasConfig := p.config.Config[config.Type] + unitConfig, hasConfig := p.GcpConfig.Config[config.Type] if !hasConfig { - return status.Errorf(codes.InvalidArgument, "unable to find config %s in stack config %+v", config.Type, p.config.Config) + return status.Errorf(codes.InvalidArgument, "unable to find config %s in stack config %+v", config.Type, p.GcpConfig.Config) } if unitConfig.CloudRun == nil { - return status.Errorf(codes.InvalidArgument, "unable to find cloud run config in stack config %+v", p.config.Config) + return status.Errorf(codes.InvalidArgument, "unable to find cloud run config in stack config %+v", p.GcpConfig.Config) } // Get the image name:tag from the uri @@ -78,11 +79,11 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res image, err := image.NewImage(ctx, gcpServiceName, &image.ImageArgs{ SourceImage: config.GetImage().Uri, - RepositoryUrl: pulumi.Sprintf("gcr.io/%s/%s", p.config.ProjectId, imageName), + RepositoryUrl: pulumi.Sprintf("gcr.io/%s/%s", p.GcpConfig.ProjectId, imageName), Username: pulumi.String("oauth2accesstoken"), - Password: pulumi.String(p.authToken.AccessToken), + Password: pulumi.String(p.AuthToken.AccessToken), Server: pulumi.String("https://gcr.io"), - Runtime: runtime, + Runtime: runtime(), Telemetry: &telemetry.TelemetryConfigArgs{ TraceSampling: unitConfig.Telemetry, TraceName: "googlecloud", @@ -120,9 +121,9 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res res.EventToken = token.Result _, err = projects.NewIAMMember(ctx, gcpServiceName+"-project-member", &projects.IAMMemberArgs{ - Project: pulumi.String(p.config.ProjectId), + Project: pulumi.String(p.GcpConfig.ProjectId), Member: pulumi.Sprintf("serviceAccount:%s", sa.ServiceAccount.Email), - Role: p.baseComputeRole.Name, + Role: p.BaseComputeRole.Name, }) if err != nil { return errors.WithMessage(err, "function project membership "+name) @@ -149,7 +150,7 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res }, cloudrun.ServiceTemplateSpecContainerEnvArgs{ Name: pulumi.String("NITRIC_STACK_ID"), - Value: pulumi.String(p.stackId), + Value: pulumi.String(p.StackId), }, cloudrun.ServiceTemplateSpecContainerEnvArgs{ Name: pulumi.String("SERVICE_ACCOUNT_EMAIL"), @@ -157,7 +158,7 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res }, cloudrun.ServiceTemplateSpecContainerEnvArgs{ Name: pulumi.String("GCP_REGION"), - Value: pulumi.String(p.region), + Value: pulumi.String(p.Region), }, cloudrun.ServiceTemplateSpecContainerEnvArgs{ Name: pulumi.String("NITRIC_HTTP_PROXY_PORT"), @@ -170,24 +171,24 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res Value: res.EventToken, }) - if p.delayQueue != nil { + if p.DelayQueue != nil { env = append(env, cloudrun.ServiceTemplateSpecContainerEnvArgs{ Name: pulumi.String("DELAY_QUEUE_NAME"), - Value: pulumi.Sprintf("projects/%s/locations/%s/queues/%s", p.delayQueue.Project, p.delayQueue.Location, p.delayQueue.Name), + Value: pulumi.Sprintf("projects/%s/locations/%s/queues/%s", p.DelayQueue.Project, p.DelayQueue.Location, p.DelayQueue.Name), }) } - for k, v := range config.Env { + for k, v := range config.Env() { env = append(env, cloudrun.ServiceTemplateSpecContainerEnvArgs{ Name: pulumi.String(k), - Value: pulumi.String(v), + Value: v, }) } res.Service, err = cloudrun.NewService(ctx, gcpServiceName, &cloudrun.ServiceArgs{ AutogenerateRevisionName: pulumi.BoolPtr(true), - Location: pulumi.String(p.region), - Project: pulumi.String(p.config.ProjectId), + Location: pulumi.String(p.Region), + Project: pulumi.String(p.GcpConfig.ProjectId), Template: cloudrun.ServiceTemplateArgs{ Metadata: cloudrun.ServiceTemplateMetadataArgs{ Annotations: pulumi.StringMap{ @@ -249,7 +250,7 @@ func (p *NitricGcpPulumiProvider) Service(ctx *pulumi.Context, parent pulumi.Res return *ss[0].Url, nil }).(pulumi.StringInput) - p.cloudRunServices[name] = res + p.CloudRunServices[name] = res return nil } diff --git a/cloud/gcp/deploy/topic.go b/cloud/gcp/deploy/topic.go index bb99aac4c..153dbc8bd 100644 --- a/cloud/gcp/deploy/topic.go +++ b/cloud/gcp/deploy/topic.go @@ -36,18 +36,18 @@ func (p *NitricGcpPulumiProvider) Topic(ctx *pulumi.Context, parent pulumi.Resou var err error opts := append([]pulumi.ResourceOption{}, pulumi.Parent(parent)) - p.topics[name], err = pubsub.NewTopic(ctx, name, &pubsub.TopicArgs{ - Labels: pulumi.ToStringMap(common.Tags(p.stackId, name, resources.Topic)), + p.Topics[name], err = pubsub.NewTopic(ctx, name, &pubsub.TopicArgs{ + Labels: pulumi.ToStringMap(common.Tags(p.StackId, name, resources.Topic)), }, p.WithDefaultResourceOptions(opts...)...) if err != nil { return err } for _, sub := range config.Subscriptions { - targetService := p.cloudRunServices[sub.GetService()] + targetService := p.CloudRunServices[sub.GetService()] _, err := pubsub.NewSubscription(ctx, GetSubName(targetService.Name, name), &pubsub.SubscriptionArgs{ - Topic: p.topics[name].Name, // The GCP topic name + Topic: p.Topics[name].Name, // The GCP topic name AckDeadlineSeconds: pulumi.Int(300), RetryPolicy: pubsub.SubscriptionRetryPolicyArgs{ MinimumBackoff: pulumi.String("15s"),