mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2026-01-07 01:09:38 -06:00
feat(go-sdk): add ability to create workflows from the Go SDK more easily, quickstart improvements (#30)
* feat: add initial docs site * feat: allow workflows to be defined from go sdk * fix release action * chore: remove server dependencies from client * fix: use correct certificate for server * chore: add port and bind address to grpc config * fix: add env for grpc config * fix: nil pointer when output is null * chore: support variation in output args * fix unresolve merge conflict * fix: quickstart improvements * temp remove database url * fix: action id not required for event * fix: actionid validation for events * Remove deleted files
This commit is contained in:
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Pull and push hatchet-api
|
||||
run: |
|
||||
docker pull ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}}
|
||||
docker tag ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}} ghcr.io/hatchet-dev/hatchet/hatchet-server:latest
|
||||
docker tag ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}} ghcr.io/hatchet-dev/hatchet/hatchet-api:latest
|
||||
docker push ghcr.io/hatchet-dev/hatchet/hatchet-api:latest
|
||||
- name: Pull and push hatchet-engine
|
||||
run: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,7 @@ dump.rdb
|
||||
*.pfx
|
||||
*.cert
|
||||
generated
|
||||
.next
|
||||
|
||||
node_modules
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ message WorkerRegisterRequest {
|
||||
|
||||
// a list of actions that this worker can run
|
||||
repeated string actions = 3;
|
||||
|
||||
// (optional) the services for this worker
|
||||
repeated string services = 4;
|
||||
}
|
||||
|
||||
message WorkerRegisterResponse {
|
||||
|
||||
@@ -2,7 +2,6 @@ package cli
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"io/ioutil"
|
||||
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -189,22 +188,25 @@ func setupCerts(generated *generatedConfigFiles) error {
|
||||
return fmt.Errorf("could not create worker-client-cert.conf file: %w", err)
|
||||
}
|
||||
|
||||
// run openssl commands
|
||||
c := exec.Command("bash", "-s", "-", fullPathCertDir)
|
||||
// if CA files don't exists, run the script to regenerate all certs
|
||||
if overwrite || (!fileExists(filepath.Join(fullPathCertDir, "./ca.key")) || !fileExists(filepath.Join(fullPathCertDir, "./ca.cert"))) {
|
||||
// run openssl commands
|
||||
c := exec.Command("bash", "-s", "-", fullPathCertDir)
|
||||
|
||||
c.Stdin = strings.NewReader(GenerateCertsScript)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
c.Stdin = strings.NewReader(GenerateCertsScript)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
|
||||
err = c.Run()
|
||||
err = c.Run()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
generated.sc.TLS.TLSRootCAFile = filepath.Join(fullPathCertDir, "ca.cert")
|
||||
generated.sc.TLS.TLSCertFile = filepath.Join(fullPathCertDir, "client-internal-admin.pem")
|
||||
generated.sc.TLS.TLSKeyFile = filepath.Join(fullPathCertDir, "client-internal-admin.key")
|
||||
generated.sc.TLS.TLSCertFile = filepath.Join(fullPathCertDir, "cluster.pem")
|
||||
generated.sc.TLS.TLSKeyFile = filepath.Join(fullPathCertDir, "cluster.key")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -273,7 +275,7 @@ func getFiles(name string) [][]byte {
|
||||
basePath := filepath.Join(configDirectory, name)
|
||||
|
||||
if fileExists(basePath) {
|
||||
configFileBytes, err := ioutil.ReadFile(basePath)
|
||||
configFileBytes, err := os.ReadFile(basePath)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -285,7 +287,7 @@ func getFiles(name string) [][]byte {
|
||||
generatedPath := filepath.Join(generatedConfigDir, name)
|
||||
|
||||
if fileExists(generatedPath) {
|
||||
generatedFileBytes, err := ioutil.ReadFile(filepath.Join(generatedConfigDir, name))
|
||||
generatedFileBytes, err := os.ReadFile(filepath.Join(generatedConfigDir, name))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -314,7 +316,7 @@ func writeGeneratedConfig(generated *generatedConfigFiles) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(databasePath, databaseConfigBytes, 0666)
|
||||
err = os.WriteFile(databasePath, databaseConfigBytes, 0666)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write database.yaml file: %w", err)
|
||||
@@ -328,7 +330,7 @@ func writeGeneratedConfig(generated *generatedConfigFiles) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(serverPath, serverConfigBytes, 0666)
|
||||
err = os.WriteFile(serverPath, serverConfigBytes, 0666)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write server.yaml file: %w", err)
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/api/v1/server/run"
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/loader"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -62,7 +62,7 @@ func startServerOrDie(cf *loader.ConfigLoader, interruptCh <-chan interface{}) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx, cancel := cmdutils.InterruptContext(interruptCh)
|
||||
ctx, cancel := cmdutils.InterruptContextFromChan(interruptCh)
|
||||
defer cancel()
|
||||
|
||||
runner := run.NewAPIServer(sc)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/loader"
|
||||
"github.com/hatchet-dev/hatchet/internal/services/admin"
|
||||
"github.com/hatchet-dev/hatchet/internal/services/dispatcher"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/hatchet-dev/hatchet/internal/services/ingestor"
|
||||
"github.com/hatchet-dev/hatchet/internal/services/jobscontroller"
|
||||
"github.com/hatchet-dev/hatchet/internal/services/ticker"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ func startEngineOrDie(cf *loader.ConfigLoader, interruptCh <-chan interface{}) {
|
||||
}
|
||||
|
||||
errCh := make(chan error)
|
||||
ctx, cancel := cmdutils.InterruptContext(interruptCh)
|
||||
ctx, cancel := cmdutils.InterruptContextFromChan(interruptCh)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
if sc.HasService("grpc") {
|
||||
@@ -121,6 +121,8 @@ func startEngineOrDie(cf *loader.ConfigLoader, interruptCh <-chan interface{}) {
|
||||
grpc.WithAdmin(adminSvc),
|
||||
grpc.WithLogger(sc.Logger),
|
||||
grpc.WithTLSConfig(sc.TLSConfig),
|
||||
grpc.WithPort(sc.Runtime.GRPCPort),
|
||||
grpc.WithBindAddress(sc.Runtime.GRPCBindAddress),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -4,14 +4,21 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type printInput struct{}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := client.New(
|
||||
client.InitWorkflows(),
|
||||
)
|
||||
@@ -23,8 +30,8 @@ func main() {
|
||||
// Create a worker. This automatically reads in a TemporalClient from .env and workflow files from the .hatchet
|
||||
// directory, but this can be customized with the `worker.WithTemporalClient` and `worker.WithWorkflowFiles` options.
|
||||
worker, err := worker.NewWorker(
|
||||
worker.WithDispatcherClient(
|
||||
client.Dispatcher(),
|
||||
worker.WithClient(
|
||||
client,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -42,7 +49,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContext(cmdutils.InterruptChan())
|
||||
interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
|
||||
defer cancel()
|
||||
|
||||
err = worker.Start(interruptCtx)
|
||||
|
||||
135
examples/go-sdk-workflow/main.go
Normal file
135
examples/go-sdk-workflow/main.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type userCreateEvent struct {
|
||||
Username string `json:"username"`
|
||||
UserId string `json:"user_id"`
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
|
||||
type stepOneOutput struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func StepOne(ctx context.Context, input *userCreateEvent) (result *stepOneOutput, err error) {
|
||||
return &stepOneOutput{
|
||||
Message: "Username is: " + input.Username,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func StepTwo(ctx context.Context, input *stepOneOutput) (result *stepOneOutput, err error) {
|
||||
return &stepOneOutput{
|
||||
Message: "Above message is: " + input.Message,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := client.New()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create a worker. This automatically reads in a TemporalClient from .env and workflow files from the .hatchet
|
||||
// directory, but this can be customized with the `worker.WithTemporalClient` and `worker.WithWorkflowFiles` options.
|
||||
w, err := worker.NewWorker(
|
||||
worker.WithClient(
|
||||
client,
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = w.On(worker.Event("user:create"), &worker.WorkflowJob{
|
||||
Name: "test-job",
|
||||
Description: "This is a test job.",
|
||||
Steps: []worker.WorkflowStep{
|
||||
{
|
||||
Function: StepOne,
|
||||
},
|
||||
{
|
||||
Function: StepTwo,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// err = worker.RegisterAction("echo:echo", func(ctx context.Context, input *actionInput) (result any, err error) {
|
||||
// return map[string]interface{}{
|
||||
// "message": input.Message,
|
||||
// }, nil
|
||||
// })
|
||||
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// err = worker.RegisterAction("echo:object", func(ctx context.Context, input *actionInput) (result any, err error) {
|
||||
// return nil, nil
|
||||
// })
|
||||
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
err = w.Start(interruptCtx)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cancel()
|
||||
}()
|
||||
|
||||
testEvent := userCreateEvent{
|
||||
Username: "echo-test",
|
||||
UserId: "1234",
|
||||
Data: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
}
|
||||
|
||||
// push an event
|
||||
err = client.Event().Push(
|
||||
context.Background(),
|
||||
"user:create",
|
||||
testEvent,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-interruptCtx.Done():
|
||||
return
|
||||
default:
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type sampleEvent struct{}
|
||||
@@ -14,6 +15,12 @@ type sampleEvent struct{}
|
||||
type requeueInput struct{}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := client.New(
|
||||
client.InitWorkflows(),
|
||||
)
|
||||
@@ -25,8 +32,8 @@ func main() {
|
||||
// Create a worker. This automatically reads in a TemporalClient from .env and workflow files from the .hatchet
|
||||
// directory, but this can be customized with the `worker.WithTemporalClient` and `worker.WithWorkflowFiles` options.
|
||||
worker, err := worker.NewWorker(
|
||||
worker.WithDispatcherClient(
|
||||
client.Dispatcher(),
|
||||
worker.WithClient(
|
||||
client,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -42,7 +49,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContext(cmdutils.InterruptChan())
|
||||
interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
|
||||
defer cancel()
|
||||
|
||||
event := sampleEvent{}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type sampleEvent struct{}
|
||||
@@ -13,6 +14,12 @@ type sampleEvent struct{}
|
||||
type timeoutInput struct{}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := client.New(
|
||||
client.InitWorkflows(),
|
||||
)
|
||||
|
||||
@@ -21,3 +21,8 @@ jobs:
|
||||
timeout: 60s
|
||||
with:
|
||||
message: "Above message is: {{ .steps.echo2.message }}"
|
||||
- id: testObject
|
||||
action: echo:object
|
||||
timeout: 60s
|
||||
with:
|
||||
object: "{{ .steps.echo3.json }}"
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type userCreateEvent struct {
|
||||
@@ -20,6 +21,12 @@ type actionInput struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := client.New(
|
||||
client.InitWorkflows(),
|
||||
)
|
||||
@@ -31,8 +38,8 @@ func main() {
|
||||
// Create a worker. This automatically reads in a TemporalClient from .env and workflow files from the .hatchet
|
||||
// directory, but this can be customized with the `worker.WithTemporalClient` and `worker.WithWorkflowFiles` options.
|
||||
worker, err := worker.NewWorker(
|
||||
worker.WithDispatcherClient(
|
||||
client.Dispatcher(),
|
||||
worker.WithClient(
|
||||
client,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -50,7 +57,15 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContext(cmdutils.InterruptChan())
|
||||
err = worker.RegisterAction("echo:object", func(ctx context.Context, input *actionInput) (result any, err error) {
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client/types"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/integrations/slack"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type userCreateEvent struct {
|
||||
@@ -27,6 +28,12 @@ type actionInput struct {
|
||||
var SlackChannelWorkflow []byte
|
||||
|
||||
func init() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// initialize the slack channel workflow with SLACK_USER_ID
|
||||
slackUserId := os.Getenv("SLACK_USER_ID")
|
||||
|
||||
@@ -75,8 +82,8 @@ func main() {
|
||||
// Create a worker. This automatically reads in a TemporalClient from .env and workflow files from the .hatchet
|
||||
// directory, but this can be customized with the `worker.WithTemporalClient` and `worker.WithWorkflowFiles` options.
|
||||
worker, err := worker.NewWorker(
|
||||
worker.WithDispatcherClient(
|
||||
client.Dispatcher(),
|
||||
worker.WithClient(
|
||||
client,
|
||||
),
|
||||
worker.WithIntegration(
|
||||
slackInt,
|
||||
@@ -87,7 +94,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContext(cmdutils.InterruptChan())
|
||||
interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
|
||||
defer cancel()
|
||||
|
||||
go worker.Start(interruptCtx)
|
||||
|
||||
@@ -5,9 +5,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/cmd/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type sampleEvent struct{}
|
||||
@@ -15,6 +16,12 @@ type sampleEvent struct{}
|
||||
type timeoutInput struct{}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := client.New(
|
||||
client.InitWorkflows(),
|
||||
)
|
||||
@@ -26,8 +33,8 @@ func main() {
|
||||
// Create a worker. This automatically reads in a TemporalClient from .env and workflow files from the .hatchet
|
||||
// directory, but this can be customized with the `worker.WithTemporalClient` and `worker.WithWorkflowFiles` options.
|
||||
worker, err := worker.NewWorker(
|
||||
worker.WithDispatcherClient(
|
||||
client.Dispatcher(),
|
||||
worker.WithClient(
|
||||
client,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -48,7 +55,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
interruptCtx, cancel := cmdutils.InterruptContext(cmdutils.InterruptChan())
|
||||
interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -87,7 +87,7 @@ export function useTenantContext(): [
|
||||
} else if (computedCurrTenant?.metadata.id) {
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
newSearchParams.set('tenant', computedCurrTenant?.metadata.id);
|
||||
setSearchParams(newSearchParams);
|
||||
setSearchParams(newSearchParams, { replace: true });
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -116,7 +116,7 @@ export function useTenantContext(): [
|
||||
const setTenant = (tenant: Tenant) => {
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
newSearchParams.set('tenant', tenant.metadata.id);
|
||||
setSearchParams(newSearchParams);
|
||||
setSearchParams(newSearchParams, { replace: true });
|
||||
};
|
||||
|
||||
return [currTenant || computedCurrTenant, setTenant];
|
||||
|
||||
@@ -70,7 +70,7 @@ function Main() {
|
||||
<div className="flex flex-row flex-1 w-full h-full">
|
||||
<MainNav user={user} />
|
||||
<Sidebar memberships={memberships} currTenant={currTenant} />
|
||||
<div className="pt-12 flex-grow">
|
||||
<div className="pt-12 flex-grow overflow-y-auto overflow-x-hidden">
|
||||
<Outlet context={childCtx} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,4 +29,4 @@
|
||||
"@types/node": "18.11.10",
|
||||
"typescript": "^4.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,4 @@ The client to use to communicate with the Hatchet instance. This is required.
|
||||
|
||||
### `worker.WithName`
|
||||
|
||||
The name of the worker. This is used to identify the worker in the Hatchet UI.
|
||||
The name of the worker. This is used to identify the worker in the Hatchet UI.
|
||||
|
||||
@@ -159,6 +159,10 @@ func (store *UserSessionStore) New(r *http.Request, name string) (*sessions.Sess
|
||||
} else {
|
||||
session.IsNew = false
|
||||
}
|
||||
} else if strings.Contains(err.Error(), "the value is not valid") {
|
||||
// this error occurs if the encryption keys have been rotated, in which case we'd like a new cookie
|
||||
err = nil
|
||||
session.IsNew = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
// Adapted from: https://github.com/hatchet-dev/hatchet/blob/3c2c13168afa1af68d4baaf5ed02c9d49c5f0323/internal/config/loader/loader.go
|
||||
// Adapted from: https://github.com/hatchet-dev/hatchet-v1-archived/blob/3c2c13168afa1af68d4baaf5ed02c9d49c5f0323/internal/config/loader/loader.go
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/hatchet-dev/hatchet/internal/auth/cookie"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/client"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/database"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/loader/loaderutils"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/server"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/shared"
|
||||
"github.com/hatchet-dev/hatchet/internal/repository/prisma"
|
||||
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
|
||||
"github.com/hatchet-dev/hatchet/internal/services/ingestor"
|
||||
@@ -26,7 +20,6 @@ import (
|
||||
"github.com/hatchet-dev/hatchet/internal/validator"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// LoadDatabaseConfigFile loads the database config file via viper
|
||||
@@ -34,7 +27,7 @@ func LoadDatabaseConfigFile(files ...[]byte) (*database.ConfigFile, error) {
|
||||
configFile := &database.ConfigFile{}
|
||||
f := database.BindAllEnv
|
||||
|
||||
_, err := LoadConfigFromViper(f, configFile, files...)
|
||||
_, err := loaderutils.LoadConfigFromViper(f, configFile, files...)
|
||||
|
||||
return configFile, err
|
||||
}
|
||||
@@ -44,45 +37,11 @@ func LoadServerConfigFile(files ...[]byte) (*server.ServerConfigFile, error) {
|
||||
configFile := &server.ServerConfigFile{}
|
||||
f := server.BindAllEnv
|
||||
|
||||
_, err := LoadConfigFromViper(f, configFile, files...)
|
||||
_, err := loaderutils.LoadConfigFromViper(f, configFile, files...)
|
||||
|
||||
return configFile, err
|
||||
}
|
||||
|
||||
// LoadClientConfigFile loads the worker config file via viper
|
||||
func LoadClientConfigFile(files ...[]byte) (*client.ClientConfigFile, error) {
|
||||
configFile := &client.ClientConfigFile{}
|
||||
f := client.BindAllEnv
|
||||
|
||||
_, err := LoadConfigFromViper(f, configFile, files...)
|
||||
|
||||
return configFile, err
|
||||
}
|
||||
|
||||
func LoadConfigFromViper(bindFunc func(v *viper.Viper), configFile interface{}, files ...[]byte) (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigType("yaml")
|
||||
bindFunc(v)
|
||||
|
||||
for _, f := range files {
|
||||
err := v.MergeConfig(bytes.NewBuffer(f))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load viper config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
defaults.Set(configFile)
|
||||
|
||||
err := v.Unmarshal(configFile)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal viper config: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type ConfigLoader struct {
|
||||
directory string
|
||||
}
|
||||
@@ -94,7 +53,7 @@ func NewConfigLoader(directory string) *ConfigLoader {
|
||||
// LoadDatabaseConfig loads the database configuration
|
||||
func (c *ConfigLoader) LoadDatabaseConfig() (res *database.Config, err error) {
|
||||
sharedFilePath := filepath.Join(c.directory, "database.yaml")
|
||||
configFileBytes, err := getConfigBytes(sharedFilePath)
|
||||
configFileBytes, err := loaderutils.GetConfigBytes(sharedFilePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -112,7 +71,7 @@ func (c *ConfigLoader) LoadDatabaseConfig() (res *database.Config, err error) {
|
||||
// LoadServerConfig loads the server configuration
|
||||
func (c *ConfigLoader) LoadServerConfig() (res *server.ServerConfig, err error) {
|
||||
sharedFilePath := filepath.Join(c.directory, "server.yaml")
|
||||
configFileBytes, err := getConfigBytes(sharedFilePath)
|
||||
configFileBytes, err := loaderutils.GetConfigBytes(sharedFilePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -126,54 +85,13 @@ func (c *ConfigLoader) LoadServerConfig() (res *server.ServerConfig, err error)
|
||||
|
||||
cf, err := LoadServerConfigFile(configFileBytes...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetServerConfigFromConfigfile(dc, cf)
|
||||
}
|
||||
|
||||
// LoadClientConfig loads the client configuration
|
||||
func (c *ConfigLoader) LoadClientConfig() (res *client.ClientConfig, err error) {
|
||||
sharedFilePath := filepath.Join(c.directory, "client.yaml")
|
||||
configFileBytes, err := getConfigBytes(sharedFilePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cf, err := LoadClientConfigFile(configFileBytes...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetClientConfigFromConfigFile(cf)
|
||||
}
|
||||
|
||||
func getConfigBytes(configFilePath string) ([][]byte, error) {
|
||||
configFileBytes := make([][]byte, 0)
|
||||
|
||||
if fileExists(configFilePath) {
|
||||
fileBytes, err := ioutil.ReadFile(configFilePath) // #nosec G304 -- config files are meant to be read from user-supplied directory
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config file at path %s: %w", configFilePath, err)
|
||||
}
|
||||
|
||||
configFileBytes = append(configFileBytes, fileBytes)
|
||||
}
|
||||
|
||||
return configFileBytes, nil
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
} else if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
func GetDatabaseConfigFromConfigFile(cf *database.ConfigFile) (res *database.Config, err error) {
|
||||
databaseUrl := fmt.Sprintf(
|
||||
"postgresql://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
@@ -185,7 +103,7 @@ func GetDatabaseConfigFromConfigFile(cf *database.ConfigFile) (res *database.Con
|
||||
cf.PostgresSSLMode,
|
||||
)
|
||||
|
||||
os.Setenv("DATABASE_URL", databaseUrl)
|
||||
// os.Setenv("DATABASE_URL", databaseUrl)
|
||||
|
||||
client := db.NewClient(
|
||||
// db.WithDatasourceURL(databaseUrl),
|
||||
@@ -211,17 +129,12 @@ func GetDatabaseConfigFromConfigFile(cf *database.ConfigFile) (res *database.Con
|
||||
func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigFile) (res *server.ServerConfig, err error) {
|
||||
l := zerolog.New(os.Stderr)
|
||||
|
||||
tls, err := loadServerTLSConfig(&cf.TLS)
|
||||
tls, err := loaderutils.LoadServerTLSConfig(&cf.TLS)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load TLS config: %w", err)
|
||||
}
|
||||
|
||||
runtime := server.ServerRuntimeConfig{
|
||||
ServerURL: cf.Runtime.ServerURL,
|
||||
Port: cf.Runtime.Port,
|
||||
}
|
||||
|
||||
ss, err := cookie.NewUserSessionStore(
|
||||
cookie.WithSessionRepository(dc.Repository.UserSession()),
|
||||
cookie.WithCookieAllowInsecure(cf.Auth.Cookie.Insecure),
|
||||
@@ -246,7 +159,7 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF
|
||||
}
|
||||
|
||||
return &server.ServerConfig{
|
||||
Runtime: runtime,
|
||||
Runtime: cf.Runtime,
|
||||
Auth: cf.Auth,
|
||||
Config: dc,
|
||||
TaskQueue: rabbitmq.New(context.Background(), rabbitmq.WithURL(cf.TaskQueue.RabbitMQ.URL)),
|
||||
@@ -259,78 +172,6 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetClientConfigFromConfigFile(cf *client.ClientConfigFile) (res *client.ClientConfig, err error) {
|
||||
tlsConf, err := loadClientTLSConfig(&cf.TLS)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load TLS config: %w", err)
|
||||
}
|
||||
|
||||
return &client.ClientConfig{
|
||||
TenantId: cf.TenantId,
|
||||
TLSConfig: tlsConf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loadClientTLSConfig(tlsConfig *client.ClientTLSConfigFile) (*tls.Config, error) {
|
||||
res, ca, err := LoadBaseTLSConfig(&tlsConfig.Base)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.ServerName = tlsConfig.TLSServerName
|
||||
res.RootCAs = ca
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func loadServerTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, error) {
|
||||
res, ca, err := LoadBaseTLSConfig(tlsConfig)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
res.ClientCAs = ca
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func LoadBaseTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, *x509.CertPool, error) {
|
||||
var x509Cert tls.Certificate
|
||||
var err error
|
||||
|
||||
if tlsConfig.TLSCert != "" && tlsConfig.TLSKey != "" {
|
||||
x509Cert, err = tls.X509KeyPair([]byte(tlsConfig.TLSCert), []byte(tlsConfig.TLSKey))
|
||||
} else if tlsConfig.TLSCertFile != "" && tlsConfig.TLSKeyFile != "" {
|
||||
x509Cert, err = tls.LoadX509KeyPair(tlsConfig.TLSCertFile, tlsConfig.TLSKeyFile)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("no cert or key provided")
|
||||
}
|
||||
|
||||
var caBytes []byte
|
||||
|
||||
if tlsConfig.TLSRootCA != "" {
|
||||
caBytes = []byte(tlsConfig.TLSRootCA)
|
||||
} else if tlsConfig.TLSRootCAFile != "" {
|
||||
caBytes, err = os.ReadFile(tlsConfig.TLSRootCAFile)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("no root CA provided")
|
||||
}
|
||||
|
||||
ca := x509.NewCertPool()
|
||||
|
||||
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
|
||||
return nil, nil, fmt.Errorf("could not append root CA to cert pool: %w", err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{x509Cert},
|
||||
}, ca, nil
|
||||
}
|
||||
|
||||
func getStrArr(v string) []string {
|
||||
return strings.Split(v, " ")
|
||||
}
|
||||
|
||||
33
internal/config/loader/loaderutils/files.go
Normal file
33
internal/config/loader/loaderutils/files.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package loaderutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetConfigBytes(configFilePath string) ([][]byte, error) {
|
||||
configFileBytes := make([][]byte, 0)
|
||||
|
||||
if fileExists(configFilePath) {
|
||||
fileBytes, err := os.ReadFile(configFilePath) // #nosec G304 -- config files are meant to be read from user-supplied directory
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config file at path %s: %w", configFilePath, err)
|
||||
}
|
||||
|
||||
configFileBytes = append(configFileBytes, fileBytes)
|
||||
}
|
||||
|
||||
return configFileBytes, nil
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
} else if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !info.IsDir()
|
||||
}
|
||||
70
internal/config/loader/loaderutils/tls.go
Normal file
70
internal/config/loader/loaderutils/tls.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package loaderutils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/internal/config/client"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/shared"
|
||||
)
|
||||
|
||||
func LoadClientTLSConfig(tlsConfig *client.ClientTLSConfigFile) (*tls.Config, error) {
|
||||
res, ca, err := LoadBaseTLSConfig(&tlsConfig.Base)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.ServerName = tlsConfig.TLSServerName
|
||||
res.RootCAs = ca
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func LoadServerTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, error) {
|
||||
res, ca, err := LoadBaseTLSConfig(tlsConfig)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
res.ClientCAs = ca
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func LoadBaseTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, *x509.CertPool, error) {
|
||||
var x509Cert tls.Certificate
|
||||
var err error
|
||||
|
||||
if tlsConfig.TLSCert != "" && tlsConfig.TLSKey != "" {
|
||||
x509Cert, err = tls.X509KeyPair([]byte(tlsConfig.TLSCert), []byte(tlsConfig.TLSKey))
|
||||
} else if tlsConfig.TLSCertFile != "" && tlsConfig.TLSKeyFile != "" {
|
||||
x509Cert, err = tls.LoadX509KeyPair(tlsConfig.TLSCertFile, tlsConfig.TLSKeyFile)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("no cert or key provided")
|
||||
}
|
||||
|
||||
var caBytes []byte
|
||||
|
||||
if tlsConfig.TLSRootCA != "" {
|
||||
caBytes = []byte(tlsConfig.TLSRootCA)
|
||||
} else if tlsConfig.TLSRootCAFile != "" {
|
||||
caBytes, err = os.ReadFile(tlsConfig.TLSRootCAFile)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("no root CA provided")
|
||||
}
|
||||
|
||||
ca := x509.NewCertPool()
|
||||
|
||||
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
|
||||
return nil, nil, fmt.Errorf("could not append root CA to cert pool: %w", err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{x509Cert},
|
||||
}, ca, nil
|
||||
}
|
||||
33
internal/config/loader/loaderutils/viper.go
Normal file
33
internal/config/loader/loaderutils/viper.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package loaderutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func LoadConfigFromViper(bindFunc func(v *viper.Viper), configFile interface{}, files ...[]byte) (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigType("yaml")
|
||||
bindFunc(v)
|
||||
|
||||
for _, f := range files {
|
||||
err := v.MergeConfig(bytes.NewBuffer(f))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load viper config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
defaults.Set(configFile)
|
||||
|
||||
err := v.Unmarshal(configFile)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal viper config: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
@@ -32,6 +32,12 @@ type ConfigFileRuntime struct {
|
||||
|
||||
// ServerURL is the full server URL of the instance, including protocol.
|
||||
ServerURL string `mapstructure:"url" json:"url,omitempty" default:"http://localhost:8080"`
|
||||
|
||||
// GRPCPort is the port that the grpc service listens on
|
||||
GRPCPort int `mapstructure:"grpcPort" json:"grpcPort,omitempty" default:"7070"`
|
||||
|
||||
// GRPCBindAddress is the address that the grpc server binds to. Should set to 0.0.0.0 if binding in docker container.
|
||||
GRPCBindAddress string `mapstructure:"grpcBindAddress" json:"grpcBindAddress,omitempty" default:"127.0.0.1"`
|
||||
}
|
||||
|
||||
type ConfigFileAuth struct {
|
||||
@@ -66,17 +72,12 @@ type RabbitMQConfigFile struct {
|
||||
URL string `mapstructure:"url" json:"url,omitempty" validate:"required" default:"amqp://user:password@localhost:5672/"`
|
||||
}
|
||||
|
||||
type ServerRuntimeConfig struct {
|
||||
ServerURL string
|
||||
Port int
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
*database.Config
|
||||
|
||||
Auth ConfigFileAuth
|
||||
|
||||
Runtime ServerRuntimeConfig
|
||||
Runtime ConfigFileRuntime
|
||||
|
||||
Services []string
|
||||
|
||||
@@ -109,6 +110,8 @@ func BindAllEnv(v *viper.Viper) {
|
||||
// runtime options
|
||||
v.BindEnv("runtime.port", "SERVER_PORT")
|
||||
v.BindEnv("runtime.url", "SERVER_URL")
|
||||
v.BindEnv("runtime.grpcPort", "SERVER_GRPC_PORT")
|
||||
v.BindEnv("runtime.grpcBindAddress", "SERVER_GRPC_BIND_ADDRESS")
|
||||
v.BindEnv("services", "SERVER_SERVICES")
|
||||
|
||||
// auth options
|
||||
|
||||
@@ -21,10 +21,12 @@ func NewJobRunLookupDataFromInputBytes(input []byte) (JobRunLookupData, error) {
|
||||
return JobRunLookupData{}, fmt.Errorf("failed to convert input to map: %w", err)
|
||||
}
|
||||
|
||||
return NewJobRunLookupData(inputMap), nil
|
||||
return NewJobRunLookupData(inputMap, input), nil
|
||||
}
|
||||
|
||||
func NewJobRunLookupData(input map[string]interface{}) JobRunLookupData {
|
||||
func NewJobRunLookupData(input map[string]interface{}, rawInput []byte) JobRunLookupData {
|
||||
input["json"] = string(rawInput)
|
||||
|
||||
return JobRunLookupData{
|
||||
Input: input,
|
||||
}
|
||||
@@ -62,6 +64,9 @@ func AddStepOutput(data *types.JSON, stepReadableId string, stepOutput []byte) (
|
||||
return nil, fmt.Errorf("failed to convert step output to map: %w", err)
|
||||
}
|
||||
|
||||
// add a "json" accessor to the output
|
||||
outputMap["json"] = unquoted
|
||||
|
||||
currData := JobRunLookupData{}
|
||||
err = FromJSONType(data, &currData)
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ func jsonBytesToMap(jsonBytes []byte) (map[string]interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dataMap == nil {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
return dataMap, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,42 +2,63 @@ package datautils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// RenderTemplateFields recursively processes the input map, rendering any string fields using the data map.
|
||||
func RenderTemplateFields(data map[string]interface{}, input map[string]interface{}) error {
|
||||
func RenderTemplateFields(data map[string]interface{}, input map[string]interface{}) (map[string]interface{}, error) {
|
||||
output := map[string]interface{}{}
|
||||
|
||||
for key, val := range input {
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
tmpl, err := template.New(key).Parse(v)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating template for key %s: %v", key, err)
|
||||
return nil, fmt.Errorf("error creating template for key %s: %v", key, err)
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
err = tmpl.Execute(&tpl, data)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error executing template for key %s: %v", key, err)
|
||||
return nil, fmt.Errorf("error executing template for key %s: %v", key, err)
|
||||
}
|
||||
|
||||
res := tpl.String()
|
||||
|
||||
// if the string can be unmarshalled into a map[string]interface{}, do so
|
||||
resMap := map[string]interface{}{}
|
||||
|
||||
if err := json.Unmarshal(tpl.Bytes(), &resMap); err == nil {
|
||||
output[key] = resMap
|
||||
|
||||
// if the key is "object", the entire input is replaced with the rendered value
|
||||
if key == "object" {
|
||||
// note we do not recursively render the new input, as it may contain untrusted data.
|
||||
return resMap, nil
|
||||
}
|
||||
} else {
|
||||
output[key] = res
|
||||
}
|
||||
input[key] = tpl.String()
|
||||
case map[string]interface{}:
|
||||
// if we hit a nested map[string]interface{}, render those recursively
|
||||
err := RenderTemplateFields(data, v)
|
||||
recOut, err := RenderTemplateFields(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output[key] = recOut
|
||||
default:
|
||||
if reflect.TypeOf(v).Kind() == reflect.Map {
|
||||
// If it's a map but not map[string]interface{}, return an error
|
||||
return fmt.Errorf("encountered a map that is not map[string]interface{}: %s", key)
|
||||
return nil, fmt.Errorf("encountered a map that is not map[string]interface{}: %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return output, nil
|
||||
}
|
||||
|
||||
88
internal/datautils/render_test.go
Normal file
88
internal/datautils/render_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package datautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderTemplateFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data map[string]interface{}
|
||||
input map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple string template",
|
||||
data: map[string]interface{}{"testing": "datavalue"},
|
||||
input: map[string]interface{}{
|
||||
"render": "{{ .testing }}",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"render": "datavalue",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nested map template",
|
||||
data: map[string]interface{}{"testing": "nestedvalue"},
|
||||
input: map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"render": "{{ .testing }}",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"render": "nestedvalue",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "object template",
|
||||
data: map[string]interface{}{"testing": `{ "nested": "nestedvalue" }`},
|
||||
input: map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"render": "{{ .testing }}",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"render": map[string]interface{}{
|
||||
"nested": "nestedvalue",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "replace object",
|
||||
data: map[string]interface{}{"testing": `{ "nested": "nestedvalue" }`},
|
||||
input: map[string]interface{}{
|
||||
"object": "{{ .testing }}",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"nested": "nestedvalue",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, err := RenderTemplateFields(tt.data, tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RenderTemplateFields() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr {
|
||||
jsonExpected, _ := json.Marshal(tt.expected)
|
||||
jsonResult, _ := json.Marshal(output)
|
||||
if string(jsonExpected) != string(jsonResult) {
|
||||
t.Errorf("Expected %v, got %v", string(jsonExpected), string(jsonResult))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ type CreateEventOpts struct {
|
||||
// (required) the tenant id
|
||||
TenantId string `validate:"required,uuid"`
|
||||
|
||||
// (required) the event key - must be in actionId form
|
||||
Key string `validate:"required,actionId"`
|
||||
// (required) the event key
|
||||
Key string `validate:"required"`
|
||||
|
||||
// (optional) the event data
|
||||
Data *db.JSON
|
||||
|
||||
@@ -157,7 +157,43 @@ func (w *workerRepository) CreateNewWorker(tenantId string, opts *repository.Cre
|
||||
|
||||
txs := []transaction.Param{}
|
||||
|
||||
optionals := []db.WorkerSetParam{}
|
||||
workerId := uuid.New().String()
|
||||
|
||||
createTx := w.client.Worker.CreateOne(
|
||||
db.Worker.Tenant.Link(
|
||||
db.Tenant.ID.Equals(tenantId),
|
||||
),
|
||||
db.Worker.Name.Set(opts.Name),
|
||||
db.Worker.Dispatcher.Link(
|
||||
db.Dispatcher.ID.Equals(opts.DispatcherId),
|
||||
),
|
||||
db.Worker.ID.Set(workerId),
|
||||
).Tx()
|
||||
|
||||
txs = append(txs, createTx)
|
||||
|
||||
for _, svc := range opts.Services {
|
||||
upsertServiceTx := w.client.Service.UpsertOne(
|
||||
db.Service.TenantIDName(
|
||||
db.Service.TenantID.Equals(tenantId),
|
||||
db.Service.Name.Equals(svc),
|
||||
),
|
||||
).Create(
|
||||
db.Service.Name.Set(svc),
|
||||
db.Service.Tenant.Link(
|
||||
db.Tenant.ID.Equals(tenantId),
|
||||
),
|
||||
db.Service.Workers.Link(
|
||||
db.Worker.ID.Equals(workerId),
|
||||
),
|
||||
).Update(
|
||||
db.Service.Workers.Link(
|
||||
db.Worker.ID.Equals(workerId),
|
||||
),
|
||||
).Tx()
|
||||
|
||||
txs = append(txs, upsertServiceTx)
|
||||
}
|
||||
|
||||
if len(opts.Actions) > 0 {
|
||||
for _, action := range opts.Actions {
|
||||
@@ -173,28 +209,22 @@ func (w *workerRepository) CreateNewWorker(tenantId string, opts *repository.Cre
|
||||
),
|
||||
).Update().Tx())
|
||||
|
||||
optionals = append(optionals, db.Worker.Actions.Link(
|
||||
db.Action.TenantIDID(
|
||||
db.Action.TenantID.Equals(tenantId),
|
||||
db.Action.ID.Equals(action),
|
||||
// This is unfortunate but due to https://github.com/steebchen/prisma-client-go/issues/1095,
|
||||
// we cannot set db.Worker.Actions.Link multiple times, and since Link required a unique action
|
||||
// where clause, we have to do these in separate transactions
|
||||
txs = append(txs, w.client.Worker.FindUnique(
|
||||
db.Worker.ID.Equals(workerId),
|
||||
).Update(
|
||||
db.Worker.Actions.Link(
|
||||
db.Action.TenantIDID(
|
||||
db.Action.TenantID.Equals(tenantId),
|
||||
db.Action.ID.Equals(action),
|
||||
),
|
||||
),
|
||||
))
|
||||
).Tx())
|
||||
}
|
||||
}
|
||||
|
||||
createTx := w.client.Worker.CreateOne(
|
||||
db.Worker.Tenant.Link(
|
||||
db.Tenant.ID.Equals(tenantId),
|
||||
),
|
||||
db.Worker.Name.Set(opts.Name),
|
||||
db.Worker.Dispatcher.Link(
|
||||
db.Dispatcher.ID.Equals(opts.DispatcherId),
|
||||
),
|
||||
optionals...,
|
||||
).Tx()
|
||||
|
||||
txs = append(txs, createTx)
|
||||
|
||||
err := w.client.Prisma.Transaction(txs...).Exec(context.Background())
|
||||
|
||||
if err != nil {
|
||||
@@ -235,12 +265,19 @@ func (w *workerRepository) UpdateWorker(tenantId, workerId string, opts *reposit
|
||||
),
|
||||
).Update().Tx())
|
||||
|
||||
optionals = append(optionals, db.Worker.Actions.Link(
|
||||
db.Action.TenantIDID(
|
||||
db.Action.TenantID.Equals(tenantId),
|
||||
db.Action.ID.Equals(action),
|
||||
// This is unfortunate but due to https://github.com/steebchen/prisma-client-go/issues/1095,
|
||||
// we cannot set db.Worker.Actions.Link multiple times, and since Link required a unique action
|
||||
// where clause, we have to do these in separate transactions
|
||||
txs = append(txs, w.client.Worker.FindUnique(
|
||||
db.Worker.ID.Equals(workerId),
|
||||
).Update(
|
||||
db.Worker.Actions.Link(
|
||||
db.Action.TenantIDID(
|
||||
db.Action.TenantID.Equals(tenantId),
|
||||
db.Action.ID.Equals(action),
|
||||
),
|
||||
),
|
||||
))
|
||||
).Tx())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ type CreateWorkerOpts struct {
|
||||
// The name of the worker
|
||||
Name string `validate:"required,hatchetName"`
|
||||
|
||||
// The name of the service
|
||||
Services []string `validate:"dive,hatchetName"`
|
||||
|
||||
// A list of actions this worker can run
|
||||
Actions []string `validate:"dive,actionId"`
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type CreateWorkflowVersionOpts struct {
|
||||
Version string `validate:"required"`
|
||||
|
||||
// (optional) event triggers for the workflow
|
||||
EventTriggers []string `validate:"dive,actionId"`
|
||||
EventTriggers []string
|
||||
|
||||
// (optional) cron triggers for the workflow
|
||||
CronTriggers []string `validate:"dive,cron"`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -29,14 +30,7 @@ func GetCreateWorkflowRunOptsFromEvent(event *db.EventModel, workflowVersion *db
|
||||
eventId := event.ID
|
||||
data := event.InnerEvent.Data
|
||||
|
||||
dataInputMap := make(map[string]interface{})
|
||||
err := datautils.FromJSONType(data, &dataInputMap)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not marshal event data: %w", err)
|
||||
}
|
||||
|
||||
structuredJobRunData := datautils.NewJobRunLookupData(dataInputMap)
|
||||
structuredJobRunData, err := datautils.NewJobRunLookupDataFromInputBytes([]byte(json.RawMessage(*data)))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create job run lookup data: %w", err)
|
||||
|
||||
@@ -130,6 +130,8 @@ type WorkerRegisterRequest struct {
|
||||
WorkerName string `protobuf:"bytes,2,opt,name=workerName,proto3" json:"workerName,omitempty"`
|
||||
// a list of actions that this worker can run
|
||||
Actions []string `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"`
|
||||
// (optional) the services for this worker
|
||||
Services []string `protobuf:"bytes,4,rep,name=services,proto3" json:"services,omitempty"`
|
||||
}
|
||||
|
||||
func (x *WorkerRegisterRequest) Reset() {
|
||||
@@ -185,6 +187,13 @@ func (x *WorkerRegisterRequest) GetActions() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WorkerRegisterRequest) GetServices() []string {
|
||||
if x != nil {
|
||||
return x.Services
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WorkerRegisterResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -733,115 +742,117 @@ var file_dispatcher_proto_rawDesc = []byte{
|
||||
0x0a, 0x10, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x6d, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67,
|
||||
0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f,
|
||||
0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x22, 0x70, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69,
|
||||
0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x89, 0x01, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65,
|
||||
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72,
|
||||
0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22,
|
||||
0x70, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
|
||||
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e,
|
||||
0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e,
|
||||
0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d,
|
||||
0x65, 0x22, 0x9d, 0x02, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
|
||||
0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49,
|
||||
0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e,
|
||||
0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b,
|
||||
0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52,
|
||||
0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x61,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61,
|
||||
0x64, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x65,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61,
|
||||
0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61,
|
||||
0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64,
|
||||
0x22, 0x52, 0x0a, 0x18, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61,
|
||||
0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x22, 0x9d, 0x02, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65,
|
||||
0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6a, 0x6f, 0x62,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52,
|
||||
0x75, 0x6e, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70,
|
||||
0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
|
||||
0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
|
||||
0x64, 0x12, 0x2b, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x18,
|
||||
0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79,
|
||||
0x70, 0x65, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24,
|
||||
0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18,
|
||||
0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79,
|
||||
0x6c, 0x6f, 0x61, 0x64, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74,
|
||||
0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74,
|
||||
0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65,
|
||||
0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65,
|
||||
0x72, 0x49, 0x64, 0x22, 0x52, 0x0a, 0x18, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x19, 0x57, 0x6f, 0x72, 0x6b, 0x65,
|
||||
0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||
0x65, 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x19, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e,
|
||||
0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe1, 0x02, 0x0a, 0x0b, 0x41, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e,
|
||||
0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e,
|
||||
0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75,
|
||||
0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75,
|
||||
0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73,
|
||||
0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||
0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x09, 0x65, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x41,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09,
|
||||
0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x4d, 0x0a,
|
||||
0x13, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe1, 0x02, 0x0a,
|
||||
0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f,
|
||||
0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f,
|
||||
0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x09,
|
||||
0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x10, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
|
||||
0x65, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c,
|
||||
0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
0x22, 0x4d, 0x0a, 0x13, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x2a,
|
||||
0x35, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a,
|
||||
0x0e, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55, 0x4e, 0x10,
|
||||
0x00, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x45, 0x50,
|
||||
0x5f, 0x52, 0x55, 0x4e, 0x10, 0x01, 0x2a, 0x86, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54,
|
||||
0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e,
|
||||
0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f,
|
||||
0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54,
|
||||
0x45, 0x44, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45,
|
||||
0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45,
|
||||
0x44, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e,
|
||||
0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x32,
|
||||
0x81, 0x02, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x3d,
|
||||
0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x16, 0x2e, 0x57, 0x6f, 0x72,
|
||||
0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x17, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73,
|
||||
0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a,
|
||||
0x06, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x14, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e,
|
||||
0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00,
|
||||
0x30, 0x01, 0x12, 0x37, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x1a, 0x14, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, 0x55,
|
||||
0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x19, 0x2e, 0x57, 0x6f, 0x72,
|
||||
0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e,
|
||||
0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61,
|
||||
0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68,
|
||||
0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x2a, 0x35, 0x0a, 0x0a,
|
||||
0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54,
|
||||
0x41, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55, 0x4e, 0x10, 0x00, 0x12, 0x13,
|
||||
0x0a, 0x0f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55,
|
||||
0x4e, 0x10, 0x01, 0x2a, 0x86, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f,
|
||||
0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,
|
||||
0x57, 0x4e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45,
|
||||
0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10,
|
||||
0x01, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f,
|
||||
0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02,
|
||||
0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54,
|
||||
0x59, 0x50, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x32, 0x81, 0x02, 0x0a,
|
||||
0x0a, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x08, 0x52,
|
||||
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x16, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72,
|
||||
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x17, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x65, 0x6e, 0x12, 0x14, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x41, 0x73, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x30, 0x01, 0x12,
|
||||
0x37, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x1a, 0x14, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, 0x55, 0x6e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x19, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72,
|
||||
0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68,
|
||||
0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68,
|
||||
0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -85,24 +85,18 @@ func (s *DispatcherImpl) Register(ctx context.Context, request *contracts.Worker
|
||||
|
||||
s.l.Debug().Msgf("Received register request from ID %s with actions %v", request.WorkerName, request.Actions)
|
||||
|
||||
// // get a list of step ids based on a list of action ids for the tenant
|
||||
// steps, err := s.repo.Step().ListStepsByActions(request.TenantId, request.Actions)
|
||||
svcs := request.Services
|
||||
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// stepIds := make([]string, len(steps))
|
||||
|
||||
// for i, step := range steps {
|
||||
// stepIds[i] = step.ID
|
||||
// }
|
||||
if svcs == nil || len(svcs) == 0 {
|
||||
svcs = []string{"default"}
|
||||
}
|
||||
|
||||
// create a worker in the database
|
||||
worker, err := s.repo.Worker().CreateNewWorker(request.TenantId, &repository.CreateWorkerOpts{
|
||||
DispatcherId: s.dispatcherId,
|
||||
Name: request.WorkerName,
|
||||
Actions: request.Actions,
|
||||
Services: svcs,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -23,8 +23,9 @@ type Server struct {
|
||||
dispatchercontracts.UnimplementedDispatcherServer
|
||||
admincontracts.UnimplementedWorkflowServiceServer
|
||||
|
||||
l *zerolog.Logger
|
||||
port int
|
||||
l *zerolog.Logger
|
||||
port int
|
||||
bindAddress string
|
||||
|
||||
ingestor ingestor.Ingestor
|
||||
dispatcher dispatcher.Dispatcher
|
||||
@@ -35,20 +36,22 @@ type Server struct {
|
||||
type ServerOpt func(*ServerOpts)
|
||||
|
||||
type ServerOpts struct {
|
||||
l *zerolog.Logger
|
||||
port int
|
||||
ingestor ingestor.Ingestor
|
||||
dispatcher dispatcher.Dispatcher
|
||||
admin admin.AdminService
|
||||
tls *tls.Config
|
||||
l *zerolog.Logger
|
||||
port int
|
||||
bindAddress string
|
||||
ingestor ingestor.Ingestor
|
||||
dispatcher dispatcher.Dispatcher
|
||||
admin admin.AdminService
|
||||
tls *tls.Config
|
||||
}
|
||||
|
||||
func defaultServerOpts() *ServerOpts {
|
||||
logger := zerolog.New(os.Stderr)
|
||||
|
||||
return &ServerOpts{
|
||||
l: &logger,
|
||||
port: 7070,
|
||||
l: &logger,
|
||||
port: 7070,
|
||||
bindAddress: "127.0.0.1",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +61,12 @@ func WithLogger(l *zerolog.Logger) ServerOpt {
|
||||
}
|
||||
}
|
||||
|
||||
func WithBindAddress(bindAddress string) ServerOpt {
|
||||
return func(opts *ServerOpts) {
|
||||
opts.bindAddress = bindAddress
|
||||
}
|
||||
}
|
||||
|
||||
func WithPort(port int) ServerOpt {
|
||||
return func(opts *ServerOpts) {
|
||||
opts.port = port
|
||||
@@ -100,12 +109,13 @@ func NewServer(fs ...ServerOpt) (*Server, error) {
|
||||
}
|
||||
|
||||
return &Server{
|
||||
l: opts.l,
|
||||
port: opts.port,
|
||||
ingestor: opts.ingestor,
|
||||
dispatcher: opts.dispatcher,
|
||||
admin: opts.admin,
|
||||
tls: opts.tls,
|
||||
l: opts.l,
|
||||
port: opts.port,
|
||||
bindAddress: opts.bindAddress,
|
||||
ingestor: opts.ingestor,
|
||||
dispatcher: opts.dispatcher,
|
||||
admin: opts.admin,
|
||||
tls: opts.tls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -114,9 +124,9 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (s *Server) startGRPC(ctx context.Context) error {
|
||||
s.l.Debug().Msgf("starting grpc server on port %d", s.port)
|
||||
s.l.Debug().Msgf("starting grpc server on %s:%d", s.bindAddress, s.port)
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", s.port))
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddress, s.port))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen: %w", err)
|
||||
|
||||
@@ -479,7 +479,7 @@ func (ec *JobsControllerImpl) queueStepRun(ctx context.Context, tenantId, stepId
|
||||
return fmt.Errorf("could not get step inputs: %w", err)
|
||||
}
|
||||
|
||||
err = datautils.RenderTemplateFields(lookupDataMap, inputDataMap)
|
||||
inputDataMap, err = datautils.RenderTemplateFields(lookupDataMap, inputDataMap)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not render template fields: %w", err)
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/internal/config/loader"
|
||||
"github.com/hatchet-dev/hatchet/internal/validator"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client/loader"
|
||||
"github.com/hatchet-dev/hatchet/pkg/client/types"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
// TODO: add validator to client side
|
||||
type GetActionListenerRequest struct {
|
||||
WorkerName string
|
||||
Services []string
|
||||
Actions []string
|
||||
}
|
||||
|
||||
@@ -157,6 +158,7 @@ func (d *dispatcherClientImpl) newActionListener(ctx context.Context, req *GetAc
|
||||
TenantId: d.tenantId,
|
||||
WorkerName: req.WorkerName,
|
||||
Actions: req.Actions,
|
||||
Services: req.Services,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
54
pkg/client/loader/loader.go
Normal file
54
pkg/client/loader/loader.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/internal/config/client"
|
||||
"github.com/hatchet-dev/hatchet/internal/config/loader/loaderutils"
|
||||
)
|
||||
|
||||
type ConfigLoader struct {
|
||||
directory string
|
||||
}
|
||||
|
||||
// LoadClientConfig loads the client configuration
|
||||
func (c *ConfigLoader) LoadClientConfig() (res *client.ClientConfig, err error) {
|
||||
sharedFilePath := filepath.Join(c.directory, "client.yaml")
|
||||
configFileBytes, err := loaderutils.GetConfigBytes(sharedFilePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cf, err := LoadClientConfigFile(configFileBytes...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetClientConfigFromConfigFile(cf)
|
||||
}
|
||||
|
||||
// LoadClientConfigFile loads the worker config file via viper
|
||||
func LoadClientConfigFile(files ...[]byte) (*client.ClientConfigFile, error) {
|
||||
configFile := &client.ClientConfigFile{}
|
||||
f := client.BindAllEnv
|
||||
|
||||
_, err := loaderutils.LoadConfigFromViper(f, configFile, files...)
|
||||
|
||||
return configFile, err
|
||||
}
|
||||
|
||||
func GetClientConfigFromConfigFile(cf *client.ClientConfigFile) (res *client.ClientConfig, err error) {
|
||||
tlsConf, err := loaderutils.LoadClientTLSConfig(&cf.TLS)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load TLS config: %w", err)
|
||||
}
|
||||
|
||||
return &client.ClientConfig{
|
||||
TenantId: cf.TenantId,
|
||||
TLSConfig: tlsConf,
|
||||
}, nil
|
||||
}
|
||||
@@ -35,6 +35,10 @@ func ParseActionID(actionID string) (Action, error) {
|
||||
parts := strings.Split(actionID, ":")
|
||||
numParts := len(parts)
|
||||
|
||||
if numParts < 2 || numParts > 3 {
|
||||
return Action{}, fmt.Errorf("invalid action id %s, must have at least 2 strings separated : (colon)", actionID)
|
||||
}
|
||||
|
||||
integrationId := firstToLower(parts[0])
|
||||
verb := strings.ToLower(parts[1])
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Adapted from: https://github.com/hatchet-dev/hatchet/blob/3c2c13168afa1af68d4baaf5ed02c9d49c5f0323/cmd/cmdutils/interrupt.go
|
||||
// Adapted from: https://github.com/hatchet-dev/hatchet-v1-archived/blob/3c2c13168afa1af68d4baaf5ed02c9d49c5f0323/cmd/cmdutils/interrupt.go
|
||||
package cmdutils
|
||||
|
||||
import (
|
||||
@@ -22,7 +22,13 @@ func InterruptChan() <-chan interface{} {
|
||||
return ret
|
||||
}
|
||||
|
||||
func InterruptContext(interruptChan <-chan interface{}) (context.Context, context.CancelFunc) {
|
||||
func NewInterruptContext() (context.Context, context.CancelFunc) {
|
||||
interruptChan := InterruptChan()
|
||||
|
||||
return InterruptContextFromChan(interruptChan)
|
||||
}
|
||||
|
||||
func InterruptContextFromChan(interruptChan <-chan interface{}) (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go func() {
|
||||
@@ -32,3 +32,69 @@ func decodeArgsToInterface(fnType reflect.Type) (result interface{}, err error)
|
||||
|
||||
return reflect.New(secondArgElem).Interface(), nil
|
||||
}
|
||||
|
||||
func decodeFnArgTypes(fnType reflect.Type) (result []reflect.Type, err error) {
|
||||
if fnType.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("method must be a function")
|
||||
}
|
||||
|
||||
// if not a function with two arguments, return error
|
||||
if fnType.NumIn() != 2 {
|
||||
return nil, fmt.Errorf("method must have exactly two arguments")
|
||||
}
|
||||
|
||||
// if first argument is not a context, return error
|
||||
firstArg := fnType.In(0)
|
||||
|
||||
if firstArg.Kind() != reflect.Interface || !firstArg.Implements(reflect.TypeOf((*context.Context)(nil)).Elem()) {
|
||||
return nil, fmt.Errorf("first argument must be context.Context")
|
||||
}
|
||||
|
||||
// if second argument is not a pointer to a struct, return error
|
||||
secondArg := fnType.In(1)
|
||||
|
||||
if secondArg.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("second argument must be a pointer to a struct")
|
||||
}
|
||||
|
||||
secondArgElem := secondArg.Elem()
|
||||
|
||||
if secondArgElem.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("second argument must be a pointer to a struct")
|
||||
}
|
||||
|
||||
return []reflect.Type{firstArg, secondArg}, nil
|
||||
}
|
||||
|
||||
func decodeFnReturnTypes(fnType reflect.Type) (result []reflect.Type, err error) {
|
||||
if fnType.NumOut() > 2 {
|
||||
return nil, fmt.Errorf("fn cannot have more than 2 return values")
|
||||
}
|
||||
|
||||
firstOut := fnType.Out(0)
|
||||
|
||||
// if there are two args, the first one should be a pointer to a struct
|
||||
if fnType.NumOut() == 2 {
|
||||
if firstOut.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("first argument must be a pointer to a struct when there are two return values")
|
||||
}
|
||||
|
||||
firstOutElem := firstOut.Elem()
|
||||
|
||||
if firstOutElem.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("first argument must be a pointer to a struct when there are two return values")
|
||||
}
|
||||
}
|
||||
|
||||
lastOut := fnType.Out(fnType.NumOut() - 1)
|
||||
|
||||
if lastOut.Kind() != reflect.Interface || !lastOut.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
return nil, fmt.Errorf("last return value must be error")
|
||||
}
|
||||
|
||||
if fnType.NumOut() == 1 {
|
||||
return []reflect.Type{firstOut}, nil
|
||||
}
|
||||
|
||||
return []reflect.Type{firstOut, lastOut}, nil
|
||||
}
|
||||
|
||||
@@ -40,28 +40,26 @@ func getFnFromMethod(method any) (result actionFunc, err error) {
|
||||
}
|
||||
|
||||
// if function does not return two values, return error
|
||||
if methodType.NumOut() != 2 {
|
||||
return nil, fmt.Errorf("method must return exactly two values")
|
||||
if methodType.NumOut() == 2 {
|
||||
// if first return value is not a pointer to a struct, return error
|
||||
firstReturn := methodType.Out(0)
|
||||
|
||||
if firstReturn.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("first return value must be a pointer to a struct")
|
||||
}
|
||||
|
||||
firstReturnElem := firstReturn.Elem()
|
||||
|
||||
if firstReturnElem.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("first return value must be a pointer to a struct")
|
||||
}
|
||||
}
|
||||
|
||||
// if first return value is not a pointer to a struct, return error
|
||||
// firstReturn := methodType.Out(0)
|
||||
// if last return value is not an error, return error
|
||||
lastReturn := methodType.Out(methodType.NumOut() - 1)
|
||||
|
||||
// if firstReturn.Kind() != reflect.Ptr {
|
||||
// return nil, fmt.Errorf("first return value must be a pointer to a struct")
|
||||
// }
|
||||
|
||||
// firstReturnElem := firstReturn.Elem()
|
||||
|
||||
// if firstReturnElem.Kind() != reflect.Struct {
|
||||
// return nil, fmt.Errorf("first return value must be a pointer to a struct")
|
||||
// }
|
||||
|
||||
// if second return value is not an error, return error
|
||||
secondReturn := methodType.Out(1)
|
||||
|
||||
if secondReturn.Kind() != reflect.Interface || !secondReturn.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
return nil, fmt.Errorf("second return value must be an error")
|
||||
if lastReturn.Kind() != reflect.Interface || !lastReturn.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
return nil, fmt.Errorf("second return value must be of type error")
|
||||
}
|
||||
|
||||
return func(args ...interface{}) []interface{} {
|
||||
@@ -77,6 +75,12 @@ func getFnFromMethod(method any) (result actionFunc, err error) {
|
||||
})
|
||||
|
||||
// Return the results as an interface slice
|
||||
return []interface{}{values[0].Interface(), values[1].Interface()}
|
||||
res := []interface{}{}
|
||||
|
||||
for i := range values {
|
||||
res = append(res, values[i].Interface())
|
||||
}
|
||||
|
||||
return res
|
||||
}, nil
|
||||
}
|
||||
|
||||
60
pkg/worker/service.go
Normal file
60
pkg/worker/service.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/pkg/client/types"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Name string
|
||||
|
||||
worker *Worker
|
||||
}
|
||||
|
||||
func (s *Service) On(t triggerConverter, workflow workflowConverter) error {
|
||||
apiWorkflow := workflow.ToWorkflow(s.Name)
|
||||
|
||||
wt := &types.WorkflowTriggers{}
|
||||
|
||||
t.ToWorkflowTriggers(wt)
|
||||
|
||||
apiWorkflow.Triggers = *wt
|
||||
|
||||
// create the workflow via the API
|
||||
err := s.worker.client.Admin().PutWorkflow(&apiWorkflow)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register all steps as actions
|
||||
for actionId, fn := range workflow.ToActionMap(s.Name) {
|
||||
err := s.worker.registerAction(actionId, fn)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) RegisterAction(fn any) error {
|
||||
fnType := reflect.TypeOf(fn)
|
||||
|
||||
if fnType.Kind() != reflect.Func {
|
||||
return fmt.Errorf("method must be a function")
|
||||
}
|
||||
|
||||
if fnType.Name() == "" {
|
||||
return fmt.Errorf("function cannot be anonymous")
|
||||
}
|
||||
|
||||
fnId := fnType.Name()
|
||||
|
||||
actionId := fmt.Sprintf("%s:%s", s.Name, fnId)
|
||||
|
||||
return s.worker.registerAction(actionId, fn)
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/integrations"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type actionFunc func(args ...any) []any
|
||||
@@ -47,11 +46,7 @@ func (j *actionImpl) MethodFn() any {
|
||||
}
|
||||
|
||||
type Worker struct {
|
||||
conn *grpc.ClientConn
|
||||
client client.DispatcherClient
|
||||
|
||||
// The worker id that gets assigned on register
|
||||
workerId string
|
||||
client client.Client
|
||||
|
||||
name string
|
||||
|
||||
@@ -60,12 +55,14 @@ type Worker struct {
|
||||
l *zerolog.Logger
|
||||
|
||||
cancelMap sync.Map
|
||||
|
||||
services sync.Map
|
||||
}
|
||||
|
||||
type WorkerOpt func(*WorkerOpts)
|
||||
|
||||
type WorkerOpts struct {
|
||||
client client.DispatcherClient
|
||||
client client.Client
|
||||
name string
|
||||
l *zerolog.Logger
|
||||
|
||||
@@ -88,7 +85,7 @@ func WithName(name string) WorkerOpt {
|
||||
}
|
||||
}
|
||||
|
||||
func WithDispatcherClient(client client.DispatcherClient) WorkerOpt {
|
||||
func WithClient(client client.Client) WorkerOpt {
|
||||
return func(opts *WorkerOpts) {
|
||||
opts.client = client
|
||||
}
|
||||
@@ -123,7 +120,7 @@ func NewWorker(fs ...WorkerOpt) (*Worker, error) {
|
||||
for _, integrationAction := range actions {
|
||||
action := fmt.Sprintf("%s:%s", integrationId, integrationAction)
|
||||
|
||||
err := w.RegisterAction(action, integration.ActionHandler(integrationAction))
|
||||
err := w.registerAction(action, integration.ActionHandler(integrationAction))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not register integration action %s: %w", action, err)
|
||||
@@ -131,10 +128,45 @@ func NewWorker(fs ...WorkerOpt) (*Worker, error) {
|
||||
}
|
||||
}
|
||||
|
||||
w.NewService("default")
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *Worker) NewService(name string) *Service {
|
||||
svc := &Service{
|
||||
Name: name,
|
||||
worker: w,
|
||||
}
|
||||
|
||||
w.services.Store(name, svc)
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func (w *Worker) On(t triggerConverter, workflow workflowConverter) error {
|
||||
// get the default service
|
||||
svc, ok := w.services.Load("default")
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("could not load default service")
|
||||
}
|
||||
|
||||
return svc.(*Service).On(t, workflow)
|
||||
}
|
||||
|
||||
func (w *Worker) RegisterAction(name string, method any) error {
|
||||
// get the default service
|
||||
svc, ok := w.services.Load("default")
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("could not load default service")
|
||||
}
|
||||
|
||||
return svc.(*Service).RegisterAction(method)
|
||||
}
|
||||
|
||||
func (w *Worker) registerAction(name string, method any) error {
|
||||
actionFunc, err := getFnFromMethod(method)
|
||||
|
||||
if err != nil {
|
||||
@@ -163,7 +195,7 @@ func (w *Worker) Start(ctx context.Context) error {
|
||||
actionNames = append(actionNames, job.Name())
|
||||
}
|
||||
|
||||
listener, err := w.client.GetActionListener(ctx, &client.GetActionListenerRequest{
|
||||
listener, err := w.client.Dispatcher().GetActionListener(ctx, &client.GetActionListenerRequest{
|
||||
WorkerName: w.name,
|
||||
Actions: actionNames,
|
||||
})
|
||||
@@ -195,13 +227,10 @@ RunWorker:
|
||||
}
|
||||
|
||||
w.l.Debug().Msgf("action %s completed with result %v", action.ActionId, res)
|
||||
|
||||
return
|
||||
}(action)
|
||||
case <-ctx.Done():
|
||||
w.l.Debug().Msgf("worker %s received context done, stopping", w.name)
|
||||
break RunWorker
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +257,7 @@ func (w *Worker) executeAction(ctx context.Context, assignedAction *client.Actio
|
||||
|
||||
func (w *Worker) startStepRun(ctx context.Context, assignedAction *client.Action) (result any, err error) {
|
||||
// send a message that the step run started
|
||||
_, err = w.client.SendActionEvent(
|
||||
_, err = w.client.Dispatcher().SendActionEvent(
|
||||
ctx,
|
||||
w.getActionEvent(assignedAction, client.ActionEventTypeStarted),
|
||||
)
|
||||
@@ -269,9 +298,11 @@ func (w *Worker) startStepRun(ctx context.Context, assignedAction *client.Action
|
||||
default:
|
||||
}
|
||||
|
||||
result = runResults[0]
|
||||
if len(runResults) == 2 {
|
||||
result = runResults[0]
|
||||
}
|
||||
|
||||
if runResults[1] != nil {
|
||||
if runResults[len(runResults)-1] != nil {
|
||||
err = runResults[1].(error)
|
||||
}
|
||||
|
||||
@@ -280,7 +311,7 @@ func (w *Worker) startStepRun(ctx context.Context, assignedAction *client.Action
|
||||
|
||||
failureEvent.EventPayload = err.Error()
|
||||
|
||||
_, err := w.client.SendActionEvent(
|
||||
_, err := w.client.Dispatcher().SendActionEvent(
|
||||
ctx,
|
||||
failureEvent,
|
||||
)
|
||||
@@ -292,13 +323,6 @@ func (w *Worker) startStepRun(ctx context.Context, assignedAction *client.Action
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: check last argument for error
|
||||
|
||||
// if err != nil {
|
||||
// // TODO: send a message that the step run failed
|
||||
// return nil, fmt.Errorf("could not run job: %w", err)
|
||||
// }
|
||||
|
||||
// send a message that the step run completed
|
||||
finishedEvent, err := w.getActionFinishedEvent(assignedAction, result)
|
||||
|
||||
@@ -306,7 +330,7 @@ func (w *Worker) startStepRun(ctx context.Context, assignedAction *client.Action
|
||||
return nil, fmt.Errorf("could not create finished event: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.client.SendActionEvent(
|
||||
_, err = w.client.Dispatcher().SendActionEvent(
|
||||
ctx,
|
||||
finishedEvent,
|
||||
)
|
||||
|
||||
201
pkg/worker/workflow.go
Normal file
201
pkg/worker/workflow.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/pkg/client/types"
|
||||
)
|
||||
|
||||
type triggerConverter interface {
|
||||
ToWorkflowTriggers(*types.WorkflowTriggers)
|
||||
}
|
||||
|
||||
type Cron string
|
||||
|
||||
func (c Cron) ToWorkflowTriggers(wt *types.WorkflowTriggers) {
|
||||
if wt.Cron == nil {
|
||||
wt.Cron = []string{}
|
||||
}
|
||||
|
||||
wt.Cron = append(wt.Cron, string(c))
|
||||
}
|
||||
|
||||
type Event string
|
||||
|
||||
func (e Event) ToWorkflowTriggers(wt *types.WorkflowTriggers) {
|
||||
if wt.Events == nil {
|
||||
wt.Events = []string{}
|
||||
}
|
||||
|
||||
wt.Events = append(wt.Events, string(e))
|
||||
}
|
||||
|
||||
type workflowConverter interface {
|
||||
ToWorkflow(svcName string) types.Workflow
|
||||
ToActionMap(svcName string) map[string]any
|
||||
}
|
||||
|
||||
type Workflow struct {
|
||||
Jobs []WorkflowJob
|
||||
}
|
||||
|
||||
type WorkflowJob struct {
|
||||
// The name of the job
|
||||
Name string
|
||||
|
||||
Description string
|
||||
|
||||
Timeout string
|
||||
|
||||
// The steps that are run in the job
|
||||
Steps []WorkflowStep
|
||||
}
|
||||
|
||||
func (j *WorkflowJob) ToWorkflow(svcName string) types.Workflow {
|
||||
apiJob, err := j.ToWorkflowJob(svcName)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
jobs := map[string]types.WorkflowJob{
|
||||
j.Name: *apiJob,
|
||||
}
|
||||
|
||||
return types.Workflow{
|
||||
Name: j.Name,
|
||||
Version: "v0.1.0",
|
||||
Jobs: jobs,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WorkflowJob) ToWorkflowJob(svcName string) (*types.WorkflowJob, error) {
|
||||
apiJob := &types.WorkflowJob{
|
||||
Description: j.Description,
|
||||
Timeout: j.Timeout,
|
||||
Steps: []types.WorkflowStep{},
|
||||
}
|
||||
|
||||
var prevStep *step
|
||||
|
||||
for i, step := range j.Steps {
|
||||
newStep, err := step.ToWorkflowStep(prevStep, svcName, i)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiJob.Steps = append(apiJob.Steps, newStep.APIStep)
|
||||
|
||||
prevStep = newStep
|
||||
}
|
||||
|
||||
return apiJob, nil
|
||||
}
|
||||
|
||||
func (j *WorkflowJob) ToActionMap(svcName string) map[string]any {
|
||||
res := map[string]any{}
|
||||
|
||||
for i, step := range j.Steps {
|
||||
actionId := step.GetActionId(svcName, i)
|
||||
|
||||
res[actionId] = step.Function
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type WorkflowStep struct {
|
||||
// The step timeout
|
||||
Timeout string
|
||||
|
||||
// The executed function
|
||||
Function any
|
||||
}
|
||||
|
||||
type step struct {
|
||||
Id string
|
||||
|
||||
// non-ctx input is not optional
|
||||
NonCtxInput reflect.Type
|
||||
|
||||
// non-err output is optional
|
||||
NonErrOutput *reflect.Type
|
||||
|
||||
APIStep types.WorkflowStep
|
||||
}
|
||||
|
||||
func (s *WorkflowStep) ToWorkflowStep(prevStep *step, svcName string, index int) (*step, error) {
|
||||
fnType := reflect.TypeOf(s.Function)
|
||||
|
||||
res := &step{}
|
||||
|
||||
res.Id = s.GetStepId(index)
|
||||
|
||||
res.APIStep = types.WorkflowStep{
|
||||
Name: res.Id,
|
||||
ID: s.GetStepId(index),
|
||||
Timeout: s.Timeout,
|
||||
ActionID: s.GetActionId(svcName, index),
|
||||
}
|
||||
|
||||
inputs, err := decodeFnArgTypes(fnType)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.NonCtxInput = inputs[1]
|
||||
|
||||
outputs, err := decodeFnReturnTypes(fnType)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(outputs) > 1 {
|
||||
res.NonErrOutput = &outputs[0]
|
||||
}
|
||||
|
||||
// if the previous step's first output matches the last input of this step, then the data
|
||||
// is passed through
|
||||
if prevStep != nil && prevStep.NonErrOutput != nil {
|
||||
if inputs[1] == *prevStep.NonErrOutput {
|
||||
res.APIStep.With = map[string]interface{}{
|
||||
"object": "{{ .steps." + prevStep.Id + ".json }}",
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.APIStep.With = map[string]interface{}{
|
||||
"object": "{{ .input.json }}",
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *WorkflowStep) GetStepId(index int) string {
|
||||
stepId := s.getFnName()
|
||||
|
||||
// this can happen if the function is anonymous
|
||||
if stepId == "" {
|
||||
stepId = fmt.Sprintf("step%d", index)
|
||||
}
|
||||
|
||||
return stepId
|
||||
}
|
||||
|
||||
func (s *WorkflowStep) GetActionId(svcName string, index int) string {
|
||||
stepId := s.GetStepId(index)
|
||||
|
||||
return fmt.Sprintf("%s:%s", svcName, stepId)
|
||||
}
|
||||
|
||||
func (s *WorkflowStep) getFnName() string {
|
||||
fnName := runtime.FuncForPC(reflect.ValueOf(s.Function).Pointer()).Name()
|
||||
|
||||
return strings.Split(fnName, ".")[1]
|
||||
}
|
||||
48
pkg/worker/workflow_test.go
Normal file
48
pkg/worker/workflow_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package worker
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "testing"
|
||||
// )
|
||||
|
||||
// // type actionInput struct {
|
||||
// // Message string `json:"message"`
|
||||
// // }
|
||||
|
||||
// // type stepOneOutput struct {
|
||||
// // Message string `json:"message"`
|
||||
// // }
|
||||
|
||||
// // type stepTwoOutput struct {
|
||||
// // Message string `json:"message"`
|
||||
// // }
|
||||
|
||||
// // func TestToWorkflowJob(t *testing.T) {
|
||||
// // testJob := WorkflowJob{
|
||||
// // Name: "test",
|
||||
// // Description: "test",
|
||||
// // Timeout: "1m",
|
||||
// // Steps: []WorkflowStep{
|
||||
// // {
|
||||
// // ActionId: "test:test",
|
||||
// // Function: func(ctx context.Context, input *actionInput) (result *stepOneOutput, err error) {
|
||||
// // return nil, nil
|
||||
// // },
|
||||
// // },
|
||||
// // {
|
||||
// // ActionId: "test:test",
|
||||
// // Function: func(ctx context.Context, input *stepOneOutput) (result *stepTwoOutput, err error) {
|
||||
// // return nil, nil
|
||||
// // },
|
||||
// // },
|
||||
// // },
|
||||
// // }
|
||||
|
||||
// // job, err := testJob.ToWorkflowJob()
|
||||
|
||||
// // if err != nil {
|
||||
// // t.Fatalf("could not convert workflow job: %v", err)
|
||||
// // }
|
||||
|
||||
// // t.Fatalf("%v", job)
|
||||
// // }
|
||||
@@ -318,6 +318,19 @@ CREATE TABLE "Worker" (
|
||||
CONSTRAINT "Worker_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Service" (
|
||||
"id" UUID NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"tenantId" UUID NOT NULL,
|
||||
|
||||
CONSTRAINT "Service_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_WorkflowToWorkflowTag" (
|
||||
"A" UUID NOT NULL,
|
||||
@@ -330,6 +343,12 @@ CREATE TABLE "_ActionToWorker" (
|
||||
"B" UUID NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ServiceToWorker" (
|
||||
"A" UUID NOT NULL,
|
||||
"B" UUID NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_id_key" ON "User"("id");
|
||||
|
||||
@@ -444,6 +463,12 @@ CREATE UNIQUE INDEX "Ticker_id_key" ON "Ticker"("id");
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Worker_id_key" ON "Worker"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Service_id_key" ON "Service"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Service_tenantId_name_key" ON "Service"("tenantId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_WorkflowToWorkflowTag_AB_unique" ON "_WorkflowToWorkflowTag"("A", "B");
|
||||
|
||||
@@ -456,6 +481,12 @@ CREATE UNIQUE INDEX "_ActionToWorker_AB_unique" ON "_ActionToWorker"("A", "B");
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ActionToWorker_B_index" ON "_ActionToWorker"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_ServiceToWorker_AB_unique" ON "_ServiceToWorker"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ServiceToWorker_B_index" ON "_ServiceToWorker"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserPassword" ADD CONSTRAINT "UserPassword_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -579,6 +610,9 @@ ALTER TABLE "Worker" ADD CONSTRAINT "Worker_tenantId_fkey" FOREIGN KEY ("tenantI
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Worker" ADD CONSTRAINT "Worker_dispatcherId_fkey" FOREIGN KEY ("dispatcherId") REFERENCES "Dispatcher"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Service" ADD CONSTRAINT "Service_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_WorkflowToWorkflowTag" ADD CONSTRAINT "_WorkflowToWorkflowTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -590,3 +624,9 @@ ALTER TABLE "_ActionToWorker" ADD CONSTRAINT "_ActionToWorker_A_fkey" FOREIGN KE
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ActionToWorker" ADD CONSTRAINT "_ActionToWorker_B_fkey" FOREIGN KEY ("B") REFERENCES "Worker"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ServiceToWorker" ADD CONSTRAINT "_ServiceToWorker_A_fkey" FOREIGN KEY ("A") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ServiceToWorker" ADD CONSTRAINT "_ServiceToWorker_B_fkey" FOREIGN KEY ("B") REFERENCES "Worker"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -83,6 +83,7 @@ model Tenant {
|
||||
members TenantMember[]
|
||||
workflowTags WorkflowTag[]
|
||||
actions Action[]
|
||||
services Service[]
|
||||
}
|
||||
|
||||
enum TenantMemberRole {
|
||||
@@ -658,9 +659,33 @@ model Worker {
|
||||
dispatcher Dispatcher @relation(fields: [dispatcherId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
dispatcherId String @db.Uuid
|
||||
|
||||
services Service[]
|
||||
|
||||
// the actions this worker can run
|
||||
actions Action[]
|
||||
|
||||
// the jobs the worker has run
|
||||
stepRuns StepRun[]
|
||||
}
|
||||
|
||||
model Service {
|
||||
// base fields
|
||||
id String @id @unique @default(uuid()) @db.Uuid
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
deletedAt DateTime?
|
||||
|
||||
// the service name
|
||||
name String
|
||||
|
||||
// the service description
|
||||
description String?
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
tenantId String @db.Uuid
|
||||
|
||||
// the service's workers
|
||||
workers Worker[]
|
||||
|
||||
@@unique([tenantId, name])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user