Skip to content

Commit 49b3d92

Browse files
committed
Added sub-workflows to notifications
1 parent c32b07e commit 49b3d92

38 files changed

Lines changed: 715 additions & 206 deletions

internal/common/client_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package common
2+
3+
import (
4+
"crypto/sha256"
5+
"testing"
6+
7+
"github.com/google/uuid"
8+
)
9+
10+
func TestGetClientIdentifier_UsesEnvUUID(t *testing.T) {
11+
expected := uuid.New()
12+
t.Setenv(clientIdentifierEnvVar, expected.String())
13+
14+
got := GetClientIdentifier()
15+
if got != expected {
16+
t.Fatalf("GetClientIdentifier() = %s, expected %s", got.String(), expected.String())
17+
}
18+
}
19+
20+
func TestGetClientIdentifier_UsesEnvStringHash(t *testing.T) {
21+
value := "not-a-uuid-but-still-unique"
22+
t.Setenv(clientIdentifierEnvVar, value)
23+
24+
got1 := GetClientIdentifier()
25+
got2 := GetClientIdentifier()
26+
if got1 != got2 {
27+
t.Fatalf("expected deterministic UUID for env override; got %s then %s", got1.String(), got2.String())
28+
}
29+
30+
hash := sha256.Sum256([]byte(value))
31+
expected := uuid.UUID(hash[:16])
32+
if got1 != expected {
33+
t.Fatalf("GetClientIdentifier() = %s, expected %s", got1.String(), expected.String())
34+
}
35+
}

internal/config/providers.go

Lines changed: 152 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/thand-io/agent/internal/config/environment"
1111
"github.com/thand-io/agent/internal/models"
1212
"github.com/thand-io/agent/internal/providers"
13+
sdkConstants "github.com/thand-io/agent/sdk/constants"
1314
providerSdk "github.com/thand-io/agent/sdk/providers"
1415
"go.temporal.io/sdk/workflow"
1516

