diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index 7c8832afe..fa8f5f8c7 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -28,6 +28,30 @@ jobs: - name: Push to GHCR run: | docker push ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}} + build-push-hatchet-admin: + name: hatchet-admin + runs-on: ubuntu-latest + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@v3 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.Dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}} \ + --build-arg SERVER_TARGET=admin \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + . + - name: Push to GHCR + run: | + docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}} build-push-hatchet-engine: name: hatchet-engine runs-on: ubuntu-latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ce91dcabc..578f580cd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,6 +27,11 @@ jobs: docker pull ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}} docker tag ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}} ghcr.io/hatchet-dev/hatchet/hatchet-engine:latest docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine:latest + - name: Pull and push hatchet-admin + run: | + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}} + docker tag ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}} ghcr.io/hatchet-dev/hatchet/hatchet-admin:latest + docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin:latest - name: Pull and push hatchet-frontend run: | docker pull ghcr.io/hatchet-dev/hatchet/hatchet-frontend:${{steps.tag_name.outputs.tag}} diff --git a/.gitignore b/.gitignore index 48adbf18f..6fb4aaa9a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ dump.rdb *.csr *.pfx *.cert +generated node_modules diff --git a/Taskfile.yaml b/Taskfile.yaml index 6c4008d49..d0bbdec61 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -13,7 +13,7 @@ tasks: seed-dev: cmds: - sh ./hack/dev/run-npx-with-env.sh prisma db push --force-reset - - sh ./hack/dev/run-go-with-env.sh run ./cmd/seed + - SEED_DEVELOPMENT=true sh ./hack/dev/run-go-with-env.sh run ./cmd/hatchet-admin seed start-dev: deps: - task: start-api diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index f5aa2c912..2786bfdbe 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -49,7 +49,7 @@ const ( // Defines values for StepRunStatus. const ( StepRunStatusASSIGNED StepRunStatus = "ASSIGNED" - StepRunStatusCANCELLED StepRunStatus = "CANCELLED" + StepRunStatusCancelled StepRunStatus = "CANCELLED" StepRunStatusFAILED StepRunStatus = "FAILED" StepRunStatusPENDING StepRunStatus = "PENDING" StepRunStatusPENDINGASSIGNMENT StepRunStatus = "PENDING_ASSIGNMENT" diff --git a/build/package/servers.Dockerfile b/build/package/servers.Dockerfile index a473f7490..efed0737a 100644 --- a/build/package/servers.Dockerfile +++ b/build/package/servers.Dockerfile @@ -5,7 +5,17 @@ WORKDIR /hatchet RUN apk update && apk add --no-cache gcc musl-dev git protoc protobuf-dev +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 +RUN go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.0.0 + COPY go.mod go.sum ./ + +RUN go mod download + +# prefetch the binaries, so that they will be cached and not downloaded on each change +RUN go run github.com/steebchen/prisma-client-go prefetch + COPY /api ./api COPY /api-contracts ./api-contracts COPY /internal ./internal @@ -14,16 +24,6 @@ COPY /hack ./hack COPY /prisma ./prisma COPY /cmd ./cmd -RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 -RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 -RUN go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@latest - -RUN --mount=type=cache,target=$GOPATH/pkg/mod \ - go mod download - -# prefetch the binaries, so that they will be cached and not downloaded on each change -RUN go run github.com/steebchen/prisma-client-go prefetch - # generate the Prisma Client Go client RUN go run github.com/steebchen/prisma-client-go generate --generator go @@ -32,12 +32,12 @@ RUN go run github.com/steebchen/prisma-client-go generate --generator go FROM node:16-alpine as build-openapi WORKDIR /openapi -COPY /api-contracts/openapi ./openapi - RUN npm install -g npm@8.1 RUN npm install -g @apidevtools/swagger-cli prisma +COPY /api-contracts/openapi ./openapi + RUN swagger-cli bundle ./openapi/openapi.yaml --outfile ./bin/oas/openapi.yaml --type yaml # Go build environment @@ -49,9 +49,9 @@ ARG VERSION=v0.1.0-alpha.0 # can be set to "api" or "engine" ARG SERVER_TARGET -# check if the target is empty or not set to api or engine -RUN if [ -z "$SERVER_TARGET" ] || [ "$SERVER_TARGET" != "api" ] && [ "$SERVER_TARGET" != "engine" ]; then \ - echo "SERVER_TARGET must be set to 'api' or 'engine'"; \ +# check if the target is empty or not set to api, engine, or admin +RUN if [ -z "$SERVER_TARGET" ] || [ "$SERVER_TARGET" != "api" ] && [ "$SERVER_TARGET" != "engine" ] && [ "$SERVER_TARGET" != "admin" ]; then \ + echo "SERVER_TARGET must be set to 'api', 'engine', or 'admin'"; \ exit 1; \ fi @@ -73,8 +73,12 @@ FROM alpine AS deployment # can be set to "api" or "engine" ARG SERVER_TARGET=engine -RUN apk update && apk add --no-cache gcc musl-dev +WORKDIR /hatchet +# openssl and bash needed for admin build +RUN apk update && apk add --no-cache gcc musl-dev openssl bash + +COPY --from=base /hatchet/prisma ./prisma COPY --from=build-go /hatchet/bin/hatchet-${SERVER_TARGET} /hatchet/ EXPOSE 8080 diff --git a/cmd/hatchet-admin/cli/certs/cluster-cert.conf b/cmd/hatchet-admin/cli/certs/cluster-cert.conf new file mode 100644 index 000000000..0e6bff470 --- /dev/null +++ b/cmd/hatchet-admin/cli/certs/cluster-cert.conf @@ -0,0 +1,17 @@ +[req] +default_bits = 4096 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn +[dn] +C = US +ST = NY +O = Hatchet +CN = cluster +[req_ext] +subjectAltName = @alt_names +[alt_names] +DNS.1 = cluster +IP.1 = ::1 +IP.2 = 127.0.0.1 diff --git a/cmd/hatchet-admin/cli/certs/generate-certs.sh b/cmd/hatchet-admin/cli/certs/generate-certs.sh new file mode 100644 index 000000000..8fdad1c38 --- /dev/null +++ b/cmd/hatchet-admin/cli/certs/generate-certs.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# This scripts generates test keys and certificates for the sample. + +CERTS_DIR=$1 + +echo "generating certs in dir: $1" + +# Generate a private key and a certificate for a test certificate authority +openssl genrsa -out $CERTS_DIR/ca.key 4096 +openssl req -new -x509 -key $CERTS_DIR/ca.key -sha256 -subj "/C=US/ST=NY/O=Hatchet" -days 365 -out $CERTS_DIR/ca.cert + +# Generate a private key and a certificate for cluster +openssl genrsa -out $CERTS_DIR/cluster.key 4096 +openssl req -new -key $CERTS_DIR/cluster.key -out $CERTS_DIR/cluster.csr -config $CERTS_DIR/cluster-cert.conf +openssl x509 -req -in $CERTS_DIR/cluster.csr -CA $CERTS_DIR/ca.cert -CAkey $CERTS_DIR/ca.key -CAcreateserial -out $CERTS_DIR/cluster.pem -days 365 -sha256 -extfile $CERTS_DIR/cluster-cert.conf -extensions req_ext + +# Generate a private key and a certificate for internal admin client +openssl req -newkey rsa:4096 -nodes -keyout "$CERTS_DIR/client-internal-admin.key" -out "$CERTS_DIR/client-internal-admin.csr" -config $CERTS_DIR/internal-admin-client-cert.conf +openssl x509 -req -in $CERTS_DIR/client-internal-admin.csr -CA $CERTS_DIR/ca.cert -CAkey $CERTS_DIR/ca.key -CAcreateserial -out $CERTS_DIR/client-internal-admin.pem -days 365 -sha256 -extfile $CERTS_DIR/internal-admin-client-cert.conf -extensions req_ext + +# Generate a private key and a certificate for worker client +openssl req -newkey rsa:4096 -nodes -keyout "$CERTS_DIR/client-worker.key" -out "$CERTS_DIR/client-worker.csr" -config $CERTS_DIR/worker-client-cert.conf +openssl x509 -req -in $CERTS_DIR/client-worker.csr -CA $CERTS_DIR/ca.cert -CAkey $CERTS_DIR/ca.key -CAcreateserial -out $CERTS_DIR/client-worker.pem -days 365 -sha256 -extfile $CERTS_DIR/worker-client-cert.conf -extensions req_ext diff --git a/cmd/hatchet-admin/cli/certs/internal-admin-client-cert.conf b/cmd/hatchet-admin/cli/certs/internal-admin-client-cert.conf new file mode 100644 index 000000000..acda4b5aa --- /dev/null +++ b/cmd/hatchet-admin/cli/certs/internal-admin-client-cert.conf @@ -0,0 +1,17 @@ +[req] +default_bits = 4096 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn +[dn] +C = US +ST = NY +O = Hatchet +CN = internal-admin +[req_ext] +subjectAltName = @alt_names +[alt_names] +DNS.1 = localhost +IP.1 = ::1 +IP.2 = 127.0.0.1 diff --git a/cmd/hatchet-admin/cli/certs/worker-client-cert.conf b/cmd/hatchet-admin/cli/certs/worker-client-cert.conf new file mode 100644 index 000000000..c0e21e358 --- /dev/null +++ b/cmd/hatchet-admin/cli/certs/worker-client-cert.conf @@ -0,0 +1,17 @@ +[req] +default_bits = 4096 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn +[dn] +C = US +ST = NY +O = Hatchet +CN = worker +[req_ext] +subjectAltName = @alt_names +[alt_names] +DNS.1 = localhost +IP.1 = ::1 +IP.2 = 127.0.0.1 diff --git a/cmd/hatchet-admin/cli/quickstart.go b/cmd/hatchet-admin/cli/quickstart.go new file mode 100644 index 000000000..3505edf52 --- /dev/null +++ b/cmd/hatchet-admin/cli/quickstart.go @@ -0,0 +1,338 @@ +package cli + +import ( + _ "embed" + "io/ioutil" + + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/fatih/color" + "github.com/hatchet-dev/hatchet/internal/config/database" + "github.com/hatchet-dev/hatchet/internal/config/loader" + "github.com/hatchet-dev/hatchet/internal/config/server" + "github.com/hatchet-dev/hatchet/internal/encryption" + "sigs.k8s.io/yaml" + + "github.com/spf13/cobra" +) + +var certDir string +var generatedConfigDir string +var skip []string +var overwrite bool + +const ( + StageCerts string = "certs" + StageKeys string = "keys" + StageSeed string = "seed" +) + +var quickstartCmd = &cobra.Command{ + Use: "quickstart", + Short: "Command used to setup a Hatchet instance", + Run: func(cmd *cobra.Command, args []string) { + err := runQuickstart() + + if err != nil { + red := color.New(color.FgRed) + red.Printf("Error running [%s]:%s\n", cmd.Use, err.Error()) + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(quickstartCmd) + + quickstartCmd.PersistentFlags().StringVar( + &certDir, + "cert-dir", + "./certs", + "path to the directory where certificates should be stored", + ) + + quickstartCmd.PersistentFlags().StringVar( + &generatedConfigDir, + "generated-config-dir", + "./generated", + "path to the directory where the generated config should be written", + ) + + quickstartCmd.PersistentFlags().StringArrayVar( + &skip, + "skip", + []string{}, + "a list of steps to skip. possible values are \"certs\"", + ) + + quickstartCmd.PersistentFlags().BoolVar( + &overwrite, + "overwrite", + true, + "whether generated files should be overwritten, if they exist", + ) +} + +func runQuickstart() error { + generated, err := loadBaseConfigFiles() + + if err != nil { + return fmt.Errorf("could not get base config files: %w", err) + } + + if !shouldSkip(StageCerts) { + err := setupCerts(generated) + + if err != nil { + return fmt.Errorf("could not setup certs: %w", err) + } + } + + if !shouldSkip(StageKeys) { + err := generateKeys(generated) + + if err != nil { + return fmt.Errorf("could not generate keys: %w", err) + } + } + + err = writeGeneratedConfig(generated) + + if err != nil { + return fmt.Errorf("could not write generated config files: %w", err) + } + + if !shouldSkip(StageSeed) { + // reload config at this point + configLoader := loader.NewConfigLoader(configDirectory) + err = runSeed(configLoader) + + if err != nil { + return fmt.Errorf("could not run seed: %w", err) + } + } + + return nil +} + +func shouldSkip(stage string) bool { + for _, skipStage := range skip { + if stage == skipStage { + return true + } + } + + return false +} + +//go:embed certs/cluster-cert.conf +var ClusterCertConf []byte + +//go:embed certs/internal-admin-client-cert.conf +var InternalAdminClientCertConf []byte + +//go:embed certs/worker-client-cert.conf +var WorkerClientCertConf []byte + +//go:embed certs/generate-certs.sh +var GenerateCertsScript string + +type generatedConfigFiles struct { + sc *server.ServerConfigFile + dc *database.ConfigFile +} + +func setupCerts(generated *generatedConfigFiles) error { + color.New(color.FgGreen).Printf("Generating certificates in cert directory %s\n", certDir) + + // verify that bash and openssl are installed on the system + if !commandExists("openssl") { + return fmt.Errorf("openssl must be installed and available in your $PATH") + } + + if !commandExists("bash") { + return fmt.Errorf("bash must be installed and available in your $PATH") + } + + // write certificate config files to system + fullPathCertDir, err := filepath.Abs(certDir) + + if err != nil { + return err + } + + err = os.MkdirAll(fullPathCertDir, os.ModePerm) + + if err != nil { + return fmt.Errorf("could not create cert directory: %w", err) + } + + err = os.WriteFile(filepath.Join(fullPathCertDir, "./cluster-cert.conf"), ClusterCertConf, 0666) + + if err != nil { + return fmt.Errorf("could not create cluster-cert.conf file: %w", err) + } + + err = os.WriteFile(filepath.Join(fullPathCertDir, "./internal-admin-client-cert.conf"), InternalAdminClientCertConf, 0666) + + if err != nil { + return fmt.Errorf("could not create internal-admin-client-cert.conf file: %w", err) + } + + err = os.WriteFile(filepath.Join(fullPathCertDir, "./worker-client-cert.conf"), WorkerClientCertConf, 0666) + + if err != nil { + return fmt.Errorf("could not create worker-client-cert.conf file: %w", err) + } + + // run openssl commands + c := exec.Command("bash", "-s", "-", fullPathCertDir) + + c.Stdin = strings.NewReader(GenerateCertsScript) + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + err = c.Run() + + 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") + + return nil +} + +func generateKeys(generated *generatedConfigFiles) error { + color.New(color.FgGreen).Printf("Generating encryption keys for Hatchet server\n") + + cookieHashKey, err := encryption.GenerateRandomBytes(8) + + if err != nil { + return fmt.Errorf("could not generate hash key for instance: %w", err) + } + + cookieBlockKey, err := encryption.GenerateRandomBytes(8) + + if err != nil { + return fmt.Errorf("could not generate block key for instance: %w", err) + } + + if overwrite || (generated.sc.Auth.Cookie.Secrets == "") { + generated.sc.Auth.Cookie.Secrets = fmt.Sprintf("%s %s", cookieHashKey, cookieBlockKey) + } + + return nil +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func commandExists(cmd string) bool { + _, err := exec.LookPath(cmd) + return err == nil +} + +func loadBaseConfigFiles() (*generatedConfigFiles, error) { + res := &generatedConfigFiles{} + var err error + + res.dc, err = loader.LoadDatabaseConfigFile(getFiles("database.yaml")...) + + if err != nil { + return nil, err + } + + res.sc, err = loader.LoadServerConfigFile(getFiles("server.yaml")...) + + if err != nil { + return nil, err + } + + return res, nil +} + +func shouldWriteConfig(conf string) bool { + return overwrite || conf == "" +} + +func getFiles(name string) [][]byte { + files := [][]byte{} + + basePath := filepath.Join(configDirectory, name) + + if fileExists(basePath) { + configFileBytes, err := ioutil.ReadFile(basePath) + + if err != nil { + panic(err) + } + + files = append(files, configFileBytes) + } + + generatedPath := filepath.Join(generatedConfigDir, name) + + if fileExists(generatedPath) { + generatedFileBytes, err := ioutil.ReadFile(filepath.Join(generatedConfigDir, name)) + + if err != nil { + panic(err) + } + + files = append(files, generatedFileBytes) + } + + return files +} + +func writeGeneratedConfig(generated *generatedConfigFiles) error { + color.New(color.FgGreen).Printf("Generating config files %s\n", generatedConfigDir) + + err := os.MkdirAll(generatedConfigDir, os.ModePerm) + + if err != nil { + return fmt.Errorf("could not create generated config directory: %w", err) + } + + databasePath := filepath.Join(generatedConfigDir, "./database.yaml") + + databaseConfigBytes, err := yaml.Marshal(generated.dc) + + if err != nil { + return err + } + + err = ioutil.WriteFile(databasePath, databaseConfigBytes, 0666) + + if err != nil { + return fmt.Errorf("could not write database.yaml file: %w", err) + } + + serverPath := filepath.Join(generatedConfigDir, "./server.yaml") + + serverConfigBytes, err := yaml.Marshal(generated.sc) + + if err != nil { + return err + } + + err = ioutil.WriteFile(serverPath, serverConfigBytes, 0666) + + if err != nil { + return fmt.Errorf("could not write server.yaml file: %w", err) + } + + return nil +} diff --git a/cmd/hatchet-admin/cli/root.go b/cmd/hatchet-admin/cli/root.go new file mode 100644 index 000000000..101df7d75 --- /dev/null +++ b/cmd/hatchet-admin/cli/root.go @@ -0,0 +1,63 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// Version will be linked by an ldflag during build +var Version string = "v0.1.0-alpha.0" + +var printVersion bool + +var configDirectory string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "hatchet-admin", + Short: "hatchet-admin performs administrative tasks for a Hatchet instance.", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if printVersion { + fmt.Println(Version) + os.Exit(0) + } + + // var err error + + // configLoader := loader.NewConfigLoader(configDirectory) + // sc, err = configLoader.LoadServerConfig() + + // if err != nil { + // fmt.Printf("Fatal: could not load server config: %v\n", err) + // os.Exit(1) + // } + }, + Run: func(cmd *cobra.Command, args []string) { + return + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + rootCmd.PersistentFlags().BoolVar( + &printVersion, + "version", + false, + "print version and exit.", + ) + + rootCmd.PersistentFlags().StringVar( + &configDirectory, + "config", + "", + "The path the config folder.", + ) + + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/hatchet-admin/cli/seed.go b/cmd/hatchet-admin/cli/seed.go new file mode 100644 index 000000000..f986a38bd --- /dev/null +++ b/cmd/hatchet-admin/cli/seed.go @@ -0,0 +1,185 @@ +package cli + +import ( + "errors" + "fmt" + "os" + + "github.com/hatchet-dev/hatchet/internal/config/loader" + "github.com/hatchet-dev/hatchet/internal/datautils" + "github.com/hatchet-dev/hatchet/internal/repository" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" + "github.com/spf13/cobra" +) + +// seedCmd seeds the database with initial data +var seedCmd = &cobra.Command{ + Use: "seed", + Short: "seed create initial data in the database.", + Run: func(cmd *cobra.Command, args []string) { + var err error + + configLoader := loader.NewConfigLoader(configDirectory) + err = runSeed(configLoader) + + if err != nil { + fmt.Printf("Fatal: could not load server config: %v\n", err) + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(seedCmd) +} + +func runSeed(cf *loader.ConfigLoader) error { + // load the config + dc, err := cf.LoadDatabaseConfig() + + if err != nil { + panic(err) + } + + shouldSeedUser := dc.Seed.AdminEmail != "" && dc.Seed.AdminPassword != "" + var userId string + + if shouldSeedUser { + // seed an example user + hashedPw, err := repository.HashPassword(dc.Seed.AdminPassword) + + if err != nil { + return err + } + + user, err := dc.Repository.User().GetUserByEmail(dc.Seed.AdminEmail) + + if err != nil { + if errors.Is(err, db.ErrNotFound) { + user, err = dc.Repository.User().CreateUser(&repository.CreateUserOpts{ + Email: dc.Seed.AdminEmail, + Name: repository.StringPtr(dc.Seed.AdminName), + EmailVerified: repository.BoolPtr(true), + Password: *hashedPw, + }) + + if err != nil { + return err + } + } else { + return err + } + } + + userId = user.ID + } + + tenant, err := dc.Repository.Tenant().GetTenantBySlug("default") + + if err != nil { + if errors.Is(err, db.ErrNotFound) { + // seed an example tenant + // initialize a tenant + tenant, err = dc.Repository.Tenant().CreateTenant(&repository.CreateTenantOpts{ + Name: dc.Seed.DefaultTenantName, + Slug: dc.Seed.DefaultTenantSlug, + }) + + if err != nil { + return err + } + + fmt.Println("created tenant", tenant.ID) + + // add the user to the tenant + _, err = dc.Repository.Tenant().CreateTenantMember(tenant.ID, &repository.CreateTenantMemberOpts{ + Role: "OWNER", + UserId: userId, + }) + + if err != nil { + return err + } + } else { + return err + } + } + + if dc.Seed.IsDevelopment { + err = seedDev(dc.Repository, tenant.ID) + + if err != nil { + return err + } + } + + return nil +} + +func seedDev(repo repository.Repository, tenantId string) error { + // seed example workflows + firstInput, _ := datautils.ToJSONType(map[string]interface{}{ + "message": "Username is {{ .input.username }}", + }) + + secondInput, _ := datautils.ToJSONType(map[string]interface{}{ + "message": "Above message is: {{ .steps.echo1.message }}", + }) + + thirdInput, _ := datautils.ToJSONType(map[string]interface{}{ + "message": "Above message is: {{ .steps.echo1.message }}", + }) + + workflow, err := repo.Workflow().GetWorkflowByName(tenantId, "test-workflow") + + if err != nil { + if errors.Is(err, db.ErrNotFound) { + + _, err := repo.Workflow().CreateNewWorkflow(tenantId, &repository.CreateWorkflowVersionOpts{ + Name: "test-workflow", + Description: repository.StringPtr("This is a test workflow."), + Version: "v0.1.0", + EventTriggers: []string{ + "user:create", + }, + Tags: []repository.CreateWorkflowTagOpts{ + { + Name: "Preview", + }, + }, + Jobs: []repository.CreateWorkflowJobOpts{ + { + Name: "job-name", + Steps: []repository.CreateWorkflowStepOpts{ + { + ReadableId: "echo1", + Action: "echo:echo", + Inputs: firstInput, + }, + { + ReadableId: "echo2", + Action: "echo:echo", + Inputs: secondInput, + }, + { + ReadableId: "echo3", + Action: "echo:echo", + Inputs: thirdInput, + }, + }, + }, + }, + }) + + if err != nil { + return err + } + + fmt.Println("created workflow", workflow.ID, workflow.Name) + } + + return err + } + + return nil +} diff --git a/cmd/hatchet-admin/main.go b/cmd/hatchet-admin/main.go new file mode 100644 index 000000000..cb2843580 --- /dev/null +++ b/cmd/hatchet-admin/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/hatchet-dev/hatchet/cmd/hatchet-admin/cli" +) + +func main() { + cli.Execute() +} diff --git a/cmd/hatchet-api/main.go b/cmd/hatchet-api/main.go index e4535e218..caa6b5854 100644 --- a/cmd/hatchet-api/main.go +++ b/cmd/hatchet-api/main.go @@ -11,6 +11,7 @@ import ( ) var printVersion bool +var configDirectory string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ @@ -22,7 +23,7 @@ var rootCmd = &cobra.Command{ os.Exit(0) } - cf := &loader.ConfigLoader{} + cf := loader.NewConfigLoader(configDirectory) interruptChan := cmdutils.InterruptChan() startServerOrDie(cf, interruptChan) @@ -40,16 +41,21 @@ func main() { "print version and exit.", ) + rootCmd.PersistentFlags().StringVar( + &configDirectory, + "config", + "", + "The path the config folder.", + ) + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } -func startServerOrDie(configLoader *loader.ConfigLoader, interruptCh <-chan interface{}) { +func startServerOrDie(cf *loader.ConfigLoader, interruptCh <-chan interface{}) { // init the repository - cf := &loader.ConfigLoader{} - sc, err := cf.LoadServerConfig() if err != nil { diff --git a/cmd/hatchet-engine/main.go b/cmd/hatchet-engine/main.go index c9d263083..b36f00824 100644 --- a/cmd/hatchet-engine/main.go +++ b/cmd/hatchet-engine/main.go @@ -18,6 +18,7 @@ import ( ) var printVersion bool +var configDirectory string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ @@ -29,7 +30,7 @@ var rootCmd = &cobra.Command{ os.Exit(0) } - cf := &loader.ConfigLoader{} + cf := loader.NewConfigLoader(configDirectory) interruptChan := cmdutils.InterruptChan() startEngineOrDie(cf, interruptChan) @@ -47,6 +48,13 @@ func main() { "print version and exit.", ) + rootCmd.PersistentFlags().StringVar( + &configDirectory, + "config", + "", + "The path the config folder.", + ) + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/seed/main.go b/cmd/seed/main.go deleted file mode 100644 index 2a75eb369..000000000 --- a/cmd/seed/main.go +++ /dev/null @@ -1,159 +0,0 @@ -package main - -import ( - "errors" - "fmt" - - "github.com/hatchet-dev/hatchet/internal/config/loader" - "github.com/hatchet-dev/hatchet/internal/datautils" - "github.com/hatchet-dev/hatchet/internal/repository" - "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" -) - -func main() { - // init the repository - cf := &loader.ConfigLoader{} - - // load the config - dc, err := cf.LoadDatabaseConfig() - - if err != nil { - panic(err) - } - - // seed an example user - hashedPw, err := repository.HashPassword("Admin123!!") - - if err != nil { - panic(err) - } - - user, err := dc.Repository.User().GetUserByEmail("admin@example.com") - - if err != nil { - if errors.Is(err, db.ErrNotFound) { - user, err = dc.Repository.User().CreateUser(&repository.CreateUserOpts{ - Email: "admin@example.com", - Name: repository.StringPtr("Admin"), - EmailVerified: repository.BoolPtr(true), - Password: *hashedPw, - }) - - if err != nil { - panic(err) - } - } else { - panic(err) - } - } - - tenant, err := dc.Repository.Tenant().GetTenantBySlug("default") - - if err != nil { - if errors.Is(err, db.ErrNotFound) { - // seed an example tenant - // initialize a tenant - tenant, err = dc.Repository.Tenant().CreateTenant(&repository.CreateTenantOpts{ - Name: "Default", - Slug: "default", - }) - - if err != nil { - panic(err) - } - - fmt.Println("created tenant", tenant.ID) - - // add the user to the tenant - _, err = dc.Repository.Tenant().CreateTenantMember(tenant.ID, &repository.CreateTenantMemberOpts{ - Role: "OWNER", - UserId: user.ID, - }) - - if err != nil { - panic(err) - } - } else { - panic(err) - } - } - - // seed example workflows - firstInput, _ := datautils.ToJSONType(map[string]interface{}{ - "message": "Username is {{ .input.username }}", - }) - - secondInput, _ := datautils.ToJSONType(map[string]interface{}{ - "message": "Above message is: {{ .steps.echo1.message }}", - }) - - thirdInput, _ := datautils.ToJSONType(map[string]interface{}{ - "message": "Above message is: {{ .steps.echo1.message }}", - }) - - _, err = dc.Repository.Workflow().CreateNewWorkflow(tenant.ID, &repository.CreateWorkflowVersionOpts{ - Name: "test-workflow", - Description: repository.StringPtr("This is a test workflow."), - Version: "v0.1.0", - EventTriggers: []string{ - "user:create", - }, - Tags: []repository.CreateWorkflowTagOpts{ - { - Name: "Preview", - }, - }, - Jobs: []repository.CreateWorkflowJobOpts{ - { - Name: "job-name", - Steps: []repository.CreateWorkflowStepOpts{ - { - ReadableId: "echo1", - Action: "echo:echo", - Inputs: firstInput, - }, - { - ReadableId: "echo2", - Action: "echo:echo", - Inputs: secondInput, - }, - { - ReadableId: "echo3", - Action: "echo:echo", - Inputs: thirdInput, - }, - }, - }, - }, - }) - - if err != nil { - panic(err) - } - - workflows, err := dc.Repository.Workflow().ListWorkflowsForEvent(tenant.ID, "user:create") - - if err != nil { - panic(err) - } - - for _, workflow := range workflows { - fmt.Println("created workflow", workflow.ID, workflow.Workflow().Name, workflow.Version) - } - - // seed example events - generateEvents(dc.Repository, tenant.ID) -} - -func generateEvents(repo repository.Repository, tenantId string) { - for i := 0; i < 600; i++ { - _, err := repo.Event().CreateEvent(&repository.CreateEventOpts{ - TenantId: tenantId, - Key: fmt.Sprintf("user-%d:create", i), - }) - - if err != nil { - panic(err) - } - } -} diff --git a/go.mod b/go.mod index ed253a642..a25f2f8b4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ replace golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 require ( github.com/creasty/defaults v1.7.0 + github.com/fatih/color v1.16.0 github.com/getkin/kin-openapi v0.122.0 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.2.2 @@ -17,9 +18,10 @@ require ( github.com/labstack/echo/v4 v4.11.3 github.com/oapi-codegen/runtime v1.1.0 github.com/shopspring/decimal v1.3.1 + github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.16.0 - github.com/steebchen/prisma-client-go v0.27.1 - github.com/takuoki/gocase v1.0.0 + github.com/steebchen/prisma-client-go v0.31.3 + sigs.k8s.io/yaml v1.4.0 ) require ( @@ -44,7 +46,6 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/time v0.3.0 // indirect @@ -62,10 +63,9 @@ require ( github.com/gorilla/schema v1.2.1 github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/iancoleman/strcase v0.2.0 github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -83,8 +83,8 @@ require ( golang.org/x/crypto v0.14.0 golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.4.0 - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect google.golang.org/grpc v1.58.2 google.golang.org/protobuf v1.31.0 diff --git a/go.sum b/go.sum index b2ed4e815..07fad62ff 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -181,9 +183,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -231,8 +230,9 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= @@ -280,8 +280,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= -github.com/steebchen/prisma-client-go v0.27.1 h1:AIyQqsH0z65T5CCpgZzZW7ymlgRfRxtuJENiXbONLJw= -github.com/steebchen/prisma-client-go v0.27.1/go.mod h1:uLyNTrma4goWuhfbpZuxCRRennjQHJtFps0+M36Iqwc= +github.com/steebchen/prisma-client-go v0.31.3 h1:ubqSRFfPUTAHZijY/HqeTlKJWJpI3Lj4O338AqvqBeg= +github.com/steebchen/prisma-client-go v0.31.3/go.mod h1:ksKELgUZSn56rbAv1jlF8D7o8V6lis0Tc2LEgv2qNbs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -298,8 +298,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/takuoki/gocase v1.0.0 h1:gPwLJTWVm2T1kUiCsKirg/faaIUGVTI0FA3SYr75a44= -github.com/takuoki/gocase v1.0.0/go.mod h1:QgOKJrbuJoDrtoKswBX1/Dw8mJrkOV9tbQZJaxaJ6zc= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -354,7 +352,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -390,7 +387,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -413,7 +409,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -457,15 +452,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -475,7 +468,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -528,7 +520,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -652,3 +643,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/config/database/config.go b/internal/config/database/config.go index 5eea337eb..e343618d4 100644 --- a/internal/config/database/config.go +++ b/internal/config/database/config.go @@ -12,12 +12,27 @@ type ConfigFile struct { PostgresPassword string `mapstructure:"password" json:"password,omitempty" default:"hatchet"` PostgresDbName string `mapstructure:"dbName" json:"dbName,omitempty" default:"hatchet"` PostgresSSLMode string `mapstructure:"sslMode" json:"sslMode,omitempty" default:"disable"` + + Seed SeedConfigFile `mapstructure:"seed" json:"seed,omitempty"` +} + +type SeedConfigFile struct { + AdminEmail string `mapstructure:"adminEmail" json:"adminEmail,omitempty" default:"admin@example.com"` + AdminPassword string `mapstructure:"adminPassword" json:"adminPassword,omitempty" default:"Admin123!!"` + AdminName string `mapstructure:"adminName" json:"adminName,omitempty" default:"Admin"` + + DefaultTenantName string `mapstructure:"defaultTenantName" json:"defaultTenantName,omitempty" default:"default"` + DefaultTenantSlug string `mapstructure:"defaultTenantSlug" json:"defaultTenantSlug,omitempty" default:"default"` + + IsDevelopment bool `mapstructure:"isDevelopment" json:"isDevelopment,omitempty" default:"false"` } type Config struct { Disconnect func() error Repository repository.Repository + + Seed SeedConfigFile } func BindAllEnv(v *viper.Viper) { @@ -27,4 +42,11 @@ func BindAllEnv(v *viper.Viper) { v.BindEnv("password", "DATABASE_POSTGRES_PASSWORD") v.BindEnv("dbName", "DATABASE_POSTGRES_DB_NAME") v.BindEnv("sslMode", "DATABASE_POSTGRES_SSL_MODE") + + v.BindEnv("seed.adminEmail", "ADMIN_EMAIL") + v.BindEnv("seed.adminPassword", "ADMIN_PASSWORD") + v.BindEnv("seed.adminName", "ADMIN_NAME") + v.BindEnv("seed.defaultTenantName", "DEFAULT_TENANT_NAME") + v.BindEnv("seed.defaultTenantSlug", "DEFAULT_TENANT_SLUG") + v.BindEnv("seed.isDevelopment", "SEED_DEVELOPMENT") } diff --git a/internal/config/loader/loader.go b/internal/config/loader/loader.go index 1768d9143..022c2d093 100644 --- a/internal/config/loader/loader.go +++ b/internal/config/loader/loader.go @@ -85,7 +85,11 @@ func LoadConfigFromViper(bindFunc func(v *viper.Viper), configFile interface{}, } type ConfigLoader struct { - version, directory string + directory string +} + +func NewConfigLoader(directory string) *ConfigLoader { + return &ConfigLoader{directory} } // LoadDatabaseConfig loads the database configuration @@ -162,9 +166,12 @@ func getConfigBytes(configFilePath string) ([][]byte, error) { func fileExists(filename string) bool { info, err := os.Stat(filename) - if os.IsNotExist(err) { + if err != nil && os.IsNotExist(err) { + return false + } else if err != nil { return false } + return !info.IsDir() } @@ -179,10 +186,10 @@ 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), + // db.WithDatasourceURL(databaseUrl), ) if err := client.Prisma.Connect(); err != nil { @@ -198,6 +205,7 @@ func GetDatabaseConfigFromConfigFile(cf *database.ConfigFile) (res *database.Con return &database.Config{ Disconnect: client.Prisma.Disconnect, Repository: prisma.NewPrismaRepository(client, pool), + Seed: cf.Seed, }, nil } diff --git a/internal/config/server/server.go b/internal/config/server/server.go index c4cffd98f..ce5f22e99 100644 --- a/internal/config/server/server.go +++ b/internal/config/server/server.go @@ -22,8 +22,6 @@ type ServerConfigFile struct { Services []string `mapstructure:"services" json:"services,omitempty" default:"[\"ticker\", \"grpc\", \"eventscontroller\", \"jobscontroller\"]"` - Namespaces []string `mapstructure:"namespaces" json:"namespaces,omitempty" default:"[\"default\"]"` - TLS shared.TLSConfigFile `mapstructure:"tls" json:"tls,omitempty"` } diff --git a/internal/repository/prisma/dbsqlc/models.go b/internal/repository/prisma/dbsqlc/models.go index 70e7c459e..1f11ce099 100644 --- a/internal/repository/prisma/dbsqlc/models.go +++ b/internal/repository/prisma/dbsqlc/models.go @@ -65,7 +65,7 @@ const ( StepRunStatusRUNNING StepRunStatus = "RUNNING" StepRunStatusSUCCEEDED StepRunStatus = "SUCCEEDED" StepRunStatusFAILED StepRunStatus = "FAILED" - StepRunStatusCANCELLED StepRunStatus = "CANCELLED" + StepRunStatusCancelled StepRunStatus = "CANCELLED" ) func (e *StepRunStatus) Scan(src interface{}) error { diff --git a/internal/repository/prisma/step_run.go b/internal/repository/prisma/step_run.go index 188a9f1b7..0c10b335d 100644 --- a/internal/repository/prisma/step_run.go +++ b/internal/repository/prisma/step_run.go @@ -79,7 +79,7 @@ func (j *stepRunRepository) ListStepRuns(tenantId string, opts *repository.ListS params, db.StepRun.RequeueAfter.Before(time.Now()), db.StepRun.WorkerID.IsNull(), - db.StepRun.Status.Equals(db.StepRunStatusPENDINGASSIGNMENT), + db.StepRun.Status.Equals(db.StepRunStatusPendingAssignment), // db.StepRun.Or( // db.StepRun.Prev // db.StepRun.Step.Where( @@ -298,9 +298,9 @@ func (j *stepRunRepository) GetStepRunById(tenantId, stepRunId string) (*db.Step func (j *stepRunRepository) CancelPendingStepRuns(tenantId, jobRunId, reason string) error { _, err := j.client.StepRun.FindMany( db.StepRun.JobRunID.Equals(jobRunId), - db.StepRun.Status.Equals(db.StepRunStatusPENDING), + db.StepRun.Status.Equals(db.StepRunStatusPending), ).Update( - db.StepRun.Status.Set(db.StepRunStatusCANCELLED), + db.StepRun.Status.Set(db.StepRunStatusCancelled), db.StepRun.CancelledAt.Set(time.Now()), db.StepRun.CancelledReason.Set(reason), ).Exec(context.Background()) diff --git a/internal/repository/prisma/worker.go b/internal/repository/prisma/worker.go index fd9d303d9..44aaa8b63 100644 --- a/internal/repository/prisma/worker.go +++ b/internal/repository/prisma/worker.go @@ -281,7 +281,7 @@ func (w *workerRepository) AddStepRun(tenantId, workerId, stepRunId string) erro tx2 := w.client.StepRun.FindUnique( db.StepRun.ID.Equals(stepRunId), ).Update( - db.StepRun.Status.Set(db.StepRunStatusASSIGNED), + db.StepRun.Status.Set(db.StepRunStatusAssigned), ).Tx() err := w.client.Prisma.Transaction(tx1, tx2).Exec(context.Background()) diff --git a/internal/services/dispatcher/server.go b/internal/services/dispatcher/server.go index 95f695cdb..3b287234e 100644 --- a/internal/services/dispatcher/server.go +++ b/internal/services/dispatcher/server.go @@ -131,7 +131,7 @@ func (s *DispatcherImpl) Listen(request *contracts.WorkerListenRequest, stream c defer func() { s.workers.Delete(request.WorkerId) - inactive := db.WorkerStatusINACTIVE + inactive := db.WorkerStatusInactive _, err := s.repo.Worker().UpdateWorker(request.TenantId, request.WorkerId, &repository.UpdateWorkerOpts{ Status: &inactive, diff --git a/internal/services/jobscontroller/controller.go b/internal/services/jobscontroller/controller.go index 5953db5e9..ab0ffd8c7 100644 --- a/internal/services/jobscontroller/controller.go +++ b/internal/services/jobscontroller/controller.go @@ -255,7 +255,7 @@ func (ec *JobsControllerImpl) handleJobRunTimedOut(ctx context.Context, task *ta stepRuns, err := ec.repo.StepRun().ListStepRuns(metadata.TenantId, &repository.ListStepRunsOpts{ JobRunId: &jobRun.ID, - Status: repository.StepRunStatusPtr(db.StepRunStatusRUNNING), + Status: repository.StepRunStatusPtr(db.StepRunStatusRunning), }) if err != nil { @@ -276,7 +276,7 @@ func (ec *JobsControllerImpl) handleJobRunTimedOut(ctx context.Context, task *ta stepRun, err := ec.repo.StepRun().UpdateStepRun(metadata.TenantId, currStepRun.ID, &repository.UpdateStepRunOpts{ CancelledAt: &now, CancelledReason: repository.StringPtr("JOB_RUN_TIMED_OUT"), - Status: repository.StepRunStatusPtr(db.StepRunStatusCANCELLED), + Status: repository.StepRunStatusPtr(db.StepRunStatusCancelled), }) if err != nil { @@ -483,7 +483,7 @@ func (ec *JobsControllerImpl) queueStepRun(ctx context.Context, tenantId, stepId func (ec *JobsControllerImpl) scheduleStepRun(ctx context.Context, tenantId, stepId, stepRunId string) error { // indicate that the step run is pending assignment stepRun, err := ec.repo.StepRun().UpdateStepRun(tenantId, stepRunId, &repository.UpdateStepRunOpts{ - Status: repository.StepRunStatusPtr(db.StepRunStatusPENDINGASSIGNMENT), + Status: repository.StepRunStatusPtr(db.StepRunStatusPendingAssignment), }) if err != nil { @@ -598,7 +598,7 @@ func (ec *JobsControllerImpl) handleStepRunStarted(ctx context.Context, task *ta _, err = ec.repo.StepRun().UpdateStepRun(metadata.TenantId, payload.StepRunId, &repository.UpdateStepRunOpts{ StartedAt: &startedAt, - Status: repository.StepRunStatusPtr(db.StepRunStatusRUNNING), + Status: repository.StepRunStatusPtr(db.StepRunStatusRunning), }) return err @@ -639,7 +639,7 @@ func (ec *JobsControllerImpl) handleStepRunFinished(ctx context.Context, task *t stepRun, err := ec.repo.StepRun().UpdateStepRun(metadata.TenantId, payload.StepRunId, &repository.UpdateStepRunOpts{ FinishedAt: &finishedAt, - Status: repository.StepRunStatusPtr(db.StepRunStatusSUCCEEDED), + Status: repository.StepRunStatusPtr(db.StepRunStatusSucceeded), Output: &outputJSON, }) @@ -743,7 +743,7 @@ func (ec *JobsControllerImpl) handleStepRunFailed(ctx context.Context, task *tas stepRun, err := ec.repo.StepRun().UpdateStepRun(metadata.TenantId, payload.StepRunId, &repository.UpdateStepRunOpts{ FinishedAt: &failedAt, Error: &payload.Error, - Status: repository.StepRunStatusPtr(db.StepRunStatusFAILED), + Status: repository.StepRunStatusPtr(db.StepRunStatusFailed), }) if err != nil { @@ -806,7 +806,7 @@ func (ec *JobsControllerImpl) handleStepRunTimedOut(ctx context.Context, task *t stepRun, err := ec.repo.StepRun().UpdateStepRun(metadata.TenantId, payload.StepRunId, &repository.UpdateStepRunOpts{ CancelledAt: &now, CancelledReason: repository.StringPtr("TIMED_OUT"), - Status: repository.StepRunStatusPtr(db.StepRunStatusCANCELLED), + Status: repository.StepRunStatusPtr(db.StepRunStatusCancelled), }) if err != nil { @@ -875,7 +875,7 @@ func (ec *JobsControllerImpl) handleTickerRemoved(ctx context.Context, task *tas // get all step runs assigned to the ticker stepRuns, err := ec.repo.StepRun().ListAllStepRuns(&repository.ListAllStepRunsOpts{ NoTickerId: repository.BoolPtr(true), - Status: repository.StepRunStatusPtr(db.StepRunStatusRUNNING), + Status: repository.StepRunStatusPtr(db.StepRunStatusRunning), }) if err != nil { @@ -913,7 +913,7 @@ func (ec *JobsControllerImpl) handleTickerRemoved(ctx context.Context, task *tas // get all step runs assigned to the ticker jobRuns, err := ec.repo.JobRun().ListAllJobRuns(&repository.ListAllJobRunsOpts{ NoTickerId: repository.BoolPtr(true), - Status: repository.JobRunStatusPtr(db.JobRunStatusRUNNING), + Status: repository.JobRunStatusPtr(db.JobRunStatusRunning), }) if err != nil {