From 65f5cb738e132070e6211ea9dbcc8eaf36817039 Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Wed, 30 Aug 2023 09:04:09 +1000 Subject: [PATCH 1/4] Add dapr component config for cron binding. --- cloud/azure/deploy/exec/containerapp.go | 14 +++++ cloud/azure/deploy/schedule/dapr.go | 78 +++++++++++++++++++++++++ cloud/azure/deploy/up.go | 29 +++++++-- cloud/azure/go.mod | 1 + cloud/azure/go.sum | 2 + 5 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 cloud/azure/deploy/schedule/dapr.go diff --git a/cloud/azure/deploy/exec/containerapp.go b/cloud/azure/deploy/exec/containerapp.go index 0b618651b..8fc2186f1 100644 --- a/cloud/azure/deploy/exec/containerapp.go +++ b/cloud/azure/deploy/exec/containerapp.go @@ -28,6 +28,7 @@ import ( "github.com/pulumi/pulumi-azure-native-sdk/containerregistry" "github.com/pulumi/pulumi-random/sdk/v4/go/random" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/samber/lo" "github.com/nitrictech/nitric/cloud/azure/deploy/config" "github.com/nitrictech/nitric/cloud/azure/deploy/policy" @@ -52,6 +53,7 @@ type ContainerAppArgs struct { MongoDatabaseName pulumi.StringInput MongoDatabaseConnectionString pulumi.StringInput Config config.AzureContainerAppsConfig + Schedules []*deploy.Resource } type ContainerApp struct { @@ -182,6 +184,11 @@ func NewContainerApp(ctx *pulumi.Context, name string, args *ContainerAppArgs, o } } + // if this instance contains a schedule set the minimum instances to 1 + if len(args.Schedules) > 0 { + args.Config.MinReplicas = lo.Max([]int{args.Config.MinReplicas, 1}) + } + env := app.EnvironmentVarArray{ app.EnvironmentVarArgs{ Name: pulumi.String("EVENT_TOKEN"), @@ -247,6 +254,7 @@ func NewContainerApp(ctx *pulumi.Context, name string, args *ContainerAppArgs, o Location: args.Location, ManagedEnvironmentId: args.ManagedEnv.ID(), Configuration: app.ConfigurationArgs{ + ActiveRevisionsMode: pulumi.String("Single"), Ingress: app.IngressArgs{ External: pulumi.BoolPtr(true), TargetPort: pulumi.Int(9001), @@ -258,6 +266,12 @@ func NewContainerApp(ctx *pulumi.Context, name string, args *ContainerAppArgs, o PasswordSecretRef: pulumi.String("pwd"), }, }, + Dapr: &app.DaprArgs{ + AppId: pulumi.String(appName), + AppPort: pulumi.Int(9001), + AppProtocol: pulumi.String("http"), + Enabled: pulumi.Bool(true), + }, Secrets: app.SecretArray{ app.SecretArgs{ Name: pulumi.String("pwd"), diff --git a/cloud/azure/deploy/schedule/dapr.go b/cloud/azure/deploy/schedule/dapr.go new file mode 100644 index 000000000..1fdb89596 --- /dev/null +++ b/cloud/azure/deploy/schedule/dapr.go @@ -0,0 +1,78 @@ +// 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 schedule + +import ( + "fmt" + "strings" + + "github.com/nitrictech/nitric/cloud/azure/deploy/exec" + "github.com/pkg/errors" + "github.com/pulumi/pulumi-azure-native-sdk/app" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type ScheduleArgs struct { + ResourceGroupName pulumi.StringInput + Target *exec.ContainerApp + Environment *app.ManagedEnvironment + Cron string +} + +type Schedule struct { + pulumi.ResourceState + + Name string + Component *app.DaprComponent +} + +func NewDaprCronBindingSchedule(ctx *pulumi.Context, name string, args *ScheduleArgs, opts ...pulumi.ResourceOption) (*Schedule, error) { + res := &Schedule{ + Name: name, + } + normalizedName := strings.ToLower(strings.ReplaceAll(name, " ", "-")) + err := ctx.RegisterComponentResource("nitric:func:ContainerApp", name, res, opts...) + if err != nil { + return nil, err + } + + res.Component, err = app.NewDaprComponent(ctx, normalizedName, &app.DaprComponentArgs{ + ResourceGroupName: args.ResourceGroupName, + EnvironmentName: args.Environment.Name, + ComponentName: pulumi.String(normalizedName), + ComponentType: pulumi.String("bindings.cron"), + Version: pulumi.String("v1"), + Metadata: app.DaprMetadataArray{ + app.DaprMetadataArgs{ + Name: pulumi.String("schedule"), + Value: pulumi.String(args.Cron), + }, + app.DaprMetadataArgs{ + Name: pulumi.String("route"), + Value: pulumi.Sprintf("/x-nitric-schedule/%s", normalizedName), + }, + }, + Scopes: pulumi.StringArray{ + // Limit the scope to the target container app + args.Target.App.Configuration.Dapr().AppId().Elem(), + }, + }) + + if err != nil { + return nil, errors.WithMessage(err, fmt.Sprintf("unable to create nitric schedule %s: failed to create DaprComponent for app", name)) + } + + return res, nil +} diff --git a/cloud/azure/deploy/up.go b/cloud/azure/deploy/up.go index 3d2accf3d..0df776e41 100644 --- a/cloud/azure/deploy/up.go +++ b/cloud/azure/deploy/up.go @@ -42,6 +42,7 @@ import ( "github.com/nitrictech/nitric/cloud/azure/deploy/config" "github.com/nitrictech/nitric/cloud/azure/deploy/exec" "github.com/nitrictech/nitric/cloud/azure/deploy/queue" + "github.com/nitrictech/nitric/cloud/azure/deploy/schedule" "github.com/nitrictech/nitric/cloud/azure/deploy/topic" "github.com/nitrictech/nitric/cloud/azure/deploy/utils" "github.com/nitrictech/nitric/cloud/common/deploy/image" @@ -108,12 +109,6 @@ func (d *DeployServer) Up(request *deploy.DeployUpRequest, stream deploy.DeployS return item.GetSchedule() != nil }) - if len(schedules) > 0 { - // TODO: Add schedule support - // NOTE: Currently CRONTAB support is required, we either need to revisit the design of - // our scheduled expressions or implement a workaround or request a feature. - return fmt.Errorf("schedules are not currently supported for Azure deployments") - } apis := lo.Filter[*deploy.Resource](request.Spec.Resources, func(item *deploy.Resource, index int) bool { return item.GetApi() != nil @@ -265,6 +260,10 @@ func (d *DeployServer) Up(request *deploy.DeployUpRequest, stream deploy.DeployS } if euConfig.ContainerApps != nil { + schedules := lo.Filter(schedules, func(item *deploy.Resource, idx int) bool { + return item.GetSchedule().Target.GetExecutionUnit() == eu.Name + }) + apps[eu.Name], err = exec.NewContainerApp(ctx, eu.Name, &exec.ContainerAppArgs{ ResourceGroupName: rg.Name, Location: pulumi.String(details.Region), @@ -280,6 +279,7 @@ func (d *DeployServer) Up(request *deploy.DeployUpRequest, stream deploy.DeployS MongoDatabaseName: mongodbName, MongoDatabaseConnectionString: mongoConnectionString, Config: *euConfig.ContainerApps, + Schedules: schedules, }, pulumi.Parent(contEnv)) if err != nil { return status.Errorf(codes.Internal, "error occurred whilst creating container app %s", eu.Name) @@ -290,6 +290,23 @@ func (d *DeployServer) Up(request *deploy.DeployUpRequest, stream deploy.DeployS } } + for _, s := range schedules { + cAppTarget, ok := apps[s.GetSchedule().Target.GetExecutionUnit()] + if !ok { + return fmt.Errorf("could not find target %s for schedule %s", s.GetSchedule().Target, s.Name) + } + + _, err := schedule.NewDaprCronBindingSchedule(ctx, s.Name, &schedule.ScheduleArgs{ + ResourceGroupName: rg.Name, + Target: cAppTarget, + Environment: contEnv.ManagedEnv, + Cron: s.GetSchedule().Cron, + }) + if err != nil { + return err + } + } + // For each bucket create a new bucket for _, b := range buckets { azBucket, err := bucket.NewAzureStorageBucket(ctx, b.Name, &bucket.AzureStorageBucketArgs{ diff --git a/cloud/azure/go.mod b/cloud/azure/go.mod index 60a00fbcb..dfb4ab920 100644 --- a/cloud/azure/go.mod +++ b/cloud/azure/go.mod @@ -34,6 +34,7 @@ require ( github.com/pulumi/pulumi-azure-native-sdk/operationalinsights v1.92.0 github.com/pulumi/pulumi-azure-native-sdk/resources v1.92.0 github.com/pulumi/pulumi-azure-native-sdk/storage v1.92.0 + github.com/pulumi/pulumi-azure-native/sdk v1.93.0 github.com/pulumi/pulumi-azure/sdk/v4 v4.42.0 github.com/pulumi/pulumi-azuread/sdk/v5 v5.33.0 github.com/pulumi/pulumi-random/sdk/v4 v4.8.2 diff --git a/cloud/azure/go.sum b/cloud/azure/go.sum index 526564f42..93a703e8f 100644 --- a/cloud/azure/go.sum +++ b/cloud/azure/go.sum @@ -772,6 +772,8 @@ github.com/pulumi/pulumi-azure-native-sdk/resources v1.92.0 h1:JU4X8/lVgtt/019Tu github.com/pulumi/pulumi-azure-native-sdk/resources v1.92.0/go.mod h1:hcrDiuRrruKdGbfA9KSSDsu/ywC29FKk02bsvIVxlQE= github.com/pulumi/pulumi-azure-native-sdk/storage v1.92.0 h1:pKOZCj84srz9uVEQJuzL6NAUBMwU/wZe3hFW6IJw9vQ= github.com/pulumi/pulumi-azure-native-sdk/storage v1.92.0/go.mod h1:d2qFMWBm21L9R3aHqx+mJ4Xvcs77WFFfG1Dn72twwvo= +github.com/pulumi/pulumi-azure-native/sdk v1.93.0 h1:8vj8O3ZQ24SF9QT5wV76KWh3DOM5jTwKYoEy4TYX5FE= +github.com/pulumi/pulumi-azure-native/sdk v1.93.0/go.mod h1:lCTXVgZKSgw5n+CMW9iBmiTcwVgQJ8nwGLmPZedj578= github.com/pulumi/pulumi-azure/sdk/v4 v4.42.0 h1:DOeBB0fJ/2IcEu1rT0IL8S2KNNf3bMXtTeLuPj13cPU= github.com/pulumi/pulumi-azure/sdk/v4 v4.42.0/go.mod h1:7zcnKiAlh4fut19e5HcWO1F3cwi03a5pM121/lZ8TyE= github.com/pulumi/pulumi-azuread/sdk/v5 v5.33.0 h1:/KXC518s5uvNrtk12HQCKgtzHm69v+IYMTmMInB5rCY= From 8b2860c26789f45d27a910a3a4e9b49eb1cf5cc1 Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Fri, 1 Sep 2023 10:01:10 +1000 Subject: [PATCH 2/4] fmt --- cloud/azure/deploy/up.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cloud/azure/deploy/up.go b/cloud/azure/deploy/up.go index 0df776e41..b3bb171a1 100644 --- a/cloud/azure/deploy/up.go +++ b/cloud/azure/deploy/up.go @@ -108,8 +108,6 @@ func (d *DeployServer) Up(request *deploy.DeployUpRequest, stream deploy.DeployS schedules := lo.Filter[*deploy.Resource](request.Spec.Resources, func(item *deploy.Resource, index int) bool { return item.GetSchedule() != nil }) - - apis := lo.Filter[*deploy.Resource](request.Spec.Resources, func(item *deploy.Resource, index int) bool { return item.GetApi() != nil }) From 9905fabee09db052727d703f84edec1fb7ddd2ca Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Mon, 4 Sep 2023 12:44:19 +1000 Subject: [PATCH 3/4] add success for options requests and add event token for dapr binding --- cloud/azure/deploy/schedule/dapr.go | 2 +- cloud/azure/runtime/gateway/http.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cloud/azure/deploy/schedule/dapr.go b/cloud/azure/deploy/schedule/dapr.go index 1fdb89596..5a034a60d 100644 --- a/cloud/azure/deploy/schedule/dapr.go +++ b/cloud/azure/deploy/schedule/dapr.go @@ -61,7 +61,7 @@ func NewDaprCronBindingSchedule(ctx *pulumi.Context, name string, args *Schedule }, app.DaprMetadataArgs{ Name: pulumi.String("route"), - Value: pulumi.Sprintf("/x-nitric-schedule/%s", normalizedName), + Value: pulumi.Sprintf("/x-nitric-schedule/%s?token=%s", normalizedName, args.Target.EventToken), }, }, Scopes: pulumi.StringArray{ diff --git a/cloud/azure/runtime/gateway/http.go b/cloud/azure/runtime/gateway/http.go index 1dc311fea..8cea65c90 100644 --- a/cloud/azure/runtime/gateway/http.go +++ b/cloud/azure/runtime/gateway/http.go @@ -150,7 +150,13 @@ func (a *azMiddleware) handleSubscription(process pool.WorkerPool) fasthttp.Requ } func (a *azMiddleware) handleSchedule(process pool.WorkerPool) fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + if strings.ToUpper(string(ctx.Request.Header.Method())) == "OPTIONS" { + ctx.SuccessString("text/plain", "success") + return + } + if !eventAuthorised(ctx) { ctx.Error("Unauthorized", 401) return From 93a43e44d5b886ad4d555871788304d10de46bf3 Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Mon, 4 Sep 2023 12:55:32 +1000 Subject: [PATCH 4/4] fmt --- cloud/azure/runtime/gateway/http.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud/azure/runtime/gateway/http.go b/cloud/azure/runtime/gateway/http.go index 8cea65c90..a4170e2d2 100644 --- a/cloud/azure/runtime/gateway/http.go +++ b/cloud/azure/runtime/gateway/http.go @@ -150,13 +150,12 @@ func (a *azMiddleware) handleSubscription(process pool.WorkerPool) fasthttp.Requ } func (a *azMiddleware) handleSchedule(process pool.WorkerPool) fasthttp.RequestHandler { - return func(ctx *fasthttp.RequestCtx) { if strings.ToUpper(string(ctx.Request.Header.Method())) == "OPTIONS" { ctx.SuccessString("text/plain", "success") return } - + if !eventAuthorised(ctx) { ctx.Error("Unauthorized", 401) return