@@ -190,7 +191,7 @@ func (c *Config) InitializeProviders() error {
190191
if c.IsServer() {
191192
_ = c.GetServices()
192193
if err := c.SetupTemporal(); err != nil {
193-
return fmt.Errorf("setting up temporal services: %w", err)
194+
return fmt.Errorf("failed to set up temporal services: %w", err)
194195
}
195196
}
196197

@@ -239,25 +240,29 @@ func (c *Config) InitializeProviders() error {
239240
models.ProviderCapabilityRoles,
240241
models.ProviderCapabilityPermissions,
241242
models.ProviderCapabilityTenants,
243+
models.ProviderCapabilityNotifier,
244+
models.ProviderCapabilityProvisioning,
242245
) {
243246

244247
logrus.Infoln("Provider", result.key, "supports RBAC/Identities capabilities")
245248

246-
// Register provider workflows and activities with Temporal if available
247-
if c.IsServer() {
249+
// If a server or agent register all capabilities depending on their runtime
248250

249-
if c.GetServices() != nil && c.GetServices().HasTemporal() {
251+
if c.GetServices() != nil && c.GetServices().HasTemporal() {
250252

251-
logrus.Infoln("Registering Temporal workflows/activities for provider", result.key)
253+
logrus.Infoln("Registering Temporal workflows/activities for provider", result.key)
252254

253-
temporalService := c.GetServices().GetTemporal()
255+
temporalService := c.GetServices().GetTemporal()
254256

255-
worker := temporalService.GetWorker()
257+
worker := temporalService.GetWorker()
256258

257-
if worker == nil {
258-
logrus.Errorln("Temporal client is configured but worker is nil, cannot register workflows/activities for provider", result.key)
259-
continue
260-
}
259+
if worker == nil {
260+
logrus.Errorln("Temporal client is configured but worker is nil, cannot register workflows/activities for provider", result.key)
261+
continue
262+
}
263+
264+
// Only sync jobs can happen on the server
265+
if c.IsServer() {
261266

262267
syncWorkflowName := models.CreateTemporalProviderWorkflowName(
263268
providerResult.GetIdentifier(),
@@ -277,75 +282,146 @@ func (c *Config) InitializeProviders() error {
277282
VersioningBehavior: workflow.VersioningBehaviorPinned,
278283
},
279284
)
285+
}
286+
287+
logrus.Info("Registering default activities for provider", providerResult.GetName())
288+
289+
// Check the provider capability is either agent or server
290+
providerCapabilities := providerResult.GetCapabilities()
280291

281-
if providerResult.HasCapability(models.ProviderCapabilityProvisioning) {
282-
283-
authWorkflowName := models.CreateTemporalProviderWorkflowName(
284-
providerResult.GetIdentifier(),
285-
models.TemporalAuthorizeRoleWorkflowName)
286-
287-
logrus.WithFields(logrus.Fields{
288-
"workflow": authWorkflowName,
289-
"provider": providerResult.GetIdentifier(),
290-
}).Infoln("Registering provider authorize role workflow with name", authWorkflowName)
291-
292-
// Register the provider-specific authorize and revoke role workflows.
293-
// These are closure-based: they capture the live provider instance so the
294-
// child workflow can call provider.AuthorizeRole / RevokeRole with a
295-
// full workflow.Context, allowing providers to dispatch activities,
296-
// use workflow.Go, etc.
297-
worker.RegisterWorkflowWithOptions(
298-
models.CreateProviderAuthorizeRoleWorkflow(providerResult),
299-
workflow.RegisterOptions{
300-
Name: authWorkflowName,
301-
VersioningBehavior: workflow.VersioningBehaviorPinned,
302-
},
303-
)
304-
305-
revokeWorkflowName := models.CreateTemporalProviderWorkflowName(
306-
providerResult.GetIdentifier(),
307-
models.TemporalRevokeRoleWorkflowName)
308-
309-
logrus.WithFields(logrus.Fields{
310-
"workflow": revokeWorkflowName,
311-
"provider": providerResult.GetIdentifier(),
312-
}).Infoln("Registering provider revoke role workflow with name", revokeWorkflowName)
313-
314-
worker.RegisterWorkflowWithOptions(
315-
models.CreateProviderRevokeRoleWorkflow(providerResult),
316-
workflow.RegisterOptions{
317-
Name: revokeWorkflowName,
318-
VersioningBehavior: workflow.VersioningBehaviorPinned,
319-
},
320-
)
292+
// We always register the provider on the server
293+
// so that it can re-set the task queue if needed.
294+
// only register on agent/client if its needed.
295+
296+
// Register the provisioning capability
297+
if providerCapabilities != nil &&
298+
providerCapabilities.Provisioning.Enabled &&
299+
(providerCapabilities.Provisioning.Runtime == c.GetMode() || (providerCapabilities.Provisioning.Runtime == sdkConstants.ModeAgent && c.IsClient()) || c.IsServer()) {
300+
301+
authWorkflowName := models.CreateTemporalProviderWorkflowName(
302+
providerResult.GetIdentifier(),
303+
models.TemporalAuthorizeRoleWorkflowName)
304+
305+
logrus.WithFields(logrus.Fields{
306+
"workflow": authWorkflowName,
307+
"provider": providerResult.GetIdentifier(),
308+
}).Infoln("Registering provider authorize role workflow with name", authWorkflowName)
309+
310+
// Register the provider-specific authorize and revoke role workflows.
311+
// These are closure-based: they capture the live provider instance so the
312+
// child workflow can call provider.AuthorizeRole / RevokeRole with a
313+
// full workflow.Context, allowing providers to dispatch activities,
314+
// use workflow.Go, etc.
315+
worker.RegisterWorkflowWithOptions(
316+
models.CreateProviderAuthorizeRoleWorkflow(providerResult),
317+
workflow.RegisterOptions{
318+
Name: authWorkflowName,
319+
VersioningBehavior: workflow.VersioningBehaviorPinned,
320+
},
321+
)
322+
323+
revokeWorkflowName := models.CreateTemporalProviderWorkflowName(
324+
providerResult.GetIdentifier(),
325+
models.TemporalRevokeRoleWorkflowName)
326+
327+
logrus.WithFields(logrus.Fields{
328+
"workflow": revokeWorkflowName,
329+
"provider": providerResult.GetIdentifier(),
330+
}).Infoln("Registering provider revoke role workflow with name", revokeWorkflowName)
331+
332+
worker.RegisterWorkflowWithOptions(
333+
models.CreateProviderRevokeRoleWorkflow(providerResult),
334+
workflow.RegisterOptions{
335+
Name: revokeWorkflowName,
336+
VersioningBehavior: workflow.VersioningBehaviorPinned,
337+
},
338+
)
339+
} else {
340+
provisioningEnabled := false
341+
provisioningRuntime := sdkConstants.Mode("")
342+
if providerCapabilities != nil && providerCapabilities.Provisioning != nil {
343+
provisioningEnabled = providerCapabilities.Provisioning.Enabled
344+
provisioningRuntime = providerCapabilities.Provisioning.Runtime
321345
}
322-
// Register all custom provider workflows
323-
workflowsRegistry := providerResult.RegisterWorkflows()
324-
if workflowsRegistry != nil {
325-
logrus.Infoln("Registering Temporal workflows for provider", result.key)
326-
worker.RegisterWorkflow(workflowsRegistry)
346+
logrus.WithFields(logrus.Fields{
347+
"provider": result.key,
348+
"mode": c.GetMode(),
349+
"provisioning.enabled": provisioningEnabled,
350+
"provisioning.runtime": provisioningRuntime,
351+
}).Infoln("Skipping provisioning workflow registration: provider does not support provisioning in this runtime")
352+
}
353+
354+
// Register the notification capability
355+
if providerCapabilities != nil &&
356+
providerCapabilities.Notifier.Enabled &&
357+
((providerCapabilities.Notifier.Runtime == c.GetMode()) || (providerCapabilities.Notifier.Runtime == sdkConstants.ModeAgent && c.IsClient()) || c.IsServer()) {
358+
359+
notifierWorkflowName := models.CreateTemporalProviderWorkflowName(
360+
providerResult.GetIdentifier(),
361+
models.TemporalNotifyWorkflowName)
362+
363+
logrus.WithFields(logrus.Fields{
364+
"workflow": notifierWorkflowName,
365+
"provider": providerResult.GetIdentifier(),
366+
}).Infoln("Registering provider notify workflow with name", notifierWorkflowName)
367+
368+
worker.RegisterWorkflowWithOptions(
369+
models.CreateProviderNotifyWorkflow(providerResult),
370+
workflow.RegisterOptions{
371+
Name: notifierWorkflowName,
372+
VersioningBehavior: workflow.VersioningBehaviorPinned,
373+
},
374+
)
375+
} else {
376+
notifierEnabled := false
377+
notifierRuntime := sdkConstants.Mode("")
378+
if providerCapabilities != nil && providerCapabilities.Notifier != nil {
379+
notifierEnabled = providerCapabilities.Notifier.Enabled
380+
notifierRuntime = providerCapabilities.Notifier.Runtime
327381
}
382+
logrus.WithFields(logrus.Fields{
383+
"provider": result.key,
384+
"mode": c.GetMode(),
385+
"notifier.enabled": notifierEnabled,
386+
"notifier.runtime": notifierRuntime,
387+
}).Infoln("Skipping notify workflow registration: provider does not support notifier in this runtime")
388+
}
389+
390+
// Register all custom provider workflows
391+
workflowsRegistry := providerResult.RegisterWorkflows(c.GetMode())
392+
if workflowsRegistry != nil {
393+
logrus.Infoln("Registering Temporal workflows for provider", result.key)
394+
worker.RegisterWorkflow(workflowsRegistry)
395+
}
396+
397+
logrus.Infoln("Finished registering Temporal workflows/activities for provider", result.key)
398+
399+
// Register default provider activities
400+
err := models.RegisterProviderActivities(temporalService, providerResult, c)
401+
if err != nil {
402+
logrus.WithError(err).Errorln("Failed to register default activities for provider:", result.key)
403+
continue
404+
}
405+
406+
logrus.Infoln("Registered default activities for provider", providerResult.GetName())
328407

329-
// Register default provider activities
330-
err := models.RegisterProviderActivities(temporalService, providerResult, c)
408+
customActivities := providerResult.RegisterActivities(c.GetMode())
409+
if customActivities != nil {
410+
411+
logrus.Infoln("Registering custom Temporal activities for provider", result.key)
412+
413+
// Now register any custom activities defined by the provider
414+
err = models.RegisterActivities(
415+
temporalService,
416+
providerResult.GetIdentifier(),
417+
customActivities,
418+
)
331419
if err != nil {
332-
logrus.WithError(err).Errorln("Failed to register default activities for provider:", result.key)
420+
logrus.WithError(err).Errorln("Failed to register custom activities for provider:", result.key)
333421
continue
334422
}
335-
336-
customActivities := providerResult.RegisterActivities()
337-
if customActivities != nil {
338-
// Now register any custom activities defined by the provider
339-
err = models.RegisterActivities(
340-
temporalService,
341-
providerResult.GetIdentifier(),
342-
customActivities,
343-
)
344-
if err != nil {
345-
logrus.WithError(err).Errorln("Failed to register custom activities for provider:", result.key)
346-
continue
347-
}
348-
}
423+
} else {
424+
logrus.Infoln("No custom activities to register for provider", result.key)
349425
}
350426

351427
logrus.Infoln("Synchronizing provider", result.key)
@@ -358,7 +434,7 @@ func (c *Config) InitializeProviders() error {
358434
}
359435
} else {
360436
// Provider doesn't have RBAC/Identity capabilities, no sync needed
361-
result.provider.SetReady()
437+
providerResult.SetReady()
362438
}
363439

364440
// The provider returned from the goroutine already has the client set
@@ -425,7 +501,8 @@ func (c *Config) initializeSingleProvider(providerKey string, p *models.Provider
425501
// getProviderImplementation returns the appropriate provider implementation based on config mode
426502
func (c *Config) getProviderImplementation(providerKey string, providerName string) (models.Provider, error) {
427503

428-
if c.IsServer() || c.IsAgent() {
504+
// TODO
505+
if c.IsServer() || c.IsAgent() || c.IsClient() {
429506
return providers.CreateInstance(strings.ToLower(providerName))
430507
}
431508

internal/daemon/model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type SimpleServices struct {
99

1010
type SimpleConfig struct {
1111
ApiBasePath string
12+
Mode string
1213
Server SimpleServer
1314
Services SimpleServices
1415
}

internal/daemon/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func (s *Server) GetTemplateData(c *gin.Context) TemplateData {
156156
return TemplateData{
157157
Config: SimpleConfig{
158158
ApiBasePath: s.Config.GetApiBasePath(),
159+
Mode: string(s.Config.GetMode()),
159160
Server: SimpleServer{
160161
Host: s.Config.Server.Host,
161162
Port: s.Config.Server.Port,

internal/models/provider.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/hashicorp/go-version"
1111
"github.com/thand-io/agent/internal/common"
1212
"github.com/thand-io/agent/internal/interpolate"
13+
sdkConstants "github.com/thand-io/agent/sdk/constants"
1314
)
1415

1516
var ErrNotImplemented = errors.New("not implemented")
@@ -111,8 +112,8 @@ type Provider interface {
111112
Synchronize(ctx context.Context, temporalClient TemporalImpl, req *SynchronizeRequest) error
112113

113114
// Temporal
114-
RegisterWorkflows() any
115-
RegisterActivities() any
115+
RegisterWorkflows(runtime sdkConstants.Mode) any
116+
RegisterActivities(runtime sdkConstants.Mode) any
116117

117118
GetCapabilities() *ProviderCapabilities
118119
HasCapability(capability ProviderCapability) bool

internal/models/provider_activities.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66

77
"github.com/sirupsen/logrus"
8+
sdkConstants "github.com/thand-io/agent/sdk/constants"
89
"go.temporal.io/sdk/activity"
910
"go.temporal.io/sdk/temporal"
1011
)
@@ -16,7 +17,7 @@ import (
1617
// To expose additional, provider-specific activities, override RegisterActivities
1718
// on your provider struct to return a populated activities struct (or nil to skip):
1819
//
19-
// func (p *myProvider) RegisterActivities() any {
20+
// func (p *myProvider) RegisterActivities(runtime sdkConstants.Mode) any {
2021
// return &myProviderActivities{provider: p}
2122
// }
2223
//
@@ -68,7 +69,7 @@ func RegisterProviderActivities(temporalClient TemporalImpl, provider Provider,
6869
}
6970

7071
// RegisterActivities — BaseProvider default; returns ErrNotImplemented.
71-
func (b *BaseProvider) RegisterActivities() any {
72+
func (b *BaseProvider) RegisterActivities(runtime sdkConstants.Mode) any {
7273
return nil
7374
}
7475

0 commit comments

Comments
 (0)