feat: github app integration (#163)

* feat: github app integration

* chore: proto

* fix: migrate instead of push

* fix: db migrate -> migrate

* fix: migrate again

* remove skip-generate

* add back generate

* setup pnpm
This commit is contained in:
abelanger5
2024-02-13 18:34:16 -08:00
committed by GitHub
parent 02c2ea1307
commit 3743746657
82 changed files with 6778 additions and 271 deletions
+21 -3
View File
@@ -33,7 +33,7 @@ jobs:
- name: Generate
run: |
go run github.com/steebchen/prisma-client-go db push --skip-generate
go run github.com/steebchen/prisma-client-go migrate deploy
task generate
- name: Check for diff
@@ -80,11 +80,20 @@ jobs:
with:
version: "25.1"
- name: Install Task
uses: arduino/setup-task@v1
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.21"
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Compose
run: docker compose up -d
@@ -92,7 +101,9 @@ jobs:
run: go mod download
- name: Generate
run: go run github.com/steebchen/prisma-client-go db push
run: |
go run github.com/steebchen/prisma-client-go migrate deploy
task generate
- name: Test
run: go test -tags integration ./... -v -failfast
@@ -122,6 +133,12 @@ jobs:
with:
go-version: "1.21"
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Compose
run: docker compose up -d
@@ -130,7 +147,8 @@ jobs:
- name: Generate
run: |
go run github.com/steebchen/prisma-client-go db push
go run github.com/steebchen/prisma-client-go migrate deploy
task generate
task generate-certs
- name: Prepare
+6
View File
@@ -9,3 +9,9 @@ app.dev.hatchet-tools.com {
reverse_proxy localhost:5173
}
}
grpc.dev.hatchet-tools.com {
tls internal
reverse_proxy h2c://127.0.0.1:7070
}
+9 -1
View File
@@ -9,7 +9,7 @@ tasks:
- sh ./hack/dev/run-go-with-env.sh run github.com/steebchen/prisma-client-go migrate dev
seed-dev:
cmds:
- sh ./hack/dev/run-npx-with-env.sh prisma db push --force-reset --skip-generate
- sh ./hack/dev/run-go-with-env.sh run github.com/steebchen/prisma-client-go migrate dev --skip-generate
- SEED_DEVELOPMENT=true sh ./hack/dev/run-go-with-env.sh run ./cmd/hatchet-admin seed
start-dev:
deps:
@@ -85,6 +85,14 @@ tasks:
kill-query-engines:
cmds:
- ps -A | grep 'prisma-query-engine-darwin-arm64' | grep -v grep | awk '{print $1}' | xargs kill -9 $1
kill-apis:
cmds:
- ps -A | grep 'cmd/hatchet-api' | grep -v grep | awk '{print $1}' | xargs kill -9 $1
- ps -A | grep 'exe/hatchet-api' | grep -v grep | awk '{print $1}' | xargs kill -9 $1
kill-engines:
cmds:
- ps -A | grep 'cmd/hatchet-engine' | grep -v grep | awk '{print $1}' | xargs kill -9 $1
- ps -A | grep 'exe/hatchet-engine' | grep -v grep | awk '{print $1}' | xargs kill -9 $1
prisma-studio:
cmds:
- sh ./hack/dev/run-npx-with-env.sh prisma studio
@@ -221,6 +221,9 @@ message OverridesData {
// the value to set
string value = 3;
// the filename of the caller
string callerFilename = 4;
}
message OverridesDataResponse {}
@@ -68,6 +68,8 @@ ReplayEventRequest:
$ref: "./event.yaml#/ReplayEventRequest"
Workflow:
$ref: "./workflow.yaml#/Workflow"
WorkflowDeploymentConfig:
$ref: "./workflow.yaml#/WorkflowDeploymentConfig"
WorkflowVersionMeta:
$ref: "./workflow.yaml#/WorkflowVersionMeta"
WorkflowVersion:
@@ -120,3 +122,19 @@ RerunStepRunRequest:
$ref: "./workflow_run.yaml#/RerunStepRunRequest"
TriggerWorkflowRunRequest:
$ref: "./workflow_run.yaml#/TriggerWorkflowRunRequest"
LinkGithubRepositoryRequest:
$ref: "./workflow.yaml#/LinkGithubRepositoryRequest"
GithubBranch:
$ref: "./github_app.yaml#/GithubBranch"
GithubRepo:
$ref: "./github_app.yaml#/GithubRepo"
GithubAppInstallation:
$ref: "./github_app.yaml#/GithubAppInstallation"
ListGithubAppInstallationsResponse:
$ref: "./github_app.yaml#/ListGithubAppInstallationsResponse"
ListGithubReposResponse:
$ref: "./github_app.yaml#/ListGithubReposResponse"
ListGithubBranchesResponse:
$ref: "./github_app.yaml#/ListGithubBranchesResponse"
CreatePullRequestFromStepRun:
$ref: "./workflow_run.yaml#/CreatePullRequestFromStepRun"
@@ -0,0 +1,61 @@
GithubBranch:
type: object
properties:
branch_name:
type: string
is_default:
type: boolean
required:
- branch_name
- is_default
GithubRepo:
type: object
properties:
repo_owner:
type: string
repo_name:
type: string
required:
- repo_owner
- repo_name
GithubAppInstallation:
type: object
properties:
metadata:
$ref: "./metadata.yaml#/APIResourceMeta"
installation_settings_url:
type: string
account_name:
type: string
account_avatar_url:
type: string
required:
- metadata
- installation_settings_url
- account_name
- account_avatar_url
ListGithubAppInstallationsResponse:
type: object
properties:
pagination:
$ref: "./metadata.yaml#/PaginationResponse"
rows:
type: array
items:
$ref: "#/GithubAppInstallation"
required:
- pagination
- rows
ListGithubReposResponse:
type: array
items:
$ref: "#/GithubRepo"
ListGithubBranchesResponse:
type: array
items:
$ref: "#/GithubBranch"
@@ -24,10 +24,38 @@ Workflow:
items:
$ref: "#/Job"
description: The jobs of the workflow.
deployment:
$ref: "#/WorkflowDeploymentConfig"
required:
- metadata
- name
type: object
WorkflowDeploymentConfig:
properties:
metadata:
$ref: "./metadata.yaml#/APIResourceMeta"
gitRepoName:
type: string
description: The repository name.
gitRepoOwner:
type: string
description: The repository owner.
gitRepoBranch:
type: string
description: The repository branch.
githubAppInstallation:
$ref: "./_index.yaml#/GithubAppInstallation"
description: The Github App installation.
githubAppInstallationId:
type: string
format: uuid
description: The id of the Github App installation.
required:
- metadata
- gitRepoName
- gitRepoOwner
- gitRepoBranch
- githubAppInstallationId
WorkflowTag:
type: object
@@ -205,3 +233,25 @@ Step:
- jobId
- action
- nextId
LinkGithubRepositoryRequest:
type: object
properties:
installationId:
type: string
description: The repository name.
minLength: 36
maxLength: 36
gitRepoName:
type: string
description: The repository name.
gitRepoOwner:
type: string
description: The repository owner.
gitRepoBranch:
type: string
description: The repository branch.
required:
- installationId
- gitRepoName
- gitRepoOwner
- gitRepoBranch
@@ -227,3 +227,10 @@ TriggerWorkflowRunRequest:
type: object
required:
- input
CreatePullRequestFromStepRun:
properties:
branchName:
type: string
required:
- branchName
+18
View File
@@ -28,6 +28,14 @@ paths:
$ref: "./paths/user/user.yaml#/oauth-start-google"
/api/v1/users/google/callback:
$ref: "./paths/user/user.yaml#/oauth-callback-google"
/api/v1/users/github/start:
$ref: "./paths/user/user.yaml#/oauth-start-github"
/api/v1/users/github/callback:
$ref: "./paths/user/user.yaml#/oauth-callback-github"
/api/v1/github/webhook:
$ref: "./paths/github-app/github-app.yaml#/globalWebhook"
/api/v1/github/webhook/{webhook}:
$ref: "./paths/github-app/github-app.yaml#/tenantWebhook"
/api/v1/users/current:
$ref: "./paths/user/user.yaml#/current"
/api/v1/users/register:
@@ -72,6 +80,10 @@ paths:
$ref: "./paths/workflow/workflow.yaml#/triggerWorkflow"
/api/v1/workflows/{workflow}/versions/definition:
$ref: "./paths/workflow/workflow.yaml#/workflowVersionDefinition"
/api/v1/workflows/{workflow}/link-github:
$ref: "./paths/workflow/workflow.yaml#/linkGithub"
/api/v1/step-runs/{step-run}/create-pr:
$ref: "./paths/workflow/workflow.yaml#/createPullRequest"
/api/v1/tenants/{tenant}/workflows/runs:
$ref: "./paths/workflow/workflow.yaml#/workflowRuns"
/api/v1/tenants/{tenant}/workflow-runs/{workflow-run}:
@@ -84,3 +96,9 @@ paths:
$ref: "./paths/worker/worker.yaml#/withTenant"
/api/v1/workers/{worker}:
$ref: "./paths/worker/worker.yaml#/withWorker"
/api/v1/github-app/installations:
$ref: "./paths/github-app/github-app.yaml#/installations"
/api/v1/github-app/installations/{gh-installation}/repos:
$ref: "./paths/github-app/github-app.yaml#/repos"
/api/v1/github-app/installations/{gh-installation}/repos/{gh-repo-owner}/{gh-repo-name}/branches:
$ref: "./paths/github-app/github-app.yaml#/branches"
@@ -0,0 +1,204 @@
globalWebhook:
post:
description: Github App global webhook
operationId: github:update:global-webhook
responses:
"200":
description: Successfully processed webhook
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"401":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Unauthorized
"405":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Method not allowed
security: []
summary: Github app global webhook
tags:
- Github
tenantWebhook:
post:
description: Github App tenant webhook
operationId: github:update:tenant-webhook
parameters:
- description: The webhook id
in: path
name: webhook
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
responses:
"200":
description: Successfully processed webhook
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"401":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Unauthorized
"405":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Method not allowed
security: []
summary: Github app tenant webhook
tags:
- Github
installations:
get:
description: List Github App installations
operationId: github-app:list:installations
responses:
"200":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/ListGithubAppInstallationsResponse"
description: Successfully retrieved the installations
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"401":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Unauthorized
"405":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Method not allowed
security:
- cookieAuth: []
summary: List Github App installations
tags:
- Github
repos:
get:
description: List Github App repositories
operationId: github-app:list:repos
x-resources: ["gh-installation"]
parameters:
- description: The installation id
in: path
name: gh-installation
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
responses:
"200":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/ListGithubReposResponse"
description: Successfully retrieved the repositories
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"401":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Unauthorized
"405":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Method not allowed
security:
- cookieAuth: []
summary: List Github App repositories
tags:
- Github
branches:
get:
description: List Github App branches
operationId: github-app:list:branches
x-resources: ["gh-installation"]
parameters:
- description: The installation id
in: path
name: gh-installation
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
- description: The repository owner
in: path
name: gh-repo-owner
required: true
schema:
type: string
- description: The repository name
in: path
name: gh-repo-name
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/ListGithubBranchesResponse"
description: Successfully retrieved the branches
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"401":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Unauthorized
"405":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Method not allowed
security:
- cookieAuth: []
summary: List Github App branches
tags:
- Github
+38 -2
View File
@@ -111,7 +111,7 @@ register:
oauth-start-google:
get:
description: Starts the OAuth flow
operationId: user:update:oauth-start
operationId: user:update:google-oauth-start
responses:
"302":
description: Successfully started the OAuth flow
@@ -126,7 +126,7 @@ oauth-start-google:
oauth-callback-google:
get:
description: Completes the OAuth flow
operationId: user:update:oauth-callback
operationId: user:update:google-oauth-callback
responses:
"302":
description: Successfully completed the OAuth flow
@@ -138,6 +138,42 @@ oauth-callback-google:
summary: Complete OAuth flow
tags:
- User
oauth-start-github:
get:
description: Starts the OAuth flow
operationId: user:update:github-oauth-start
responses:
"302":
description: Successfully started the OAuth flow
headers:
location:
schema:
type: string
# Note that the security scheme requires cookies, because this endpoint is for linking
# a GitHub account to an existing user account.
security:
- cookieAuth: []
summary: Start OAuth flow
tags:
- User
oauth-callback-github:
get:
description: Completes the OAuth flow
operationId: user:update:github-oauth-callback
responses:
"302":
description: Successfully completed the OAuth flow
headers:
location:
schema:
type: string
# Note that the security scheme requires cookies, because this endpoint is for linking
# a GitHub account to an existing user account.
security:
- cookieAuth: []
summary: Complete OAuth flow
tags:
- User
logout:
post:
description: Logs out a user.
@@ -350,3 +350,103 @@ workflowRun:
summary: Get workflow run
tags:
- Workflow
linkGithub:
post:
x-resources: ["tenant", "workflow"]
description: Link a github repository to a workflow
operationId: workflow:update:link-github
parameters:
- description: The workflow id
in: path
name: workflow
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
requestBody:
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/LinkGithubRepositoryRequest"
description: The input to link a github repository
required: true
responses:
"200":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/Workflow"
description: Successfully linked the github repository
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"403":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Forbidden
"404":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Not found
summary: Link github repository
tags:
- Workflow
createPullRequest:
post:
x-resources: ["tenant", "step-run"]
description: Create a pull request for a workflow
operationId: step-run:update:create-pr
parameters:
- description: The step run id
in: path
name: step-run
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
requestBody:
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/CreatePullRequestFromStepRun"
description: The input to create a pull request
required: true
responses:
"200":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/CreatePullRequestFromStepRun"
description: Successfully created the pull request
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"403":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Forbidden
"404":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Not found
summary: Create pull request
tags:
- Workflow
+10 -4
View File
@@ -55,6 +55,7 @@ func (s *SessionHelpers) SaveUnauthenticated(c echo.Context) error {
func (s *SessionHelpers) SaveOAuthState(
c echo.Context,
integration string,
) (string, error) {
state, err := encryption.GenerateRandomBytes(16)
@@ -68,8 +69,10 @@ func (s *SessionHelpers) SaveOAuthState(
return "", err
}
stateKey := fmt.Sprintf("oauth_state_%s", integration)
// need state parameter to validate when redirected
session.Values["state"] = state
session.Values[stateKey] = state
// need a parameter to indicate that this was triggered through the oauth flow
session.Values["oauth_triggered"] = true
@@ -83,18 +86,21 @@ func (s *SessionHelpers) SaveOAuthState(
func (s *SessionHelpers) ValidateOAuthState(
c echo.Context,
integration string,
) (isValidated bool, isOAuthTriggered bool, err error) {
stateKey := fmt.Sprintf("oauth_state_%s", integration)
session, err := s.config.SessionStore.Get(c.Request(), s.config.SessionStore.GetName())
if err != nil {
return false, false, err
}
if _, ok := session.Values["state"]; !ok {
if _, ok := session.Values[stateKey]; !ok {
return false, false, fmt.Errorf("state parameter not found in session")
}
if c.Request().URL.Query().Get("state") != session.Values["state"] {
if c.Request().URL.Query().Get("state") != session.Values[stateKey] {
return false, false, fmt.Errorf("state parameters do not match")
}
@@ -107,7 +113,7 @@ func (s *SessionHelpers) ValidateOAuthState(
}
// need state parameter to validate when redirected
session.Values["state"] = ""
session.Values[stateKey] = ""
session.Values["oauth_triggered"] = false
if err := session.Save(c.Request(), c.Response()); err != nil {
@@ -0,0 +1,69 @@
package githubapp
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs/github"
"github.com/hatchet-dev/hatchet/internal/repository"
githubsdk "github.com/google/go-github/v57/github"
)
// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow.
func (g *GithubAppService) GithubUpdateTenantWebhook(ctx echo.Context, req gen.GithubUpdateTenantWebhookRequestObject) (gen.GithubUpdateTenantWebhookResponseObject, error) {
webhookId := req.Webhook.String()
webhook, err := g.config.Repository.Github().ReadGithubWebhookById(webhookId)
if err != nil {
return nil, err
}
signingSecret, err := g.config.Encryption.Decrypt(webhook.SigningSecret, "github_signing_secret")
if err != nil {
return nil, err
}
// validate the payload using the github webhook signing secret
payload, err := githubsdk.ValidatePayload(ctx.Request(), signingSecret)
if err != nil {
return nil, err
}
event, err := githubsdk.ParseWebHook(githubsdk.WebHookType(ctx.Request()), payload)
if err != nil {
return nil, err
}
switch event := event.(type) { // nolint: gocritic
case *githubsdk.PullRequestEvent:
err = g.processPullRequestEvent(webhook.TenantID, event, ctx.Request())
}
return nil, nil
}
func (g *GithubAppService) processPullRequestEvent(tenantId string, event *githubsdk.PullRequestEvent, r *http.Request) error {
pr := github.ToVCSRepositoryPullRequest(*event.GetRepo().GetOwner().Login, event.GetRepo().GetName(), event.GetPullRequest())
dbPR, err := g.config.Repository.Github().GetPullRequest(tenantId, pr.GetRepoOwner(), pr.GetRepoName(), int(pr.GetPRNumber()))
if err != nil {
return err
}
_, err = g.config.Repository.Github().UpdatePullRequest(tenantId, dbPR.ID, &repository.UpdatePullRequestOpts{
HeadBranch: repository.StringPtr(pr.GetHeadBranch()),
BaseBranch: repository.StringPtr(pr.GetBaseBranch()),
Title: repository.StringPtr(pr.GetTitle()),
State: repository.StringPtr(pr.GetState()),
})
return err
}
@@ -0,0 +1,72 @@
package githubapp
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/apierrors"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/config/server"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs/github"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
githubsdk "github.com/google/go-github/v57/github"
)
func GetGithubProvider(config *server.ServerConfig) (res github.GithubVCSProvider, reqErr error) {
vcsProvider, exists := config.VCSProviders[vcs.VCSRepositoryKindGithub]
if !exists {
return res, fmt.Errorf("No Github app set up on this Hatchet instance.")
}
res, err := github.ToGithubVCSProvider(vcsProvider)
if err != nil {
return res, fmt.Errorf("Github app is improperly set up on this Hatchet instance.")
}
return res, nil
}
func GetGithubAppConfig(config *server.ServerConfig) (*github.GithubAppConf, error) {
githubFact, reqErr := GetGithubProvider(config)
if reqErr != nil {
return nil, reqErr
}
return githubFact.GetGithubAppConfig(), nil
}
// GetGithubAppClientFromRequest gets the github app installation id from the request and authenticates
// using it and the private key
func GetGithubAppClientFromRequest(ctx echo.Context, config *server.ServerConfig) (*githubsdk.Client, *gen.APIErrors) {
user := ctx.Get("user").(*db.UserModel)
gai := ctx.Get("gh-installation").(*db.GithubAppInstallationModel)
if canAccess, err := config.Repository.Github().CanUserAccessInstallation(gai.ID, user.ID); err != nil || !canAccess {
respErr := apierrors.NewAPIErrors("User does not have access to the installation")
return nil, &respErr
}
githubFact, err := GetGithubProvider(config)
if err != nil {
config.Logger.Err(err).Msg("Error getting github provider")
respErr := apierrors.NewAPIErrors("Internal error")
return nil, &respErr
}
res, err := githubFact.GetGithubAppConfig().GetGithubClient(int64(gai.InstallationID))
if err != nil {
config.Logger.Err(err).Msg("Error getting github client")
respErr := apierrors.NewAPIErrors("Internal error")
return nil, &respErr
}
return res, nil
}
@@ -0,0 +1,116 @@
package githubapp
import (
"context"
"sync"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
githubsdk "github.com/google/go-github/v57/github"
)
func (g *GithubAppService) GithubAppListBranches(ctx echo.Context, req gen.GithubAppListBranchesRequestObject) (gen.GithubAppListBranchesResponseObject, error) {
owner := req.GhRepoOwner
name := req.GhRepoName
client, reqErr := GetGithubAppClientFromRequest(ctx, g.config)
if reqErr != nil {
return gen.GithubAppListBranches400JSONResponse(
*reqErr,
), nil
}
repo, _, err := client.Repositories.Get(
context.TODO(),
owner,
name,
)
if err != nil {
return nil, err
}
defaultBranch := repo.GetDefaultBranch()
// List all branches for a specified repo
allBranches, resp, err := client.Repositories.ListBranches(context.Background(), owner, name, &githubsdk.BranchListOptions{
ListOptions: githubsdk.ListOptions{
PerPage: 100,
},
})
if err != nil {
return nil, err
}
// make workers to get branches concurrently
const WCOUNT = 5
numPages := resp.LastPage + 1
var workerErr error
var mu sync.Mutex
var wg sync.WaitGroup
worker := func(cp int) {
defer wg.Done()
for cp < numPages {
opts := &githubsdk.BranchListOptions{
ListOptions: githubsdk.ListOptions{
Page: cp,
PerPage: 100,
},
}
branches, _, err := client.Repositories.ListBranches(context.Background(), owner, name, opts)
if err != nil {
mu.Lock()
workerErr = err
mu.Unlock()
return
}
mu.Lock()
allBranches = append(allBranches, branches...)
mu.Unlock()
cp += WCOUNT
}
}
var numJobs int
if numPages > WCOUNT {
numJobs = WCOUNT
} else {
numJobs = numPages
}
wg.Add(numJobs)
// page 1 is already loaded so we start with 2
for i := 1; i <= numJobs; i++ {
go worker(i + 1)
}
wg.Wait()
if workerErr != nil {
return nil, workerErr
}
res := make([]gen.GithubBranch, 0)
for _, branch := range allBranches {
res = append(res, gen.GithubBranch{
BranchName: *branch.Name,
IsDefault: defaultBranch == *branch.Name,
})
}
return gen.GithubAppListBranches200JSONResponse(
res,
), nil
}
@@ -0,0 +1,31 @@
package githubapp
import (
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/transformers"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
func (g *GithubAppService) GithubAppListInstallations(ctx echo.Context, req gen.GithubAppListInstallationsRequestObject) (gen.GithubAppListInstallationsResponseObject, error) {
user := ctx.Get("user").(*db.UserModel)
gais, err := g.config.Repository.Github().ListGithubAppInstallationsByUserID(user.ID)
if err != nil {
return nil, err
}
rows := make([]gen.GithubAppInstallation, 0)
for i := range gais {
rows = append(rows, *transformers.ToInstallation(&gais[i]))
}
return gen.GithubAppListInstallations200JSONResponse(
gen.ListGithubAppInstallationsResponse{
Rows: rows,
},
), nil
}
@@ -0,0 +1,101 @@
package githubapp
import (
"context"
"sync"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
githubsdk "github.com/google/go-github/v57/github"
)
func (g *GithubAppService) GithubAppListRepos(ctx echo.Context, req gen.GithubAppListReposRequestObject) (gen.GithubAppListReposResponseObject, error) {
client, reqErr := GetGithubAppClientFromRequest(ctx, g.config)
if reqErr != nil {
return gen.GithubAppListRepos400JSONResponse(
*reqErr,
), nil
}
// figure out number of repositories
opt := &githubsdk.ListOptions{
PerPage: 100,
}
repoList, resp, err := client.Apps.ListRepos(context.Background(), opt)
if err != nil {
return nil, err
}
allRepos := repoList.Repositories
// make workers to get pages concurrently
const WCOUNT = 5
numPages := resp.LastPage + 1
var workerErr error
var mu sync.Mutex
var wg sync.WaitGroup
worker := func(cp int) {
defer wg.Done()
for cp < numPages {
cur_opt := &githubsdk.ListOptions{
Page: cp,
PerPage: 100,
}
repos, _, err := client.Apps.ListRepos(context.Background(), cur_opt)
if err != nil {
mu.Lock()
workerErr = err
mu.Unlock()
return
}
mu.Lock()
allRepos = append(allRepos, repos.Repositories...)
mu.Unlock()
cp += WCOUNT
}
}
var numJobs int
if numPages > WCOUNT {
numJobs = WCOUNT
} else {
numJobs = numPages
}
wg.Add(numJobs)
// page 1 is already loaded so we start with 2
for i := 1; i <= numJobs; i++ {
go worker(i + 1)
}
wg.Wait()
if workerErr != nil {
return nil, workerErr
}
res := make([]gen.GithubRepo, 0)
for _, repo := range allRepos {
res = append(res, gen.GithubRepo{
RepoName: *repo.Name,
RepoOwner: *repo.Owner.Login,
})
}
return gen.GithubAppListRepos200JSONResponse(
res,
), nil
}
@@ -0,0 +1,95 @@
package githubapp
import (
"context"
"fmt"
"github.com/google/go-github/v57/github"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/authn"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow.
func (g *GithubAppService) UserUpdateGithubOauthCallback(ctx echo.Context, _ gen.UserUpdateGithubOauthCallbackRequestObject) (gen.UserUpdateGithubOauthCallbackResponseObject, error) {
user := ctx.Get("user").(*db.UserModel)
ghApp, err := GetGithubAppConfig(g.config)
if err != nil {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Github app is misconfigured on this Hatchet instance.")
}
isValid, isOAuthTriggered, err := authn.NewSessionHelpers(g.config).ValidateOAuthState(ctx, "github")
if err != nil || !isValid {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Could not link Github account. Please try again and make sure cookies are enabled.")
}
token, err := ghApp.Exchange(context.Background(), ctx.Request().URL.Query().Get("code"))
if err != nil {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Forbidden")
}
if !token.Valid() {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, fmt.Errorf("invalid token"), "Forbidden")
}
ghClient := github.NewClient(ghApp.Client(context.Background(), token))
githubUser, _, err := ghClient.Users.Get(context.Background(), "")
if err != nil {
return nil, err
}
if githubUser.ID == nil {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Could not get Github user ID.")
}
expiresAt := token.Expiry
// use the encryption service to encrypt the access and refresh token
accessTokenEncrypted, err := g.config.Encryption.Encrypt([]byte(token.AccessToken), "github_access_token")
if err != nil {
return nil, fmt.Errorf("failed to encrypt access token: %s", err.Error())
}
refreshTokenEncrypted, err := g.config.Encryption.Encrypt([]byte(token.RefreshToken), "github_refresh_token")
if err != nil {
return nil, fmt.Errorf("failed to encrypt refresh token: %s", err.Error())
}
// upsert in database
_, err = g.config.Repository.Github().UpsertGithubAppOAuth(user.ID, &repository.CreateGithubAppOAuthOpts{
GithubUserID: int(*githubUser.ID),
AccessToken: accessTokenEncrypted,
RefreshToken: &refreshTokenEncrypted,
ExpiresAt: &expiresAt,
})
if err != nil {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Internal error.")
}
var url string
if isOAuthTriggered {
url = fmt.Sprintf("https://github.com/apps/%s/installations/new", ghApp.GetAppName())
} else {
url = "/"
}
return gen.UserUpdateGithubOauthCallback302Response{
Headers: gen.UserUpdateGithubOauthCallback302ResponseHeaders{
Location: url,
},
}, nil
}
@@ -0,0 +1,32 @@
package githubapp
import (
"github.com/labstack/echo/v4"
"golang.org/x/oauth2"
"github.com/hatchet-dev/hatchet/api/v1/server/authn"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
)
// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow.
func (g *GithubAppService) UserUpdateGithubOauthStart(ctx echo.Context, _ gen.UserUpdateGithubOauthStartRequestObject) (gen.UserUpdateGithubOauthStartResponseObject, error) {
ghApp, err := GetGithubAppConfig(g.config)
if err != nil {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Github app is misconfigured on this Hatchet instance.")
}
state, err := authn.NewSessionHelpers(g.config).SaveOAuthState(ctx, "github")
if err != nil {
return nil, authn.GetRedirectWithError(ctx, g.config.Logger, err, "Could not get cookie. Please make sure cookies are enabled.")
}
url := ghApp.AuthCodeURL(state, oauth2.AccessTypeOffline)
return gen.UserUpdateGithubOauthStart302Response{
Headers: gen.UserUpdateGithubOauthStart302ResponseHeaders{
Location: url,
},
}, nil
}
@@ -0,0 +1,13 @@
package githubapp
import "github.com/hatchet-dev/hatchet/internal/config/server"
type GithubAppService struct {
config *server.ServerConfig
}
func NewGithubAppService(config *server.ServerConfig) *GithubAppService {
return &GithubAppService{
config: config,
}
}
@@ -0,0 +1,105 @@
package githubapp
import (
"errors"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
githubsdk "github.com/google/go-github/v57/github"
)
// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow.
func (g *GithubAppService) GithubUpdateGlobalWebhook(ctx echo.Context, req gen.GithubUpdateGlobalWebhookRequestObject) (gen.GithubUpdateGlobalWebhookResponseObject, error) {
ghApp, err := GetGithubAppConfig(g.config)
if err != nil {
return nil, err
}
// validate the payload using the github webhook signing secret
payload, err := githubsdk.ValidatePayload(ctx.Request(), []byte(ghApp.GetWebhookSecret()))
if err != nil {
return nil, err
}
event, err := githubsdk.ParseWebHook(githubsdk.WebHookType(ctx.Request()), payload)
if err != nil {
return nil, err
}
switch e := event.(type) {
case *githubsdk.InstallationRepositoriesEvent:
if *e.Action == "added" {
err = g.handleInstallationEvent(*e.Sender.ID, e.Installation)
}
case *githubsdk.InstallationEvent:
if *e.Action == "created" || *e.Action == "added" {
err = g.handleInstallationEvent(*e.Sender.ID, e.Installation)
}
if *e.Action == "deleted" {
err = g.handleDeletionEvent(e.Installation)
}
}
if err != nil {
return nil, err
}
return nil, nil
}
func (g *GithubAppService) handleInstallationEvent(senderID int64, i *githubsdk.Installation) error {
// make sure the sender exists in the database
gao, err := g.config.Repository.Github().ReadGithubAppOAuthByGithubUserID(int(senderID))
if err != nil {
return err
}
_, err = g.config.Repository.Github().ReadGithubAppInstallationByInstallationAndAccountID(int(*i.ID), int(*i.Account.ID))
if err != nil && errors.Is(err, db.ErrNotFound) {
// insert account/installation pair into database
_, err := g.config.Repository.Github().CreateInstallation(gao.GithubUserID, &repository.CreateInstallationOpts{
InstallationID: int(*i.ID),
AccountID: int(*i.Account.ID),
AccountName: *i.Account.Login,
AccountAvatarURL: i.Account.AvatarURL,
InstallationSettingsURL: i.HTMLURL,
})
if err != nil {
return err
}
return nil
}
// associate the github user id with this installation in the database
_, err = g.config.Repository.Github().AddGithubUserIdToInstallation(int(*i.ID), int(*i.Account.ID), gao.GithubUserID)
return err
}
func (g *GithubAppService) handleDeletionEvent(i *githubsdk.Installation) error {
_, err := g.config.Repository.Github().ReadGithubAppInstallationByInstallationAndAccountID(int(*i.ID), int(*i.Account.ID))
if err != nil {
return err
}
_, err = g.config.Repository.Github().DeleteInstallation(int(*i.ID), int(*i.Account.ID))
if err != nil {
return err
}
return nil
}
@@ -37,6 +37,12 @@ func (t *StepRunService) StepRunUpdateRerun(ctx echo.Context, request gen.StepRu
), nil
}
err = t.config.Repository.StepRun().ArchiveStepRunResult(tenant.ID, stepRun.ID)
if err != nil {
return nil, fmt.Errorf("could not archive step run result: %w", err)
}
// make sure input can be marshalled and unmarshalled to input type
inputBytes, err := json.Marshal(request.Body.Input)
@@ -18,8 +18,8 @@ import (
)
// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow.
func (u *UserService) UserUpdateOauthCallback(ctx echo.Context, _ gen.UserUpdateOauthCallbackRequestObject) (gen.UserUpdateOauthCallbackResponseObject, error) {
isValid, _, err := authn.NewSessionHelpers(u.config).ValidateOAuthState(ctx)
func (u *UserService) UserUpdateGoogleOauthCallback(ctx echo.Context, _ gen.UserUpdateGoogleOauthCallbackRequestObject) (gen.UserUpdateGoogleOauthCallbackResponseObject, error) {
isValid, _, err := authn.NewSessionHelpers(u.config).ValidateOAuthState(ctx, "google")
if err != nil || !isValid {
return nil, authn.GetRedirectWithError(ctx, u.config.Logger, err, "Could not log in. Please try again and make sure cookies are enabled.")
@@ -47,8 +47,8 @@ func (u *UserService) UserUpdateOauthCallback(ctx echo.Context, _ gen.UserUpdate
return nil, authn.GetRedirectWithError(ctx, u.config.Logger, err, "Internal error.")
}
return gen.UserUpdateOauthCallback302Response{
Headers: gen.UserUpdateOauthCallback302ResponseHeaders{
return gen.UserUpdateGoogleOauthCallback302Response{
Headers: gen.UserUpdateGoogleOauthCallback302ResponseHeaders{
Location: u.config.Runtime.ServerURL,
},
}, nil
@@ -8,8 +8,8 @@ import (
)
// Note: we want all errors to redirect, otherwise the user will be greeted with raw JSON in the middle of the login flow.
func (u *UserService) UserUpdateOauthStart(ctx echo.Context, _ gen.UserUpdateOauthStartRequestObject) (gen.UserUpdateOauthStartResponseObject, error) {
state, err := authn.NewSessionHelpers(u.config).SaveOAuthState(ctx)
func (u *UserService) UserUpdateGoogleOauthStart(ctx echo.Context, _ gen.UserUpdateGoogleOauthStartRequestObject) (gen.UserUpdateGoogleOauthStartResponseObject, error) {
state, err := authn.NewSessionHelpers(u.config).SaveOAuthState(ctx, "google")
if err != nil {
return nil, authn.GetRedirectWithError(ctx, u.config.Logger, err, "Could not get cookie. Please make sure cookies are enabled.")
@@ -17,8 +17,8 @@ func (u *UserService) UserUpdateOauthStart(ctx echo.Context, _ gen.UserUpdateOau
url := u.config.Auth.GoogleOAuthConfig.AuthCodeURL(state)
return gen.UserUpdateOauthStart302Response{
Headers: gen.UserUpdateOauthStart302ResponseHeaders{
return gen.UserUpdateGoogleOauthStart302Response{
Headers: gen.UserUpdateGoogleOauthStart302ResponseHeaders{
Location: url,
},
}, nil
@@ -0,0 +1,28 @@
package workflows
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
"github.com/hatchet-dev/hatchet/internal/services/worker"
)
func (t *WorkflowService) StepRunUpdateCreatePr(ctx echo.Context, request gen.StepRunUpdateCreatePrRequestObject) (gen.StepRunUpdateCreatePrResponseObject, error) {
stepRun := ctx.Get("step-run").(*db.StepRunModel)
// trigger the workflow run
_, err := t.config.InternalClient.Admin().RunWorkflow(worker.PullRequestWorkflow, &worker.StartPullRequestEvent{
TenantID: stepRun.TenantID,
StepRunID: stepRun.ID,
BranchName: request.Body.BranchName,
})
if err != nil {
return nil, err
}
return nil, fmt.Errorf("not implemented")
}
@@ -0,0 +1,61 @@
package workflows
import (
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/apierrors"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/transformers"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
func (t *WorkflowService) WorkflowUpdateLinkGithub(ctx echo.Context, request gen.WorkflowUpdateLinkGithubRequestObject) (gen.WorkflowUpdateLinkGithubResponseObject, error) {
user := ctx.Get("user").(*db.UserModel)
workflow := ctx.Get("workflow").(*db.WorkflowModel)
// check that the user has access to the installation id
installationId := request.Body.InstallationId
_, err := t.config.Repository.Github().ReadGithubAppInstallationByID(installationId)
if err != nil {
return gen.WorkflowUpdateLinkGithub404JSONResponse(
apierrors.NewAPIErrors("Installation not found"),
), nil
}
if canAccess, err := t.config.Repository.Github().CanUserAccessInstallation(installationId, user.ID); err != nil || !canAccess {
return gen.WorkflowUpdateLinkGithub403JSONResponse(
apierrors.NewAPIErrors("User does not have access to the installation"),
), nil
}
_, err = t.config.Repository.Workflow().UpsertWorkflowDeploymentConfig(
workflow.ID,
&repository.UpsertWorkflowDeploymentConfigOpts{
GithubAppInstallationId: installationId,
GitRepoName: request.Body.GitRepoName,
GitRepoOwner: request.Body.GitRepoOwner,
GitRepoBranch: request.Body.GitRepoBranch,
},
)
if err != nil {
return nil, err
}
workflow, err = t.config.Repository.Workflow().GetWorkflowById(workflow.ID)
if err != nil {
return nil, err
}
resp, err := transformers.ToWorkflow(workflow, nil)
if err != nil {
return nil, err
}
return gen.WorkflowUpdateLinkGithub200JSONResponse(*resp), nil
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
package transformers
import (
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
func ToInstallation(gai *db.GithubAppInstallationModel) *gen.GithubAppInstallation {
res := &gen.GithubAppInstallation{
Metadata: *toAPIMetadata(gai.ID, gai.CreatedAt, gai.UpdatedAt),
AccountName: gai.AccountName,
}
if settingsUrl, ok := gai.InstallationSettingsURL(); ok {
res.InstallationSettingsUrl = settingsUrl
}
if avatarURL, ok := gai.AccountAvatarURL(); ok {
res.AccountAvatarUrl = avatarURL
}
return res
}
@@ -3,6 +3,8 @@ package transformers
import (
"context"
"github.com/google/uuid"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
@@ -45,6 +47,18 @@ func ToWorkflow(workflow *db.WorkflowModel, lastRun *db.WorkflowRunModel) (*gen.
}
}
if workflow.RelationsWorkflow.DeploymentConfig != nil {
if deploymentConfig, ok := workflow.DeploymentConfig(); ok && deploymentConfig != nil {
apiDeploymentConfig, err := ToWorkflowDeploymentConfig(deploymentConfig)
if err != nil {
return nil, err
}
res.Deployment = apiDeploymentConfig
}
}
if workflow.RelationsWorkflow.Versions != nil {
if versions := workflow.Versions(); versions != nil {
apiVersions := make([]gen.WorkflowVersionMeta, len(versions))
@@ -75,6 +89,26 @@ func ToWorkflowVersionMeta(version *db.WorkflowVersionModel) *gen.WorkflowVersio
return res
}
func ToWorkflowDeploymentConfig(deploymentConfig *db.WorkflowDeploymentConfigModel) (*gen.WorkflowDeploymentConfig, error) {
res := &gen.WorkflowDeploymentConfig{
Metadata: *toAPIMetadata(deploymentConfig.ID, deploymentConfig.CreatedAt, deploymentConfig.UpdatedAt),
GitRepoName: deploymentConfig.GitRepoName,
GitRepoOwner: deploymentConfig.GitRepoOwner,
GitRepoBranch: deploymentConfig.GitRepoBranch,
}
if githubAppInstallationId, ok := deploymentConfig.GithubAppInstallationID(); ok {
res.GithubAppInstallationId = uuid.MustParse(githubAppInstallationId)
}
if githubAppInstallation, ok := deploymentConfig.GithubAppInstallation(); ok {
apiInstallation := ToInstallation(githubAppInstallation)
res.GithubAppInstallation = apiInstallation
}
return res, nil
}
func ToWorkflowVersion(workflow *db.WorkflowModel, version *db.WorkflowVersionModel) (*gen.WorkflowVersion, error) {
res := &gen.WorkflowVersion{
Metadata: *toAPIMetadata(version.ID, version.CreatedAt, version.UpdatedAt),
+21 -8
View File
@@ -11,6 +11,7 @@ import (
"github.com/hatchet-dev/hatchet/api/v1/server/authz"
apitokens "github.com/hatchet-dev/hatchet/api/v1/server/handlers/api-tokens"
"github.com/hatchet-dev/hatchet/api/v1/server/handlers/events"
githubapp "github.com/hatchet-dev/hatchet/api/v1/server/handlers/github-app"
"github.com/hatchet-dev/hatchet/api/v1/server/handlers/metadata"
stepruns "github.com/hatchet-dev/hatchet/api/v1/server/handlers/step-runs"
"github.com/hatchet-dev/hatchet/api/v1/server/handlers/tenants"
@@ -33,18 +34,20 @@ type apiService struct {
*metadata.MetadataService
*apitokens.APITokenService
*stepruns.StepRunService
*githubapp.GithubAppService
}
func newAPIService(config *server.ServerConfig) *apiService {
return &apiService{
UserService: users.NewUserService(config),
TenantService: tenants.NewTenantService(config),
EventService: events.NewEventService(config),
WorkflowService: workflows.NewWorkflowService(config),
WorkerService: workers.NewWorkerService(config),
MetadataService: metadata.NewMetadataService(config),
APITokenService: apitokens.NewAPITokenService(config),
StepRunService: stepruns.NewStepRunService(config),
UserService: users.NewUserService(config),
TenantService: tenants.NewTenantService(config),
EventService: events.NewEventService(config),
WorkflowService: workflows.NewWorkflowService(config),
WorkerService: workers.NewWorkerService(config),
MetadataService: metadata.NewMetadataService(config),
APITokenService: apitokens.NewAPITokenService(config),
StepRunService: stepruns.NewStepRunService(config),
GithubAppService: githubapp.NewGithubAppService(config),
}
}
@@ -160,6 +163,16 @@ func (t *APIServer) Run(ctx context.Context) error {
return worker, worker.TenantID, nil
})
populatorMW.RegisterGetter("gh-installation", func(config *server.ServerConfig, parentId, id string) (result interface{}, uniqueParentId string, err error) {
ghInstallation, err := config.Repository.Github().ReadGithubAppInstallationByID(id)
if err != nil {
return nil, "", err
}
return ghInstallation, "", nil
})
authnMW := authn.NewAuthN(t.config)
authzMW := authz.NewAuthZ(t.config)
+61 -4
View File
@@ -3,11 +3,14 @@ package main
import (
"fmt"
"os"
"sync"
"time"
"github.com/spf13/cobra"
"github.com/hatchet-dev/hatchet/api/v1/server/run"
"github.com/hatchet-dev/hatchet/internal/config/loader"
"github.com/hatchet-dev/hatchet/internal/services/worker"
"github.com/hatchet-dev/hatchet/pkg/cmdutils"
)
@@ -63,14 +66,68 @@ func startServerOrDie(cf *loader.ConfigLoader, interruptCh <-chan interface{}) {
panic(err)
}
errCh := make(chan error)
ctx, cancel := cmdutils.InterruptContextFromChan(interruptCh)
defer cancel()
wg := sync.WaitGroup{}
runner := run.NewAPIServer(sc)
if sc.InternalClient != nil {
wg.Add(1)
err = runner.Run(ctx)
w, err := worker.NewWorker(
worker.WithRepository(sc.Repository),
worker.WithClient(sc.InternalClient),
worker.WithVCSProviders(sc.VCSProviders),
)
if err != nil {
panic(err)
if err != nil {
panic(err)
}
go func() {
defer wg.Done()
time.Sleep(5 * time.Second)
err := w.Start(ctx)
if err != nil {
errCh <- err
return
}
}()
}
wg.Add(1)
go func() {
defer wg.Done()
runner := run.NewAPIServer(sc)
err = runner.Run(ctx)
if err != nil {
errCh <- err
return
}
}()
Loop:
for {
select {
case err := <-errCh:
fmt.Fprintf(os.Stderr, "%s", err)
// exit with non-zero exit code
os.Exit(1) //nolint:gocritic
case <-interruptCh:
break Loop
}
}
cancel()
// TODO: should wait with a timeout
// wg.Wait()
}
+163 -4
View File
@@ -16,6 +16,7 @@ import {
AcceptInviteRequest,
CreateAPITokenRequest,
CreateAPITokenResponse,
CreatePullRequestFromStepRun,
CreateTenantInviteRequest,
CreateTenantRequest,
EventData,
@@ -25,7 +26,11 @@ import {
EventOrderByDirection,
EventOrderByField,
EventSearch,
LinkGithubRepositoryRequest,
ListAPITokensResponse,
ListGithubAppInstallationsResponse,
ListGithubBranchesResponse,
ListGithubReposResponse,
RejectInviteRequest,
ReplayEventRequest,
RerunStepRunRequest,
@@ -89,11 +94,11 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
* @description Starts the OAuth flow
*
* @tags User
* @name UserUpdateOauthStart
* @name UserUpdateGoogleOauthStart
* @summary Start OAuth flow
* @request GET:/api/v1/users/google/start
*/
userUpdateOauthStart = (params: RequestParams = {}) =>
userUpdateGoogleOauthStart = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/google/start`,
method: "GET",
@@ -103,16 +108,76 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
* @description Completes the OAuth flow
*
* @tags User
* @name UserUpdateOauthCallback
* @name UserUpdateGoogleOauthCallback
* @summary Complete OAuth flow
* @request GET:/api/v1/users/google/callback
*/
userUpdateOauthCallback = (params: RequestParams = {}) =>
userUpdateGoogleOauthCallback = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/google/callback`,
method: "GET",
...params,
});
/**
* @description Starts the OAuth flow
*
* @tags User
* @name UserUpdateGithubOauthStart
* @summary Start OAuth flow
* @request GET:/api/v1/users/github/start
* @secure
*/
userUpdateGithubOauthStart = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/github/start`,
method: "GET",
secure: true,
...params,
});
/**
* @description Completes the OAuth flow
*
* @tags User
* @name UserUpdateGithubOauthCallback
* @summary Complete OAuth flow
* @request GET:/api/v1/users/github/callback
* @secure
*/
userUpdateGithubOauthCallback = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/github/callback`,
method: "GET",
secure: true,
...params,
});
/**
* @description Github App global webhook
*
* @tags Github
* @name GithubUpdateGlobalWebhook
* @summary Github app global webhook
* @request POST:/api/v1/github/webhook
*/
githubUpdateGlobalWebhook = (params: RequestParams = {}) =>
this.request<void, APIErrors>({
path: `/api/v1/github/webhook`,
method: "POST",
...params,
});
/**
* @description Github App tenant webhook
*
* @tags Github
* @name GithubUpdateTenantWebhook
* @summary Github app tenant webhook
* @request POST:/api/v1/github/webhook/{webhook}
*/
githubUpdateTenantWebhook = (webhook: string, params: RequestParams = {}) =>
this.request<void, APIErrors>({
path: `/api/v1/github/webhook/${webhook}`,
method: "POST",
...params,
});
/**
* @description Gets the current user
*
@@ -620,6 +685,44 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
format: "json",
...params,
});
/**
* @description Link a github repository to a workflow
*
* @tags Workflow
* @name WorkflowUpdateLinkGithub
* @summary Link github repository
* @request POST:/api/v1/workflows/{workflow}/link-github
* @secure
*/
workflowUpdateLinkGithub = (workflow: string, data: LinkGithubRepositoryRequest, params: RequestParams = {}) =>
this.request<Workflow, APIErrors>({
path: `/api/v1/workflows/${workflow}/link-github`,
method: "POST",
body: data,
secure: true,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description Create a pull request for a workflow
*
* @tags Workflow
* @name StepRunUpdateCreatePr
* @summary Create pull request
* @request POST:/api/v1/step-runs/{step-run}/create-pr
* @secure
*/
stepRunUpdateCreatePr = (stepRun: string, data: CreatePullRequestFromStepRun, params: RequestParams = {}) =>
this.request<CreatePullRequestFromStepRun, APIErrors>({
path: `/api/v1/step-runs/${stepRun}/create-pr`,
method: "POST",
body: data,
secure: true,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description Get all workflow runs for a tenant
*
@@ -754,4 +857,60 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
format: "json",
...params,
});
/**
* @description List Github App installations
*
* @tags Github
* @name GithubAppListInstallations
* @summary List Github App installations
* @request GET:/api/v1/github-app/installations
* @secure
*/
githubAppListInstallations = (params: RequestParams = {}) =>
this.request<ListGithubAppInstallationsResponse, APIErrors>({
path: `/api/v1/github-app/installations`,
method: "GET",
secure: true,
format: "json",
...params,
});
/**
* @description List Github App repositories
*
* @tags Github
* @name GithubAppListRepos
* @summary List Github App repositories
* @request GET:/api/v1/github-app/installations/{gh-installation}/repos
* @secure
*/
githubAppListRepos = (ghInstallation: string, params: RequestParams = {}) =>
this.request<ListGithubReposResponse, APIErrors>({
path: `/api/v1/github-app/installations/${ghInstallation}/repos`,
method: "GET",
secure: true,
format: "json",
...params,
});
/**
* @description List Github App branches
*
* @tags Github
* @name GithubAppListBranches
* @summary List Github App branches
* @request GET:/api/v1/github-app/installations/{gh-installation}/repos/{gh-repo-owner}/{gh-repo-name}/branches
* @secure
*/
githubAppListBranches = (
ghInstallation: string,
ghRepoOwner: string,
ghRepoName: string,
params: RequestParams = {},
) =>
this.request<ListGithubBranchesResponse, APIErrors>({
path: `/api/v1/github-app/installations/${ghInstallation}/repos/${ghRepoOwner}/${ghRepoName}/branches`,
method: "GET",
secure: true,
format: "json",
...params,
});
}
@@ -320,6 +320,24 @@ export interface Workflow {
lastRun?: WorkflowRun;
/** The jobs of the workflow. */
jobs?: Job[];
deployment?: WorkflowDeploymentConfig;
}
export interface WorkflowDeploymentConfig {
metadata: APIResourceMeta;
/** The repository name. */
gitRepoName: string;
/** The repository owner. */
gitRepoOwner: string;
/** The repository branch. */
gitRepoBranch: string;
/** The Github App installation. */
githubAppInstallation?: GithubAppInstallation;
/**
* The id of the Github App installation.
* @format uuid
*/
githubAppInstallationId: string;
}
export interface WorkflowVersionMeta {
@@ -579,3 +597,48 @@ export interface RerunStepRunRequest {
export interface TriggerWorkflowRunRequest {
input: object;
}
export interface LinkGithubRepositoryRequest {
/**
* The repository name.
* @minLength 36
* @maxLength 36
*/
installationId: string;
/** The repository name. */
gitRepoName: string;
/** The repository owner. */
gitRepoOwner: string;
/** The repository branch. */
gitRepoBranch: string;
}
export interface GithubBranch {
branch_name: string;
is_default: boolean;
}
export interface GithubRepo {
repo_owner: string;
repo_name: string;
}
export interface GithubAppInstallation {
metadata: APIResourceMeta;
installation_settings_url: string;
account_name: string;
account_avatar_url: string;
}
export interface ListGithubAppInstallationsResponse {
pagination: PaginationResponse;
rows: GithubAppInstallation[];
}
export type ListGithubReposResponse = GithubRepo[];
export type ListGithubBranchesResponse = GithubBranch[];
export interface CreatePullRequestFromStepRun {
branchName: string;
}
+33
View File
@@ -1,6 +1,7 @@
import { createQueryKeyStore } from '@lukemorales/query-key-factory';
import api from './api';
import invariant from 'tiny-invariant';
type ListEventQuery = Parameters<typeof api.eventList>[1];
type ListWorkflowRunsQuery = Parameters<typeof api.workflowRunList>[1];
@@ -102,4 +103,36 @@ export const queries = createQueryKeyStore({
queryFn: async () => (await api.workerGet(worker)).data,
}),
},
github: {
listInstallations: {
queryKey: ['github-app:list:installations'],
queryFn: async () => (await api.githubAppListInstallations()).data,
},
listRepos: (installation?: string) => ({
queryKey: ['github-app:list:repos', installation],
queryFn: async () => {
invariant(installation, 'Installation must be set');
const res = (await api.githubAppListRepos(installation)).data;
return res;
},
enabled: !!installation,
}),
listBranches: (
installation?: string,
repoOwner?: string,
repoName?: string,
) => ({
queryKey: ['github-app:list:branches', installation, repoOwner, repoName],
queryFn: async () => {
invariant(installation, 'Installation must be set');
invariant(repoOwner, 'Repo owner must be set');
invariant(repoName, 'Repo name must be set');
const res = (
await api.githubAppListBranches(installation, repoOwner, repoName)
).data;
return res;
},
enabled: !!installation && !!repoOwner && !!repoName,
}),
},
});
@@ -0,0 +1,52 @@
import { ColumnDef } from '@tanstack/react-table';
import { DataTableColumnHeader } from '../../../../components/molecules/data-table/data-table-column-header';
import { GithubAppInstallation } from '@/lib/api';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { GearIcon } from '@radix-ui/react-icons';
export const columns = (): ColumnDef<GithubAppInstallation>[] => {
return [
{
accessorKey: 'repository',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Account name" />
),
cell: ({ row }) => {
return (
<div className="flex flex-row gap-4 items-center">
<Avatar className="w-6 h-6">
<AvatarImage src={row.original.account_avatar_url} />
<AvatarFallback />
</Avatar>
<div>{row.original.account_name}</div>
</div>
);
},
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'settings',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Settings" />
),
cell: ({ row }) => {
return (
<a
href={row.original.installation_settings_url}
target="_blank"
rel="noreferrer"
>
<Button variant="ghost" className="flex flex-row gap-2 px-2">
<GearIcon className="h-4 w-4" />
Configure
</Button>
</a>
);
},
enableSorting: false,
enableHiding: false,
},
];
};
@@ -51,8 +51,7 @@ export function UpdateInviteForm({
},
});
const roleError =
errors.role?.message?.toString() || props.fieldErrors?.password;
const roleError = errors.role?.message?.toString() || props.fieldErrors?.role;
return (
<DialogContent className="w-fit max-w-[80%] min-w-[500px]">
@@ -19,6 +19,7 @@ import { DataTable } from '@/components/molecules/data-table/data-table';
import { columns } from './components/invites-columns';
import { columns as membersColumns } from './components/members-columns';
import { columns as apiTokensColumns } from './components/api-tokens-columns';
import { columns as githubInstallationsColumns } from './components/github-installations-columns';
import { UpdateInviteForm } from './components/update-invite-form';
import { DeleteInviteForm } from './components/delete-invite-form';
import { CreateTokenDialog } from './components/create-token-dialog';
@@ -39,6 +40,8 @@ export default function TenantSettings() {
<InvitesList />
<Separator className="my-4" />
<TokensList />
<Separator className="my-4" />
<GithubInstallationsList />
</div>
</div>
);
@@ -388,3 +391,32 @@ function RevokeToken({
</Dialog>
);
}
function GithubInstallationsList() {
const listInstallationsQuery = useQuery({
...queries.github.listInstallations,
});
const cols = githubInstallationsColumns();
return (
<div>
<div className="flex flex-row justify-between items-center">
<h3 className="text-xl font-semibold leading-tight text-foreground">
Github Accounts
</h3>
<a href="/api/v1/users/github/start">
<Button key="create-api-token">Link new account</Button>
</a>
</div>
<Separator className="my-4" />
<DataTable
isLoading={listInstallationsQuery.isLoading}
columns={cols}
data={listInstallationsQuery.data?.rows || []}
filters={[]}
getRowId={(row) => row.metadata.id}
/>
</div>
);
}
@@ -0,0 +1,292 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import api, { LinkGithubRepositoryRequest, Workflow, queries } from '@/lib/api';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { useApiError } from '@/lib/hooks';
import { useMutation, useQuery } from '@tanstack/react-query';
import { PlusIcon } from '@heroicons/react/24/outline';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { z } from 'zod';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Label } from '@/components/ui/label';
const schema = z.object({
installation: z.string(),
repoOwnerName: z.string(),
branch: z.string(),
});
export function DeploymentSettingsForm({
workflow,
show,
onClose,
}: {
workflow: Workflow;
show: boolean;
onClose: () => void;
}) {
const [errors, setErrors] = useState<string[]>([]);
const { handleApiError } = useApiError({
setErrors,
});
const linkGithubMutation = useMutation({
mutationKey: ['workflow:update:link-github', workflow?.metadata.id],
mutationFn: async (input: LinkGithubRepositoryRequest) => {
if (!workflow) {
return;
}
const res = await api.workflowUpdateLinkGithub(
workflow?.metadata.id,
input,
);
return res.data;
},
onMutate: () => {
setErrors([]);
},
onSuccess: () => {
onClose();
},
onError: handleApiError,
});
return (
<Dialog
open={!!workflow && show}
onOpenChange={(open) => {
if (!open) {
onClose();
}
}}
>
<DialogContent className="sm:max-w-[625px] py-12">
<DialogHeader>
<DialogTitle>Deployment settings</DialogTitle>
<DialogDescription>
You can change the deployment settings of your workflow here.
</DialogDescription>
</DialogHeader>
<InnerForm
workflow={workflow}
onSubmit={(opts) => {
const repoOwner = getRepoOwner(opts.repoOwnerName);
const repoName = getRepoName(opts.repoOwnerName);
if (!repoOwner || !repoName) {
return;
}
linkGithubMutation.mutate({
installationId: opts.installation,
gitRepoOwner: repoOwner,
gitRepoName: repoName,
gitRepoBranch: opts.branch,
});
}}
isLoading={linkGithubMutation.isPending}
errors={errors}
/>
</DialogContent>
</Dialog>
);
}
interface InnerFormProps {
onSubmit: (opts: z.infer<typeof schema>) => void;
isLoading: boolean;
errors?: string[];
workflow: Workflow;
}
function InnerForm({ workflow, onSubmit, isLoading, errors }: InnerFormProps) {
const { watch, handleSubmit, control, setValue } = useForm<
z.infer<typeof schema>
>({
resolver: zodResolver(schema),
defaultValues: {
installation: workflow.deployment?.githubAppInstallationId,
repoOwnerName:
workflow.deployment?.gitRepoOwner &&
workflow.deployment?.gitRepoName &&
getRepoOwnerName(
workflow.deployment?.gitRepoOwner,
workflow.deployment?.gitRepoName,
),
branch: workflow.deployment?.gitRepoBranch,
},
});
const installation = watch('installation');
const repoOwnerName = watch('repoOwnerName');
const branch = watch('branch');
const listInstallationsQuery = useQuery({
...queries.github.listInstallations,
});
const listReposQuery = useQuery({
...queries.github.listRepos(installation),
});
const listBranchesQuery = useQuery({
...queries.github.listBranches(
installation,
getRepoOwner(repoOwnerName),
getRepoName(repoOwnerName),
),
});
useEffect(() => {
if (
listInstallationsQuery.isSuccess &&
listInstallationsQuery.data.rows.length > 0 &&
!installation
) {
setValue(
'installation',
listInstallationsQuery.data?.rows[0]?.metadata.id,
);
}
}, [listInstallationsQuery, setValue, installation]);
return (
<>
<div className="grid gap-4">
<Label htmlFor="role">Github account</Label>
<Controller
control={control}
name="installation"
render={({ field }) => {
return (
<Select
onValueChange={(value: string) => {
field.onChange(value);
setValue('repoOwnerName', '');
setValue('branch', '');
}}
{...field}
>
<SelectTrigger className="w-[180px]">
<SelectValue id="role" placeholder="Choose account" />
</SelectTrigger>
<SelectContent>
{listInstallationsQuery.data?.rows.map((i) => (
<SelectItem key={i.metadata.id} value={i.metadata.id}>
{i.account_name}
</SelectItem>
))}
</SelectContent>
</Select>
);
}}
/>
<Label htmlFor="role">Github repo</Label>
<Controller
control={control}
name="repoOwnerName"
render={({ field }) => {
return (
<Select
onValueChange={(value) => {
field.onChange(value);
setValue('branch', '');
}}
{...field}
>
<SelectTrigger className="w-[180px]">
<SelectValue id="role" placeholder="Choose repository" />
</SelectTrigger>
<SelectContent>
{listReposQuery.data?.map((i) => (
<SelectItem
key={i.repo_owner + i.repo_name}
value={getRepoOwnerName(i.repo_owner, i.repo_name)}
>
{i.repo_owner}/{i.repo_name}
</SelectItem>
))}
</SelectContent>
</Select>
);
}}
/>
<Label htmlFor="role">Github branch</Label>
<Controller
control={control}
name="branch"
render={({ field }) => {
return (
<Select onValueChange={field.onChange} {...field}>
<SelectTrigger className="w-[180px]">
<SelectValue id="role" placeholder="Choose branch" />
</SelectTrigger>
<SelectContent>
{listBranchesQuery.data?.map((i) => (
<SelectItem key={i.branch_name} value={i.branch_name}>
{i.branch_name}
</SelectItem>
))}
</SelectContent>
</Select>
);
}}
/>
{errors?.map((error, index) => (
<div key={index} className="text-red-500 text-sm">
{error}
</div>
))}
<Button
disabled={!installation || !repoOwnerName || !branch}
onClick={handleSubmit(onSubmit)}
>
{isLoading && <PlusIcon className="h-4 w-4 animate-spin" />}
Save
</Button>
</div>
</>
);
}
function getRepoOwnerName(repoOwner: string, repoName: string) {
return `${repoOwner}::${repoName}`;
}
function getRepoOwner(repoOwnerName?: string) {
if (!repoOwnerName) {
return;
}
const splArr = repoOwnerName.split('::');
if (splArr.length > 1) {
return splArr[0];
}
}
function getRepoName(repoOwnerName?: string) {
if (!repoOwnerName) {
return;
}
const splArr = repoOwnerName.split('::');
if (splArr.length > 1) {
return splArr[1];
}
}
@@ -9,6 +9,7 @@ import {
useLoaderData,
useOutletContext,
useParams,
useRevalidator,
} from 'react-router-dom';
import invariant from 'tiny-invariant';
import { columns } from '../../workflow-runs/components/workflow-runs-columns';
@@ -22,6 +23,7 @@ import WorkflowVisualizer from './components/workflow-visualizer';
import { TriggerWorkflowForm } from './components/trigger-workflow-form';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { DeploymentSettingsForm } from './components/deployment-settings-form';
type WorkflowWithVersion = {
workflow: Workflow;
@@ -69,6 +71,7 @@ export async function loader({
export default function ExpandedWorkflow() {
const [triggerWorkflow, setTriggerWorkflow] = useState(false);
const loaderData = useLoaderData() as Awaited<ReturnType<typeof loader>>;
const revalidator = useRevalidator();
if (!loaderData) {
return <Loading />;
@@ -127,6 +130,14 @@ export default function ExpandedWorkflow() {
</h3>
<Separator className="my-4" />
<RecentRunsList />
<h3 className="text-xl font-bold leading-tight text-foreground mt-8">
Deployment Settings
</h3>
<Separator className="my-4" />
<DeploymentSettings
workflow={workflow}
refetch={revalidator.revalidate}
/>
</div>
</div>
);
@@ -160,3 +171,33 @@ function RecentRunsList() {
/>
);
}
function DeploymentSettings({
workflow,
refetch,
}: {
workflow: Workflow;
refetch?: () => void;
}) {
const [show, setShow] = useState(false);
return (
<div>
<Button
className="text-sm"
onClick={() => setShow(true)}
variant="outline"
>
Change settings
</Button>
<DeploymentSettingsForm
workflow={workflow}
show={show}
onClose={() => {
setShow(false);
refetch?.();
}}
/>
</div>
);
}
+4
View File
@@ -8,6 +8,7 @@ require (
github.com/fatih/color v1.16.0
github.com/getkin/kin-openapi v0.122.0
github.com/go-co-op/gocron/v2 v2.1.2
github.com/google/go-github/v57 v57.0.0
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.2.2
github.com/hashicorp/go-multierror v1.1.1
@@ -48,7 +49,9 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
@@ -82,6 +85,7 @@ require (
)
require (
github.com/bradleyfalzon/ghinstallation/v2 v2.9.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getsentry/sentry-go v0.25.0
+8
View File
@@ -50,6 +50,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bradleyfalzon/ghinstallation/v2 v2.9.0 h1:HmxIYqnxubRYcYGRc5v3wUekmo5Wv2uX3gukmWJ0AFk=
github.com/bradleyfalzon/ghinstallation/v2 v2.9.0/go.mod h1:wmkTDJf8CmVypxE8ijIStFnKoTa6solK5QfdmJrP9KI=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
@@ -124,6 +126,8 @@ github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -172,6 +176,10 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+75 -12
View File
@@ -18,12 +18,17 @@ import (
"github.com/hatchet-dev/hatchet/internal/config/loader/loaderutils"
"github.com/hatchet-dev/hatchet/internal/config/server"
"github.com/hatchet-dev/hatchet/internal/encryption"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs/github"
"github.com/hatchet-dev/hatchet/internal/logger"
"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"
"github.com/hatchet-dev/hatchet/internal/taskqueue/rabbitmq"
"github.com/hatchet-dev/hatchet/internal/validator"
"github.com/hatchet-dev/hatchet/pkg/client"
clientconfig "github.com/hatchet-dev/hatchet/internal/config/client"
)
// LoadDatabaseConfigFile loads the database config file via viper
@@ -217,19 +222,77 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF
return nil, fmt.Errorf("could not create JWT manager: %w", err)
}
vcsProviders := make(map[vcs.VCSRepositoryKind]vcs.VCSProvider)
if cf.VCS.Github.Enabled {
var err error
githubAppConf, err := github.NewGithubAppConf(&oauth.Config{
ClientID: cf.VCS.Github.GithubAppClientID,
ClientSecret: cf.VCS.Github.GithubAppClientSecret,
Scopes: []string{"read:user"},
BaseURL: cf.Runtime.ServerURL,
}, cf.VCS.Github.GithubAppName, cf.VCS.Github.GithubAppSecretPath, cf.VCS.Github.GithubAppWebhookSecret, cf.VCS.Github.GithubAppID)
if err != nil {
return nil, err
}
githubProvider := github.NewGithubVCSProvider(githubAppConf, dc.Repository, cf.Runtime.ServerURL, encryptionSvc)
vcsProviders[vcs.VCSRepositoryKindGithub] = githubProvider
}
var internalClient client.Client
if cf.Runtime.WorkerEnabled {
// get the internal tenant or create if it doesn't exist
internalTenant, err := dc.Repository.Tenant().GetTenantBySlug("internal")
if err != nil {
return nil, fmt.Errorf("could not get internal tenant: %w", err)
}
tokenSuffix, err := encryption.GenerateRandomBytes(4)
if err != nil {
return nil, fmt.Errorf("could not generate token suffix: %w", err)
}
// generate a token for the internal client
token, err := auth.JWTManager.GenerateTenantToken(internalTenant.ID, fmt.Sprintf("internal-%s", tokenSuffix))
if err != nil {
return nil, fmt.Errorf("could not generate internal token: %w", err)
}
internalClient, err = client.NewFromConfigFile(
&clientconfig.ClientConfigFile{
Token: token,
HostPort: cf.Runtime.GRPCBroadcastAddress,
},
)
if err != nil {
return nil, fmt.Errorf("could not create internal client: %w", err)
}
}
return &server.ServerConfig{
Runtime: cf.Runtime,
Auth: auth,
Encryption: encryptionSvc,
Config: dc,
TaskQueue: tq,
Services: cf.Services,
Logger: &l,
TLSConfig: tls,
SessionStore: ss,
Validator: validator.NewDefaultValidator(),
Ingestor: ingestor,
OpenTelemetry: cf.OpenTelemetry,
Runtime: cf.Runtime,
Auth: auth,
Encryption: encryptionSvc,
Config: dc,
TaskQueue: tq,
Services: cf.Services,
Logger: &l,
TLSConfig: tls,
SessionStore: ss,
Validator: validator.NewDefaultValidator(),
Ingestor: ingestor,
OpenTelemetry: cf.OpenTelemetry,
VCSProviders: vcsProviders,
InternalClient: internalClient,
}, nil
}
+36
View File
@@ -12,9 +12,11 @@ import (
"github.com/hatchet-dev/hatchet/internal/config/database"
"github.com/hatchet-dev/hatchet/internal/config/shared"
"github.com/hatchet-dev/hatchet/internal/encryption"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
"github.com/hatchet-dev/hatchet/internal/services/ingestor"
"github.com/hatchet-dev/hatchet/internal/taskqueue"
"github.com/hatchet-dev/hatchet/internal/validator"
"github.com/hatchet-dev/hatchet/pkg/client"
)
type ServerConfigFile struct {
@@ -33,6 +35,8 @@ type ServerConfigFile struct {
Logger shared.LoggerConfigFile `mapstructure:"logger" json:"logger,omitempty"`
OpenTelemetry shared.OpenTelemetryConfigFile `mapstructure:"otel" json:"otel,omitempty"`
VCS ConfigFileVCS `mapstructure:"vcs" json:"vcs,omitempty"`
}
// General server runtime options
@@ -54,6 +58,9 @@ type ConfigFileRuntime struct {
// GRPCInsecure controls whether the grpc server is insecure or uses certs
GRPCInsecure bool `mapstructure:"grpcInsecure" json:"grpcInsecure,omitempty" default:"false"`
// Whether the internal worker is enabled for this instance
WorkerEnabled bool `mapstructure:"workerEnabled" json:"workerEnabled,omitempty" default:"false"`
}
// Encryption options
@@ -116,6 +123,20 @@ type ConfigFileAuth struct {
Google ConfigFileAuthGoogle `mapstructure:"google" json:"google,omitempty"`
}
type ConfigFileVCS struct {
Github ConfigFileGithub `mapstructure:"github" json:"github,omitempty"`
}
type ConfigFileGithub struct {
Enabled bool `mapstructure:"enabled" json:"enabled"`
GithubAppClientID string `mapstructure:"appClientID" json:"appClientID,omitempty"`
GithubAppClientSecret string `mapstructure:"appClientSecret" json:"appClientSecret,omitempty"`
GithubAppName string `mapstructure:"appName" json:"appName,omitempty"`
GithubAppWebhookSecret string `mapstructure:"appWebhookSecret" json:"appWebhookSecret,omitempty"`
GithubAppID string `mapstructure:"appID" json:"appID,omitempty"`
GithubAppSecretPath string `mapstructure:"appSecretPath" json:"appSecretPath,omitempty"`
}
type ConfigFileAuthGoogle struct {
Enabled bool `mapstructure:"enabled" json:"enabled,omitempty" default:"false"`
@@ -175,6 +196,10 @@ type ServerConfig struct {
Ingestor ingestor.Ingestor
OpenTelemetry shared.OpenTelemetryConfigFile
VCSProviders map[vcs.VCSRepositoryKind]vcs.VCSProvider
InternalClient client.Client
}
func (c *ServerConfig) HasService(name string) bool {
@@ -195,6 +220,7 @@ func BindAllEnv(v *viper.Viper) {
_ = v.BindEnv("runtime.grpcBindAddress", "SERVER_GRPC_BIND_ADDRESS")
_ = v.BindEnv("runtime.grpcBroadcastAddress", "SERVER_GRPC_BROADCAST_ADDRESS")
_ = v.BindEnv("runtime.grpcInsecure", "SERVER_GRPC_INSECURE")
_ = v.BindEnv("runtime.workerEnabled", "SERVER_WORKER_ENABLED")
_ = v.BindEnv("services", "SERVER_SERVICES")
// encryption options
@@ -242,4 +268,14 @@ func BindAllEnv(v *viper.Viper) {
// otel options
_ = v.BindEnv("otel.serviceName", "SERVER_OTEL_SERVICE_NAME")
_ = v.BindEnv("otel.collectorURL", "SERVER_OTEL_COLLECTOR_URL")
// vcs options
v.BindEnv("vcs.kind", "SERVER_VCS_KIND")
v.BindEnv("vcs.github.enabled", "SERVER_VCS_GITHUB_ENABLED")
v.BindEnv("vcs.github.appClientID", "SERVER_VCS_GITHUB_APP_CLIENT_ID")
v.BindEnv("vcs.github.appClientSecret", "SERVER_VCS_GITHUB_APP_CLIENT_SECRET")
v.BindEnv("vcs.github.appName", "SERVER_VCS_GITHUB_APP_NAME")
v.BindEnv("vcs.github.appWebhookSecret", "SERVER_VCS_GITHUB_APP_WEBHOOK_SECRET")
v.BindEnv("vcs.github.appID", "SERVER_VCS_GITHUB_APP_ID")
v.BindEnv("vcs.github.appSecretPath", "SERVER_VCS_GITHUB_APP_SECRET_PATH")
}
@@ -0,0 +1,17 @@
package github
import (
githubsdk "github.com/google/go-github/v57/github"
)
type GithubBranch struct {
*githubsdk.Branch
}
func (g *GithubBranch) GetName() string {
return g.Branch.GetName()
}
func (g *GithubBranch) GetLatestRef() string {
return g.GetCommit().GetSHA()
}
@@ -0,0 +1,23 @@
package github
import (
githubsdk "github.com/google/go-github/v57/github"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
)
type GithubCommitsComparison struct {
*githubsdk.CommitsComparison
}
func (g *GithubCommitsComparison) GetFiles() []vcs.CommitFile {
var res []vcs.CommitFile
for _, f := range g.CommitsComparison.Files {
res = append(res, vcs.CommitFile{
Name: f.GetFilename(),
})
}
return res
}
+363
View File
@@ -0,0 +1,363 @@
package github
import (
"context"
"errors"
"fmt"
"io"
"net/url"
githubsdk "github.com/google/go-github/v57/github"
"github.com/hatchet-dev/hatchet/internal/encryption"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
type GithubVCSProvider struct {
repo repository.Repository
appConf *GithubAppConf
serverURL string
enc encryption.EncryptionService
}
func NewGithubVCSProvider(appConf *GithubAppConf, repo repository.Repository, serverURL string, enc encryption.EncryptionService) GithubVCSProvider {
return GithubVCSProvider{
appConf: appConf,
repo: repo,
serverURL: serverURL,
enc: enc,
}
}
func ToGithubVCSProvider(provider vcs.VCSProvider) (res GithubVCSProvider, err error) {
res, ok := provider.(GithubVCSProvider)
if !ok {
return res, fmt.Errorf("could not convert VCS provider to Github VCS provider: %w", err)
}
return res, nil
}
func (g GithubVCSProvider) GetGithubAppConfig() *GithubAppConf {
return g.appConf
}
func (g GithubVCSProvider) GetVCSRepositoryFromWorkflow(workflow *db.WorkflowModel) (vcs.VCSRepository, error) {
var installationId string
var deploymentConf *db.WorkflowDeploymentConfigModel
var ok bool
if deploymentConf, ok = workflow.DeploymentConfig(); ok {
if installationId, ok = deploymentConf.GithubAppInstallationID(); !ok {
return nil, fmt.Errorf("module does not have github app installation id param set")
}
}
gai, err := g.repo.Github().ReadGithubAppInstallationByID(installationId)
if err != nil {
return nil, err
}
client, err := g.appConf.GetGithubClient(int64(gai.InstallationID))
if err != nil {
return nil, err
}
return &GithubVCSRepository{
repoOwner: deploymentConf.GitRepoOwner,
repoName: deploymentConf.GitRepoName,
serverURL: g.serverURL,
client: client,
repo: g.repo,
enc: g.enc,
}, nil
}
// func (g GithubVCSProvider) GetVCSRepositoryFromGAI(gai *models.GithubAppInstallation) (vcs.VCSRepository, error) {
// }
type GithubVCSRepository struct {
repoOwner, repoName string
client *githubsdk.Client
repo repository.Repository
serverURL string
enc encryption.EncryptionService
}
// GetKind returns the kind of VCS provider -- used for downstream integrations
func (g *GithubVCSRepository) GetKind() vcs.VCSRepositoryKind {
return vcs.VCSRepositoryKindGithub
}
func (g *GithubVCSRepository) GetRepoOwner() string {
return g.repoOwner
}
func (g *GithubVCSRepository) GetRepoName() string {
return g.repoName
}
// SetupRepository sets up a VCS repository on Hatchet.
func (g *GithubVCSRepository) SetupRepository(tenantId string) error {
repoOwner := g.GetRepoOwner()
repoName := g.GetRepoName()
_, err := g.repo.Github().ReadGithubWebhook(tenantId, repoOwner, repoName)
if err != nil && !errors.Is(err, db.ErrNotFound) {
return err
} else if err != nil {
opts, signingSecret, err := repository.NewGithubWebhookCreateOpts(g.enc, repoOwner, repoName)
if err != nil {
return err
}
gw, err := g.repo.Github().CreateGithubWebhook(tenantId, opts)
if err != nil {
return err
}
webhookURL := fmt.Sprintf("%s/api/v1/teams/%s/github_incoming/%s", g.serverURL, tenantId, gw.ID)
_, _, err = g.client.Repositories.CreateHook(
context.Background(), repoOwner, repoName, &githubsdk.Hook{
Config: map[string]interface{}{
"url": webhookURL,
"content_type": "json",
"secret": signingSecret,
},
Events: []string{"pull_request", "push"},
Active: githubsdk.Bool(true),
},
)
return err
}
return nil
}
// GetArchiveLink returns an archive link for a specific repo SHA
func (g *GithubVCSRepository) GetArchiveLink(ref string) (*url.URL, error) {
gURL, _, err := g.client.Repositories.GetArchiveLink(
context.TODO(),
g.GetRepoOwner(),
g.GetRepoName(),
githubsdk.Zipball,
&githubsdk.RepositoryContentGetOptions{
Ref: ref,
},
2,
)
return gURL, err
}
// GetBranch gets a full branch (name and sha)
func (g *GithubVCSRepository) GetBranch(name string) (vcs.VCSBranch, error) {
branchResp, _, err := g.client.Repositories.GetBranch(
context.TODO(),
g.GetRepoOwner(),
g.GetRepoName(),
name,
2,
)
if err != nil {
return nil, err
}
return &GithubBranch{branchResp}, nil
}
// ReadFile returns a file by a SHA reference or path
func (g *GithubVCSRepository) ReadFile(ref, path string) (io.ReadCloser, error) {
file, _, err := g.client.Repositories.DownloadContents(
context.Background(),
g.GetRepoOwner(),
g.GetRepoName(),
path,
&githubsdk.RepositoryContentGetOptions{
Ref: ref,
},
)
return file, err
}
func (g *GithubVCSRepository) ReadDirectory(ref, path string) ([]vcs.DirectoryItem, error) {
_, dirs, _, err := g.client.Repositories.GetContents(
context.Background(),
g.GetRepoOwner(),
g.GetRepoName(),
path,
&githubsdk.RepositoryContentGetOptions{
Ref: ref,
},
)
if err != nil {
return nil, err
}
res := []vcs.DirectoryItem{}
for _, item := range dirs {
if item.Type != nil && item.Name != nil {
res = append(res, vcs.DirectoryItem{
Type: *item.Type,
Name: *item.Name,
})
}
}
return res, nil
}
func (g *GithubVCSRepository) CreateOrUpdatePullRequest(tenantId, workflowRunId string, opts *vcs.CreatePullRequestOpts) (*db.GithubPullRequestModel, error) {
// determine if there's an open pull request for this workflow run
prs, err := g.repo.WorkflowRun().ListPullRequestsForWorkflowRun(tenantId, workflowRunId, &repository.ListPullRequestsForWorkflowRunOpts{
State: repository.StringPtr("open"),
})
if err != nil {
return nil, err
}
if len(prs) > 0 {
// double check that the PR is still open, cycle through PRs to find the first open one
for _, pr := range prs {
prCp := pr
ghPR, err := g.getPullRequest(tenantId, workflowRunId, &prCp)
if err != nil {
return nil, err
}
if prCp.PullRequestState != ghPR.GetState() {
defer g.repo.Github().UpdatePullRequest(tenantId, prCp.ID, &repository.UpdatePullRequestOpts{ // nolint: errcheck
State: repository.StringPtr(ghPR.GetState()),
})
}
if ghPR.GetState() == "open" {
return g.updatePullRequest(tenantId, workflowRunId, &prCp, opts)
}
}
}
// if we get here, we need to create a new PR
return g.createPullRequest(tenantId, workflowRunId, opts)
}
func (g *GithubVCSRepository) getPullRequest(tenantId, workflowRunId string, pr *db.GithubPullRequestModel) (*githubsdk.PullRequest, error) {
ghPR, _, err := g.client.PullRequests.Get(
context.Background(),
pr.RepositoryOwner,
pr.RepositoryName,
pr.PullRequestNumber,
)
return ghPR, err
}
func (g *GithubVCSRepository) updatePullRequest(tenantId, workflowRunId string, pr *db.GithubPullRequestModel, opts *vcs.CreatePullRequestOpts) (*db.GithubPullRequestModel, error) {
err := commitFiles(
g.client,
opts.Files,
opts.GitRepoOwner,
opts.GitRepoName,
opts.HeadBranchName,
)
if err != nil {
return nil, fmt.Errorf("Could not commit files: %w", err)
}
return pr, nil
}
func (g *GithubVCSRepository) createPullRequest(tenantId, workflowRunId string, opts *vcs.CreatePullRequestOpts) (*db.GithubPullRequestModel, error) {
var baseBranch string
if opts.BaseBranch == nil {
repo, _, err := g.client.Repositories.Get(
context.TODO(),
opts.GitRepoOwner,
opts.GitRepoName,
)
if err != nil {
return nil, err
}
baseBranch = repo.GetDefaultBranch()
}
err := createNewBranch(g.client, opts.GitRepoOwner, opts.GitRepoName, baseBranch, opts.HeadBranchName)
if err != nil {
return nil, fmt.Errorf("Could not create PR: %w", err)
}
err = commitFiles(
g.client,
opts.Files,
opts.GitRepoOwner,
opts.GitRepoName,
opts.HeadBranchName,
)
if err != nil {
return nil, fmt.Errorf("Could not commit files: %w", err)
}
pr, _, err := g.client.PullRequests.Create(
context.Background(), opts.GitRepoOwner, opts.GitRepoName, &githubsdk.NewPullRequest{
Title: githubsdk.String(opts.Title),
Base: githubsdk.String(baseBranch),
Head: githubsdk.String(opts.HeadBranchName),
},
)
if err != nil {
return nil, err
}
return g.repo.WorkflowRun().CreateWorkflowRunPullRequest(tenantId, workflowRunId, &repository.CreateWorkflowRunPullRequestOpts{
RepositoryOwner: opts.GitRepoOwner,
RepositoryName: opts.GitRepoName,
PullRequestID: int(pr.GetID()),
PullRequestTitle: opts.Title,
PullRequestNumber: pr.GetNumber(),
PullRequestHeadBranch: opts.HeadBranchName,
PullRequestBaseBranch: baseBranch,
PullRequestState: pr.GetState(),
})
}
// CompareCommits compares a base commit with a head commit
func (g *GithubVCSRepository) CompareCommits(base, head string) (vcs.VCSCommitsComparison, error) {
commitsRes, _, err := g.client.Repositories.CompareCommits(
context.Background(),
g.GetRepoOwner(),
g.GetRepoName(),
base,
head,
&githubsdk.ListOptions{},
)
if err != nil {
return nil, err
}
return &GithubCommitsComparison{commitsRes}, nil
}
@@ -0,0 +1,83 @@
package github
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"github.com/bradleyfalzon/ghinstallation/v2"
"golang.org/x/oauth2"
"github.com/hatchet-dev/hatchet/internal/auth/oauth"
githubsdk "github.com/google/go-github/v57/github"
)
const (
GithubAuthURL string = "https://github.com/login/oauth/authorize"
GithubTokenURL string = "https://github.com/login/oauth/access_token" // #nosec G101
)
type GithubAppConf struct {
oauth2.Config
appName string
webhookSecret string
secret []byte
appID int64
}
func NewGithubAppConf(cfg *oauth.Config, appName, appSecretPath, appWebhookSecret, appID string) (*GithubAppConf, error) {
intAppID, err := strconv.ParseInt(appID, 10, 64)
if err != nil {
return nil, err
}
appSecret, err := ioutil.ReadFile(appSecretPath)
if err != nil {
return nil, fmt.Errorf("could not read github app secret: %s", err)
}
return &GithubAppConf{
appName: appName,
webhookSecret: appWebhookSecret,
secret: appSecret,
appID: intAppID,
Config: oauth2.Config{
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: GithubAuthURL,
TokenURL: GithubTokenURL,
},
RedirectURL: cfg.BaseURL + "/api/v1/users/github/callback",
Scopes: cfg.Scopes,
},
}, nil
}
func (g *GithubAppConf) GetGithubClient(installationID int64) (*githubsdk.Client, error) {
itr, err := ghinstallation.New(
http.DefaultTransport,
g.appID,
installationID,
g.secret,
)
if err != nil {
return nil, err
}
return githubsdk.NewClient(&http.Client{Transport: itr}), nil
}
func (g *GithubAppConf) GetWebhookSecret() string {
return g.webhookSecret
}
func (g *GithubAppConf) GetAppName() string {
return g.appName
}
+151
View File
@@ -0,0 +1,151 @@
package github
import (
"context"
"fmt"
"net/http"
githubsdk "github.com/google/go-github/v57/github"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
)
type ghPullRequest struct {
repoOwner, repoName string
pr *githubsdk.PullRequest
}
func ToVCSRepositoryPullRequest(repoOwner, repoName string, pr *githubsdk.PullRequest) vcs.VCSRepositoryPullRequest {
return &ghPullRequest{repoOwner, repoName, pr}
}
func (g *ghPullRequest) GetRepoOwner() string {
return g.repoOwner
}
func (g *ghPullRequest) GetRepoName() string {
return g.repoName
}
func (g *ghPullRequest) GetVCSID() vcs.VCSObjectID {
return vcs.NewVCSObjectInt(g.pr.GetID())
}
func (g *ghPullRequest) GetPRNumber() int64 {
return int64(g.pr.GetNumber())
}
func (g *ghPullRequest) GetBaseSHA() string {
return g.pr.GetBase().GetSHA()
}
func (g *ghPullRequest) GetHeadSHA() string {
return g.pr.GetHead().GetSHA()
}
func (g *ghPullRequest) GetBaseBranch() string {
return g.pr.GetBase().GetRef()
}
func (g *ghPullRequest) GetHeadBranch() string {
return g.pr.GetHead().GetRef()
}
func (g *ghPullRequest) GetTitle() string {
return g.pr.GetTitle()
}
func (g *ghPullRequest) GetState() string {
return g.pr.GetState()
}
func createNewBranch(
client *githubsdk.Client,
gitRepoOwner, gitRepoName, baseBranch, headBranch string,
) error {
_, resp, err := client.Repositories.GetBranch(
context.Background(), gitRepoOwner, gitRepoName, headBranch, 2,
)
headBranchRef := fmt.Sprintf("refs/heads/%s", headBranch)
if err == nil {
return fmt.Errorf("branch %s already exists", headBranch)
} else if resp.StatusCode != http.StatusNotFound {
return err
}
base, _, err := client.Repositories.GetBranch(
context.Background(), gitRepoOwner, gitRepoName, baseBranch, 2,
)
if err != nil {
return err
}
_, _, err = client.Git.CreateRef(
context.Background(), gitRepoOwner, gitRepoName, &githubsdk.Reference{
Ref: githubsdk.String(headBranchRef),
Object: &githubsdk.GitObject{
SHA: base.Commit.SHA,
},
},
)
if err != nil {
return err
}
return nil
}
func commitFiles(
client *githubsdk.Client,
files map[string][]byte,
gitRepoOwner, gitRepoName, branch string,
) error {
for filepath, contents := range files {
sha := ""
// get contents of a file if it exists
fileData, _, _, _ := client.Repositories.GetContents(
context.TODO(),
gitRepoOwner,
gitRepoName,
filepath,
&githubsdk.RepositoryContentGetOptions{
Ref: branch,
},
)
if fileData != nil {
sha = *fileData.SHA
}
opts := &githubsdk.RepositoryContentFileOptions{
Message: githubsdk.String(fmt.Sprintf("Create %s file", filepath)),
Content: contents,
Branch: githubsdk.String(branch),
SHA: &sha,
}
opts.Committer = &githubsdk.CommitAuthor{
Name: githubsdk.String("Hatchet Bot"),
Email: githubsdk.String("contact@hatchet.run"),
}
_, _, err := client.Repositories.UpdateFile(
context.TODO(),
gitRepoOwner,
gitRepoName,
filepath,
opts,
)
if err != nil {
return err
}
}
return nil
}
+191
View File
@@ -0,0 +1,191 @@
package vcs
import (
"fmt"
"io"
"net/url"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
type VCSRepositoryKind string
const (
// enumeration of in-tree repository kinds
VCSRepositoryKindGithub VCSRepositoryKind = "github"
VCSRepositoryKindGitlab VCSRepositoryKind = "gitlab"
)
type VCSCheckRunStatus string
const (
VCSCheckRunStatusQueued VCSCheckRunStatus = "queued"
VCSCheckRunStatusInProgress VCSCheckRunStatus = "in_progress"
VCSCheckRunStatusCompleted VCSCheckRunStatus = "completed"
)
type VCSCheckRunConclusion string
const (
VCSCheckRunConclusionSuccess VCSCheckRunConclusion = "success"
VCSCheckRunConclusionFailure VCSCheckRunConclusion = "failure"
VCSCheckRunConclusionCancelled VCSCheckRunConclusion = "cancelled"
VCSCheckRunConclusionSkipped VCSCheckRunConclusion = "skipped"
VCSCheckRunConclusionTimedOut VCSCheckRunConclusion = "timed_out"
VCSCheckRunConclusionActionRequired VCSCheckRunConclusion = "action_required"
)
type VCSCheckRun struct {
Name string
Status VCSCheckRunStatus
Conclusion VCSCheckRunConclusion
}
type DirectoryItem struct {
Type string `json:"type"`
Name string `json:"name"`
}
type CreatePullRequestOpts struct {
GitRepoOwner string
GitRepoName string
// (optional) the base branch. If not specified, the default branch is used
BaseBranch *string
Title string
HeadBranchName string
Files map[string][]byte
}
// VCSRepository provides an interface to implement for new version control providers
// (github, gitlab, etc)
type VCSRepository interface {
// GetKind returns the kind of VCS provider -- used for downstream integrations
GetKind() VCSRepositoryKind
// GetRepoOwner returns the owner of the repository
GetRepoOwner() string
// GetRepoName returns the name of the repository
GetRepoName() string
// SetupRepository sets up a VCS repository on Hatchet.
SetupRepository(teamID string) error
// GetArchiveLink returns an archive link for a specific repo SHA
GetArchiveLink(ref string) (*url.URL, error)
// GetBranch gets a full branch (name and sha)
GetBranch(name string) (VCSBranch, error)
// CreateOrUpdatePullRequest creates a new pull request or updates an existing one
CreateOrUpdatePullRequest(tenantId, workflowRunId string, opts *CreatePullRequestOpts) (*db.GithubPullRequestModel, error)
// ReadFile returns a file by a SHA reference or path
ReadFile(ref, path string) (io.ReadCloser, error)
// ReadDirectory returns a list of directory items
ReadDirectory(ref, path string) ([]DirectoryItem, error)
// CompareCommits compares a base commit with a head commit
CompareCommits(base, head string) (VCSCommitsComparison, error)
}
// VCSObjectID is a generic method for retrieving IDs from the underlying VCS repository.
// Depending on the provider, object IDs may be int64 or strings.
//
// Object ids are meant to be passed between methods in the same VCS repository, so they should
// only be read by the same VCS repository that wrote them.
type VCSObjectID interface {
GetIDString() *string
GetIDInt64() *int64
}
type vcsObjectString struct {
id string
}
func NewVCSObjectString(id string) VCSObjectID {
return vcsObjectString{id}
}
func (v vcsObjectString) GetIDString() *string {
return &v.id
}
func (v vcsObjectString) GetIDInt64() *int64 {
return nil
}
type vcsObjectInt struct {
id int64
}
func NewVCSObjectInt(id int64) VCSObjectID {
return vcsObjectInt{id}
}
func (v vcsObjectInt) GetIDString() *string {
return nil
}
func (v vcsObjectInt) GetIDInt64() *int64 {
return &v.id
}
type VCSProvider interface {
// GetVCSRepositoryFromModule returns the corresponding VCS repository for the module.
// Callers should likely use the package method GetVCSProviderFromModule.
GetVCSRepositoryFromWorkflow(workflow *db.WorkflowModel) (VCSRepository, error)
}
// GetVCSRepositoryFromWorkflow returns the corresponding VCS repository for the workflow
func GetVCSRepositoryFromWorkflow(allProviders map[VCSRepositoryKind]VCSProvider, workflow *db.WorkflowModel) (VCSRepository, error) {
var repoKind VCSRepositoryKind
if deploymentConf, ok := workflow.DeploymentConfig(); ok {
if installationId, ok := deploymentConf.GithubAppInstallationID(); ok && installationId != "" {
repoKind = VCSRepositoryKindGithub
}
}
provider, exists := allProviders[repoKind]
if !exists {
return nil, fmt.Errorf("VCS provider kind '%s' is not registered on this Hatchet instance", repoKind)
}
return provider.GetVCSRepositoryFromWorkflow(workflow)
}
// VCSRepositoryPullRequest abstracts the underlying pull or merge request methods to only
// extract relevant information.
type VCSRepositoryPullRequest interface {
GetRepoOwner() string
GetVCSID() VCSObjectID
GetPRNumber() int64
GetRepoName() string
GetBaseSHA() string
GetHeadSHA() string
GetBaseBranch() string
GetHeadBranch() string
GetTitle() string
GetState() string
}
type VCSBranch interface {
GetName() string
GetLatestRef() string
}
type VCSCommitsComparison interface {
GetFiles() []CommitFile
}
type CommitFile struct {
Name string
}
func (f CommitFile) GetFilename() string {
return f.Name
}
+106
View File
@@ -0,0 +1,106 @@
package zip
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
type ZIPDownloader struct {
SourceURL string
ZipFolderDest string
ZipName string
AssetFolderDest string
RemoveAfterDownload bool
}
func (z *ZIPDownloader) DownloadToFile() error {
// Get the data
resp, err := http.Get(z.SourceURL)
if err != nil {
return err
}
defer resp.Body.Close()
// Create the file
out, err := os.Create(filepath.Join(z.ZipFolderDest, z.ZipName))
if err != nil {
return err
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
return err
}
func (z *ZIPDownloader) UnzipToDir() error {
r, err := zip.OpenReader(filepath.Join(z.ZipFolderDest, z.ZipName))
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
// Store filename/path for returning and using later on
fpath := filepath.Join(z.AssetFolderDest, f.Name) // nolint: gosec
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
if !strings.HasPrefix(fpath, filepath.Clean(z.AssetFolderDest)+string(os.PathSeparator)) {
return fmt.Errorf("%s: illegal file path", fpath)
}
if f.FileInfo().IsDir() {
// Make Folder
os.MkdirAll(fpath, os.ModePerm) // nolint: errcheck
continue
}
// delete file if exists
os.Remove(fpath)
// Make File
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
return err
}
_, err = io.Copy(outFile, rc) // nolint: gosec
// Close the file without defer to close before next iteration of loop
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
if z.RemoveAfterDownload {
os.Remove(filepath.Join(z.ZipFolderDest, z.ZipName))
}
return nil
}
+129
View File
@@ -0,0 +1,129 @@
package repository
import (
"fmt"
"time"
"github.com/steebchen/prisma-client-go/runtime/types"
"github.com/hatchet-dev/hatchet/internal/encryption"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
)
type CreateInstallationOpts struct {
// (required) the installation id
InstallationID int `validate:"required"`
// (required) the account ID
AccountID int `validate:"required"`
// (required) the account name
AccountName string `validate:"required"`
// (optional) the account avatar URL
AccountAvatarURL *string
// (optional) the installation settings URL
InstallationSettingsURL *string
// (optional) the installation configuration
Config *types.JSON
}
type CreateGithubWebhookOpts struct {
// (required) the repo owner
RepoOwner string `validate:"required"`
// (required) the repo name
RepoName string `validate:"required"`
// (required) the signing secret
SigningSecret []byte `validate:"required,min=1"`
}
func NewGithubWebhookCreateOpts(
enc encryption.EncryptionService,
repoOwner string,
repoName string,
) (opts *CreateGithubWebhookOpts, signingSecret string, err error) {
signingSecret, err = encryption.GenerateRandomBytes(16)
if err != nil {
return nil, "", fmt.Errorf("failed to generate signing secret: %s", err.Error())
}
// use the encryption service to encrypt the access and refresh token
signingSecretEncrypted, err := enc.Encrypt([]byte(signingSecret), "github_signing_secret")
if err != nil {
return nil, "", fmt.Errorf("failed to encrypt access token: %s", err.Error())
}
opts = &CreateGithubWebhookOpts{
RepoOwner: repoOwner,
RepoName: repoName,
SigningSecret: signingSecretEncrypted,
}
return opts, signingSecret, nil
}
type CreateGithubAppOAuthOpts struct {
// (required) the oauth provider's user id
GithubUserID int `validate:"required"`
// (required) the oauth provider's access token
AccessToken []byte `validate:"required,min=1"`
// (optional) the oauth provider's refresh token
RefreshToken *[]byte
// (optional) the oauth provider's expiry time
ExpiresAt *time.Time
// (optional) the oauth provider's configuration
Config *types.JSON
}
type UpdateInstallationOpts struct {
}
type UpdatePullRequestOpts struct {
Title *string
State *string
HeadBranch *string
BaseBranch *string
}
type GithubRepository interface {
CreateInstallation(githubUserId int, opts *CreateInstallationOpts) (*db.GithubAppInstallationModel, error)
AddGithubUserIdToInstallation(installationID, accountID, githubUserId int) (*db.GithubAppInstallationModel, error)
ReadGithubAppInstallationByID(installationId string) (*db.GithubAppInstallationModel, error)
ReadGithubWebhookById(id string) (*db.GithubWebhookModel, error)
ReadGithubWebhook(tenantId, repoOwner, repoName string) (*db.GithubWebhookModel, error)
ReadGithubAppOAuthByGithubUserID(githubUserID int) (*db.GithubAppOAuthModel, error)
CanUserAccessInstallation(installationId, userId string) (bool, error)
ReadGithubAppInstallationByInstallationAndAccountID(installationID, accountID int) (*db.GithubAppInstallationModel, error)
CreateGithubWebhook(tenantId string, opts *CreateGithubWebhookOpts) (*db.GithubWebhookModel, error)
UpsertGithubAppOAuth(userId string, opts *CreateGithubAppOAuthOpts) (*db.GithubAppOAuthModel, error)
DeleteInstallation(installationId, accountId int) (*db.GithubAppInstallationModel, error)
ListGithubAppInstallationsByUserID(userId string) ([]db.GithubAppInstallationModel, error)
UpdatePullRequest(tenantId, prId string, opts *UpdatePullRequestOpts) (*db.GithubPullRequestModel, error)
GetPullRequest(tenantId, repoOwner, repoName string, prNumber int) (*db.GithubPullRequestModel, error)
}
+164
View File
@@ -232,6 +232,47 @@ func (ns NullTenantMemberRole) Value() (driver.Value, error) {
return string(ns.TenantMemberRole), nil
}
type VcsProvider string
const (
VcsProviderGITHUB VcsProvider = "GITHUB"
)
func (e *VcsProvider) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = VcsProvider(s)
case string:
*e = VcsProvider(s)
default:
return fmt.Errorf("unsupported scan type for VcsProvider: %T", src)
}
return nil
}
type NullVcsProvider struct {
VcsProvider VcsProvider `json:"VcsProvider"`
Valid bool `json:"valid"` // Valid is true if VcsProvider is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullVcsProvider) Scan(value interface{}) error {
if value == nil {
ns.VcsProvider, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.VcsProvider.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullVcsProvider) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.VcsProvider), nil
}
type WorkerStatus string
const (
@@ -383,6 +424,86 @@ type GetGroupKeyRun struct {
WorkflowRunId pgtype.UUID `json:"workflowRunId"`
}
type GithubAppInstallation struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
GithubAppOAuthId pgtype.UUID `json:"githubAppOAuthId"`
InstallationId int32 `json:"installationId"`
AccountName string `json:"accountName"`
AccountId int32 `json:"accountId"`
AccountAvatarURL pgtype.Text `json:"accountAvatarURL"`
InstallationSettingsURL pgtype.Text `json:"installationSettingsURL"`
Config []byte `json:"config"`
TenantId pgtype.UUID `json:"tenantId"`
TenantVcsProviderId pgtype.UUID `json:"tenantVcsProviderId"`
}
type GithubAppInstallationToGithubWebhook struct {
A pgtype.UUID `json:"A"`
B pgtype.UUID `json:"B"`
}
type GithubAppOAuth struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
GithubUserID int32 `json:"githubUserID"`
AccessToken []byte `json:"accessToken"`
RefreshToken []byte `json:"refreshToken"`
ExpiresAt pgtype.Timestamp `json:"expiresAt"`
}
type GithubAppOAuthToUser struct {
A pgtype.UUID `json:"A"`
B pgtype.UUID `json:"B"`
}
type GithubPullRequest struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
TenantId pgtype.UUID `json:"tenantId"`
RepositoryOwner string `json:"repositoryOwner"`
RepositoryName string `json:"repositoryName"`
PullRequestID int32 `json:"pullRequestID"`
PullRequestTitle string `json:"pullRequestTitle"`
PullRequestNumber int32 `json:"pullRequestNumber"`
PullRequestHeadBranch string `json:"pullRequestHeadBranch"`
PullRequestBaseBranch string `json:"pullRequestBaseBranch"`
PullRequestState string `json:"pullRequestState"`
}
type GithubPullRequestComment struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
TenantId pgtype.UUID `json:"tenantId"`
PullRequestID pgtype.UUID `json:"pullRequestID"`
ModuleID string `json:"moduleID"`
CommentID int32 `json:"commentID"`
}
type GithubPullRequestToWorkflowRun struct {
A pgtype.UUID `json:"A"`
B pgtype.UUID `json:"B"`
}
type GithubWebhook struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
TenantId pgtype.UUID `json:"tenantId"`
RepositoryOwner string `json:"repositoryOwner"`
RepositoryName string `json:"repositoryName"`
SigningSecret []byte `json:"signingSecret"`
}
type Job struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
@@ -481,6 +602,8 @@ type StepRun struct {
CancelledReason pgtype.Text `json:"cancelledReason"`
CancelledError pgtype.Text `json:"cancelledError"`
InputSchema []byte `json:"inputSchema"`
CallerFiles []byte `json:"callerFiles"`
GitRepoBranch pgtype.Text `json:"gitRepoBranch"`
}
type StepRunOrder struct {
@@ -488,6 +611,24 @@ type StepRunOrder struct {
B pgtype.UUID `json:"B"`
}
type StepRunResultArchive struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
StepRunId pgtype.UUID `json:"stepRunId"`
Order int16 `json:"order"`
Input []byte `json:"input"`
Output []byte `json:"output"`
Error pgtype.Text `json:"error"`
StartedAt pgtype.Timestamp `json:"startedAt"`
FinishedAt pgtype.Timestamp `json:"finishedAt"`
TimeoutAt pgtype.Timestamp `json:"timeoutAt"`
CancelledAt pgtype.Timestamp `json:"cancelledAt"`
CancelledReason pgtype.Text `json:"cancelledReason"`
CancelledError pgtype.Text `json:"cancelledError"`
}
type Tenant struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
@@ -518,6 +659,16 @@ type TenantMember struct {
Role TenantMemberRole `json:"role"`
}
type TenantVcsProvider struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
TenantId pgtype.UUID `json:"tenantId"`
VcsProvider VcsProvider `json:"vcsProvider"`
Config []byte `json:"config"`
}
type Ticker struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
@@ -594,6 +745,18 @@ type WorkflowConcurrency struct {
LimitStrategy ConcurrencyLimitStrategy `json:"limitStrategy"`
}
type WorkflowDeploymentConfig struct {
ID pgtype.UUID `json:"id"`
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
DeletedAt pgtype.Timestamp `json:"deletedAt"`
WorkflowId pgtype.UUID `json:"workflowId"`
GitRepoName string `json:"gitRepoName"`
GitRepoOwner string `json:"gitRepoOwner"`
GitRepoBranch string `json:"gitRepoBranch"`
GithubAppInstallationId pgtype.UUID `json:"githubAppInstallationId"`
}
type WorkflowRun struct {
CreatedAt pgtype.Timestamp `json:"createdAt"`
UpdatedAt pgtype.Timestamp `json:"updatedAt"`
@@ -607,6 +770,7 @@ type WorkflowRun struct {
ConcurrencyGroupId pgtype.Text `json:"concurrencyGroupId"`
DisplayName pgtype.Text `json:"displayName"`
ID pgtype.UUID `json:"id"`
GitRepoBranch pgtype.Text `json:"gitRepoBranch"`
}
type WorkflowRunTriggeredBy struct {
+277 -12
View File
@@ -13,11 +13,14 @@ CREATE TYPE "StepRunStatus" AS ENUM ('PENDING', 'PENDING_ASSIGNMENT', 'ASSIGNED'
-- CreateEnum
CREATE TYPE "TenantMemberRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
-- CreateEnum
CREATE TYPE "VcsProvider" AS ENUM ('GITHUB');
-- CreateEnum
CREATE TYPE "WorkerStatus" AS ENUM ('ACTIVE', 'INACTIVE');
-- CreateEnum
CREATE TYPE "WorkflowRunStatus" AS ENUM ('PENDING', 'QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED');
CREATE TYPE "WorkflowRunStatus" AS ENUM ('PENDING', 'RUNNING', 'SUCCEEDED', 'FAILED', 'QUEUED');
-- CreateTable
CREATE TABLE "APIToken" (
@@ -34,10 +37,10 @@ CREATE TABLE "APIToken" (
-- CreateTable
CREATE TABLE "Action" (
"id" UUID NOT NULL,
"actionId" TEXT NOT NULL,
"description" TEXT,
"tenantId" UUID NOT NULL,
"actionId" TEXT NOT NULL,
"id" UUID NOT NULL,
CONSTRAINT "Action_pkey" PRIMARY KEY ("id")
);
@@ -75,7 +78,6 @@ CREATE TABLE "GetGroupKeyRun" (
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"workflowRunId" UUID NOT NULL,
"workerId" UUID,
"tickerId" UUID,
"status" "StepRunStatus" NOT NULL DEFAULT 'PENDING',
@@ -89,10 +91,91 @@ CREATE TABLE "GetGroupKeyRun" (
"cancelledAt" TIMESTAMP(3),
"cancelledReason" TEXT,
"cancelledError" TEXT,
"workflowRunId" UUID NOT NULL,
CONSTRAINT "GetGroupKeyRun_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubAppInstallation" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"githubAppOAuthId" UUID NOT NULL,
"installationId" INTEGER NOT NULL,
"accountName" TEXT NOT NULL,
"accountId" INTEGER NOT NULL,
"accountAvatarURL" TEXT,
"installationSettingsURL" TEXT,
"config" JSONB,
"tenantId" UUID,
"tenantVcsProviderId" UUID,
CONSTRAINT "GithubAppInstallation_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubAppOAuth" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"githubUserID" INTEGER NOT NULL,
"accessToken" BYTEA NOT NULL,
"refreshToken" BYTEA,
"expiresAt" TIMESTAMP(3),
CONSTRAINT "GithubAppOAuth_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubPullRequest" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"repositoryOwner" TEXT NOT NULL,
"repositoryName" TEXT NOT NULL,
"pullRequestID" INTEGER NOT NULL,
"pullRequestTitle" TEXT NOT NULL,
"pullRequestNumber" INTEGER NOT NULL,
"pullRequestHeadBranch" TEXT NOT NULL,
"pullRequestBaseBranch" TEXT NOT NULL,
"pullRequestState" TEXT NOT NULL,
CONSTRAINT "GithubPullRequest_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubPullRequestComment" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"pullRequestID" UUID NOT NULL,
"moduleID" TEXT NOT NULL,
"commentID" INTEGER NOT NULL,
CONSTRAINT "GithubPullRequestComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubWebhook" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"repositoryOwner" TEXT NOT NULL,
"repositoryName" TEXT NOT NULL,
"signingSecret" BYTEA NOT NULL,
CONSTRAINT "GithubWebhook_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Job" (
"id" UUID NOT NULL,
@@ -115,7 +198,6 @@ CREATE TABLE "JobRun" (
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"workflowRunId" UUID NOT NULL,
"jobId" UUID NOT NULL,
"tickerId" UUID,
"status" "JobRunStatus" NOT NULL DEFAULT 'PENDING',
@@ -126,6 +208,7 @@ CREATE TABLE "JobRun" (
"cancelledAt" TIMESTAMP(3),
"cancelledReason" TEXT,
"cancelledError" TEXT,
"workflowRunId" UUID NOT NULL,
CONSTRAINT "JobRun_pkey" PRIMARY KEY ("id")
);
@@ -196,10 +279,34 @@ CREATE TABLE "StepRun" (
"cancelledAt" TIMESTAMP(3),
"cancelledReason" TEXT,
"cancelledError" TEXT,
"inputSchema" JSONB,
"callerFiles" JSONB,
"gitRepoBranch" TEXT,
CONSTRAINT "StepRun_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StepRunResultArchive" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"stepRunId" UUID NOT NULL,
"order" SMALLSERIAL NOT NULL,
"input" JSONB,
"output" JSONB,
"error" TEXT,
"startedAt" TIMESTAMP(3),
"finishedAt" TIMESTAMP(3),
"timeoutAt" TIMESTAMP(3),
"cancelledAt" TIMESTAMP(3),
"cancelledReason" TEXT,
"cancelledError" TEXT,
CONSTRAINT "StepRunResultArchive_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Tenant" (
"id" UUID NOT NULL,
@@ -239,6 +346,19 @@ CREATE TABLE "TenantMember" (
CONSTRAINT "TenantMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TenantVcsProvider" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"vcsProvider" "VcsProvider" NOT NULL,
"config" JSONB,
CONSTRAINT "TenantVcsProvider_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Ticker" (
"id" UUID NOT NULL,
@@ -271,9 +391,9 @@ CREATE TABLE "UserOAuth" (
"userId" UUID NOT NULL,
"provider" TEXT NOT NULL,
"providerUserId" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3),
"accessToken" BYTEA NOT NULL,
"refreshToken" BYTEA,
"expiresAt" TIMESTAMP(3),
CONSTRAINT "UserOAuth_pkey" PRIMARY KEY ("id")
);
@@ -338,19 +458,35 @@ CREATE TABLE "WorkflowConcurrency" (
);
-- CreateTable
CREATE TABLE "WorkflowRun" (
CREATE TABLE "WorkflowDeploymentConfig" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"displayName" TEXT,
"workflowId" UUID NOT NULL,
"gitRepoName" TEXT NOT NULL,
"gitRepoOwner" TEXT NOT NULL,
"gitRepoBranch" TEXT NOT NULL,
"githubAppInstallationId" UUID,
CONSTRAINT "WorkflowDeploymentConfig_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WorkflowRun" (
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"workflowVersionId" UUID NOT NULL,
"concurrencyGroupId" TEXT,
"status" "WorkflowRunStatus" NOT NULL DEFAULT 'PENDING',
"error" TEXT,
"startedAt" TIMESTAMP(3),
"finishedAt" TIMESTAMP(3),
"concurrencyGroupId" TEXT,
"displayName" TEXT,
"id" UUID NOT NULL,
"gitRepoBranch" TEXT,
CONSTRAINT "WorkflowRun_pkey" PRIMARY KEY ("id")
);
@@ -362,12 +498,12 @@ CREATE TABLE "WorkflowRunTriggeredBy" (
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"parentId" UUID NOT NULL,
"input" JSONB,
"eventId" UUID,
"cronParentId" UUID,
"cronSchedule" TEXT,
"scheduledId" UUID,
"input" JSONB,
"parentId" UUID NOT NULL,
CONSTRAINT "WorkflowRunTriggeredBy_pkey" PRIMARY KEY ("id")
);
@@ -427,16 +563,34 @@ CREATE TABLE "WorkflowVersion" (
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"checksum" TEXT NOT NULL,
"version" TEXT,
"order" SMALLSERIAL NOT NULL,
"workflowId" UUID NOT NULL,
"checksum" TEXT NOT NULL,
CONSTRAINT "WorkflowVersion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ActionToWorker" (
"B" UUID NOT NULL,
"A" UUID NOT NULL
);
-- CreateTable
CREATE TABLE "_GithubAppInstallationToGithubWebhook" (
"A" UUID NOT NULL,
"B" UUID NOT NULL
);
-- CreateTable
CREATE TABLE "_GithubAppOAuthToUser" (
"A" UUID NOT NULL,
"B" UUID NOT NULL
);
-- CreateTable
CREATE TABLE "_GithubPullRequestToWorkflowRun" (
"A" UUID NOT NULL,
"B" UUID NOT NULL
);
@@ -486,6 +640,33 @@ CREATE UNIQUE INDEX "GetGroupKeyRun_id_key" ON "GetGroupKeyRun"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GetGroupKeyRun_workflowRunId_key" ON "GetGroupKeyRun"("workflowRunId" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppInstallation_id_key" ON "GithubAppInstallation"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppInstallation_installationId_accountId_key" ON "GithubAppInstallation"("installationId" ASC, "accountId" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppOAuth_githubUserID_key" ON "GithubAppOAuth"("githubUserID" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppOAuth_id_key" ON "GithubAppOAuth"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubPullRequest_id_key" ON "GithubPullRequest"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubPullRequest_tenantId_repositoryOwner_repositoryName_p_key" ON "GithubPullRequest"("tenantId" ASC, "repositoryOwner" ASC, "repositoryName" ASC, "pullRequestNumber" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubPullRequestComment_id_key" ON "GithubPullRequestComment"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubWebhook_id_key" ON "GithubWebhook"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "GithubWebhook_tenantId_repositoryOwner_repositoryName_key" ON "GithubWebhook"("tenantId" ASC, "repositoryOwner" ASC, "repositoryName" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "Job_id_key" ON "Job"("id" ASC);
@@ -519,6 +700,9 @@ CREATE UNIQUE INDEX "Step_jobId_readableId_key" ON "Step"("jobId" ASC, "readable
-- CreateIndex
CREATE UNIQUE INDEX "StepRun_id_key" ON "StepRun"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "StepRunResultArchive_id_key" ON "StepRunResultArchive"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "Tenant_id_key" ON "Tenant"("id" ASC);
@@ -534,6 +718,12 @@ CREATE UNIQUE INDEX "TenantMember_id_key" ON "TenantMember"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "TenantMember_tenantId_userId_key" ON "TenantMember"("tenantId" ASC, "userId" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "TenantVcsProvider_id_key" ON "TenantVcsProvider"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "TenantVcsProvider_tenantId_vcsProvider_key" ON "TenantVcsProvider"("tenantId" ASC, "vcsProvider" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "Ticker_id_key" ON "Ticker"("id" ASC);
@@ -573,6 +763,12 @@ CREATE UNIQUE INDEX "WorkflowConcurrency_id_key" ON "WorkflowConcurrency"("id" A
-- CreateIndex
CREATE UNIQUE INDEX "WorkflowConcurrency_workflowVersionId_key" ON "WorkflowConcurrency"("workflowVersionId" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "WorkflowDeploymentConfig_id_key" ON "WorkflowDeploymentConfig"("id" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "WorkflowDeploymentConfig_workflowId_key" ON "WorkflowDeploymentConfig"("workflowId" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "WorkflowRun_id_key" ON "WorkflowRun"("id" ASC);
@@ -615,6 +811,24 @@ CREATE UNIQUE INDEX "_ActionToWorker_AB_unique" ON "_ActionToWorker"("A" ASC, "B
-- CreateIndex
CREATE INDEX "_ActionToWorker_B_index" ON "_ActionToWorker"("B" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "_GithubAppInstallationToGithubWebhook_AB_unique" ON "_GithubAppInstallationToGithubWebhook"("A" ASC, "B" ASC);
-- CreateIndex
CREATE INDEX "_GithubAppInstallationToGithubWebhook_B_index" ON "_GithubAppInstallationToGithubWebhook"("B" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "_GithubAppOAuthToUser_AB_unique" ON "_GithubAppOAuthToUser"("A" ASC, "B" ASC);
-- CreateIndex
CREATE INDEX "_GithubAppOAuthToUser_B_index" ON "_GithubAppOAuthToUser"("B" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "_GithubPullRequestToWorkflowRun_AB_unique" ON "_GithubPullRequestToWorkflowRun"("A" ASC, "B" ASC);
-- CreateIndex
CREATE INDEX "_GithubPullRequestToWorkflowRun_B_index" ON "_GithubPullRequestToWorkflowRun"("B" ASC);
-- CreateIndex
CREATE UNIQUE INDEX "_ServiceToWorker_AB_unique" ON "_ServiceToWorker"("A" ASC, "B" ASC);
@@ -663,6 +877,27 @@ ALTER TABLE "GetGroupKeyRun" ADD CONSTRAINT "GetGroupKeyRun_workerId_fkey" FOREI
-- AddForeignKey
ALTER TABLE "GetGroupKeyRun" ADD CONSTRAINT "GetGroupKeyRun_workflowRunId_fkey" FOREIGN KEY ("workflowRunId") REFERENCES "WorkflowRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_githubAppOAuthId_fkey" FOREIGN KEY ("githubAppOAuthId") REFERENCES "GithubAppOAuth"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_tenantVcsProviderId_fkey" FOREIGN KEY ("tenantVcsProviderId") REFERENCES "TenantVcsProvider"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubPullRequest" ADD CONSTRAINT "GithubPullRequest_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubPullRequestComment" ADD CONSTRAINT "GithubPullRequestComment_pullRequestID_fkey" FOREIGN KEY ("pullRequestID") REFERENCES "GithubPullRequest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubPullRequestComment" ADD CONSTRAINT "GithubPullRequestComment_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubWebhook" ADD CONSTRAINT "GithubWebhook_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Job" ADD CONSTRAINT "Job_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -714,6 +949,9 @@ ALTER TABLE "StepRun" ADD CONSTRAINT "StepRun_tickerId_fkey" FOREIGN KEY ("ticke
-- AddForeignKey
ALTER TABLE "StepRun" ADD CONSTRAINT "StepRun_workerId_fkey" FOREIGN KEY ("workerId") REFERENCES "Worker"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "StepRunResultArchive" ADD CONSTRAINT "StepRunResultArchive_stepRunId_fkey" FOREIGN KEY ("stepRunId") REFERENCES "StepRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TenantInviteLink" ADD CONSTRAINT "TenantInviteLink_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -723,6 +961,9 @@ ALTER TABLE "TenantMember" ADD CONSTRAINT "TenantMember_tenantId_fkey" FOREIGN K
-- AddForeignKey
ALTER TABLE "TenantMember" ADD CONSTRAINT "TenantMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TenantVcsProvider" ADD CONSTRAINT "TenantVcsProvider_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserOAuth" ADD CONSTRAINT "UserOAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -747,6 +988,12 @@ ALTER TABLE "WorkflowConcurrency" ADD CONSTRAINT "WorkflowConcurrency_getConcurr
-- AddForeignKey
ALTER TABLE "WorkflowConcurrency" ADD CONSTRAINT "WorkflowConcurrency_workflowVersionId_fkey" FOREIGN KEY ("workflowVersionId") REFERENCES "WorkflowVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WorkflowDeploymentConfig" ADD CONSTRAINT "WorkflowDeploymentConfig_githubAppInstallationId_fkey" FOREIGN KEY ("githubAppInstallationId") REFERENCES "GithubAppInstallation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WorkflowDeploymentConfig" ADD CONSTRAINT "WorkflowDeploymentConfig_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WorkflowRun" ADD CONSTRAINT "WorkflowRun_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -801,6 +1048,24 @@ 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 "_GithubAppInstallationToGithubWebhook" ADD CONSTRAINT "_GithubAppInstallationToGithubWebhook_A_fkey" FOREIGN KEY ("A") REFERENCES "GithubAppInstallation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppInstallationToGithubWebhook" ADD CONSTRAINT "_GithubAppInstallationToGithubWebhook_B_fkey" FOREIGN KEY ("B") REFERENCES "GithubWebhook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppOAuthToUser" ADD CONSTRAINT "_GithubAppOAuthToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "GithubAppOAuth"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppOAuthToUser" ADD CONSTRAINT "_GithubAppOAuthToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubPullRequestToWorkflowRun" ADD CONSTRAINT "_GithubPullRequestToWorkflowRun_A_fkey" FOREIGN KEY ("A") REFERENCES "GithubPullRequest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubPullRequestToWorkflowRun" ADD CONSTRAINT "_GithubPullRequestToWorkflowRun_B_fkey" FOREIGN KEY ("B") REFERENCES "WorkflowRun"("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;
@@ -91,10 +91,11 @@ RETURNING sr.*;
-- name: UpdateStepRunOverridesData :one
UPDATE
"StepRun" as sr
"StepRun" AS sr
SET
"updatedAt" = CURRENT_TIMESTAMP,
"input" = jsonb_set("input", @fieldPath::text[], @jsonData::jsonb, true)
"input" = jsonb_set("input", @fieldPath::text[], @jsonData::jsonb, true),
"callerFiles" = jsonb_set("callerFiles", @overridesKey::text[], to_jsonb(@callerFile::text), true)
WHERE
sr."tenantId" = @tenantId::uuid AND
sr."id" = @stepRunId::uuid
@@ -109,4 +110,58 @@ SET
WHERE
sr."tenantId" = @tenantId::uuid AND
sr."id" = @stepRunId::uuid
RETURNING "inputSchema";
RETURNING "inputSchema";
-- name: ArchiveStepRunResultFromStepRun :one
WITH step_run_data AS (
SELECT
"id" AS step_run_id,
"createdAt",
"updatedAt",
"deletedAt",
"order",
"input",
"output",
"error",
"startedAt",
"finishedAt",
"timeoutAt",
"cancelledAt",
"cancelledReason",
"cancelledError"
FROM "StepRun"
WHERE "id" = @stepRunId::uuid AND "tenantId" = @tenantId::uuid
)
INSERT INTO "StepRunResultArchive" (
"id",
"createdAt",
"updatedAt",
"deletedAt",
"stepRunId",
"input",
"output",
"error",
"startedAt",
"finishedAt",
"timeoutAt",
"cancelledAt",
"cancelledReason",
"cancelledError"
)
SELECT
COALESCE(sqlc.arg('id')::uuid, gen_random_uuid()),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
step_run_data."deletedAt",
step_run_data.step_run_id,
step_run_data."input",
step_run_data."output",
step_run_data."error",
step_run_data."startedAt",
step_run_data."finishedAt",
step_run_data."timeoutAt",
step_run_data."cancelledAt",
step_run_data."cancelledReason",
step_run_data."cancelledError"
FROM step_run_data
RETURNING *;
@@ -11,9 +11,93 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const archiveStepRunResultFromStepRun = `-- name: ArchiveStepRunResultFromStepRun :one
WITH step_run_data AS (
SELECT
"id" AS step_run_id,
"createdAt",
"updatedAt",
"deletedAt",
"order",
"input",
"output",
"error",
"startedAt",
"finishedAt",
"timeoutAt",
"cancelledAt",
"cancelledReason",
"cancelledError"
FROM "StepRun"
WHERE "id" = $2::uuid AND "tenantId" = $3::uuid
)
INSERT INTO "StepRunResultArchive" (
"id",
"createdAt",
"updatedAt",
"deletedAt",
"stepRunId",
"input",
"output",
"error",
"startedAt",
"finishedAt",
"timeoutAt",
"cancelledAt",
"cancelledReason",
"cancelledError"
)
SELECT
COALESCE($1::uuid, gen_random_uuid()),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
step_run_data."deletedAt",
step_run_data.step_run_id,
step_run_data."input",
step_run_data."output",
step_run_data."error",
step_run_data."startedAt",
step_run_data."finishedAt",
step_run_data."timeoutAt",
step_run_data."cancelledAt",
step_run_data."cancelledReason",
step_run_data."cancelledError"
FROM step_run_data
RETURNING id, "createdAt", "updatedAt", "deletedAt", "stepRunId", "order", input, output, error, "startedAt", "finishedAt", "timeoutAt", "cancelledAt", "cancelledReason", "cancelledError"
`
type ArchiveStepRunResultFromStepRunParams struct {
ID pgtype.UUID `json:"id"`
Steprunid pgtype.UUID `json:"steprunid"`
Tenantid pgtype.UUID `json:"tenantid"`
}
func (q *Queries) ArchiveStepRunResultFromStepRun(ctx context.Context, db DBTX, arg ArchiveStepRunResultFromStepRunParams) (*StepRunResultArchive, error) {
row := db.QueryRow(ctx, archiveStepRunResultFromStepRun, arg.ID, arg.Steprunid, arg.Tenantid)
var i StepRunResultArchive
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.StepRunId,
&i.Order,
&i.Input,
&i.Output,
&i.Error,
&i.StartedAt,
&i.FinishedAt,
&i.TimeoutAt,
&i.CancelledAt,
&i.CancelledReason,
&i.CancelledError,
)
return &i, err
}
const getStepRun = `-- name: GetStepRun :one
SELECT
"StepRun".id, "StepRun"."createdAt", "StepRun"."updatedAt", "StepRun"."deletedAt", "StepRun"."tenantId", "StepRun"."jobRunId", "StepRun"."stepId", "StepRun"."order", "StepRun"."workerId", "StepRun"."tickerId", "StepRun".status, "StepRun".input, "StepRun".output, "StepRun"."requeueAfter", "StepRun"."scheduleTimeoutAt", "StepRun".error, "StepRun"."startedAt", "StepRun"."finishedAt", "StepRun"."timeoutAt", "StepRun"."cancelledAt", "StepRun"."cancelledReason", "StepRun"."cancelledError", "StepRun"."inputSchema"
"StepRun".id, "StepRun"."createdAt", "StepRun"."updatedAt", "StepRun"."deletedAt", "StepRun"."tenantId", "StepRun"."jobRunId", "StepRun"."stepId", "StepRun"."order", "StepRun"."workerId", "StepRun"."tickerId", "StepRun".status, "StepRun".input, "StepRun".output, "StepRun"."requeueAfter", "StepRun"."scheduleTimeoutAt", "StepRun".error, "StepRun"."startedAt", "StepRun"."finishedAt", "StepRun"."timeoutAt", "StepRun"."cancelledAt", "StepRun"."cancelledReason", "StepRun"."cancelledError", "StepRun"."inputSchema", "StepRun"."callerFiles", "StepRun"."gitRepoBranch"
FROM
"StepRun"
WHERE
@@ -53,13 +137,15 @@ func (q *Queries) GetStepRun(ctx context.Context, db DBTX, arg GetStepRunParams)
&i.CancelledReason,
&i.CancelledError,
&i.InputSchema,
&i.CallerFiles,
&i.GitRepoBranch,
)
return &i, err
}
const resolveLaterStepRuns = `-- name: ResolveLaterStepRuns :many
WITH currStepRun AS (
SELECT id, "createdAt", "updatedAt", "deletedAt", "tenantId", "jobRunId", "stepId", "order", "workerId", "tickerId", status, input, output, "requeueAfter", "scheduleTimeoutAt", error, "startedAt", "finishedAt", "timeoutAt", "cancelledAt", "cancelledReason", "cancelledError", "inputSchema"
SELECT id, "createdAt", "updatedAt", "deletedAt", "tenantId", "jobRunId", "stepId", "order", "workerId", "tickerId", status, input, output, "requeueAfter", "scheduleTimeoutAt", error, "startedAt", "finishedAt", "timeoutAt", "cancelledAt", "cancelledReason", "cancelledError", "inputSchema", "callerFiles", "gitRepoBranch"
FROM "StepRun"
WHERE
"id" = $1::uuid AND
@@ -92,7 +178,7 @@ WHERE
WHERE "id" = $1::uuid
) AND
sr."tenantId" = $2::uuid
RETURNING sr.id, sr."createdAt", sr."updatedAt", sr."deletedAt", sr."tenantId", sr."jobRunId", sr."stepId", sr."order", sr."workerId", sr."tickerId", sr.status, sr.input, sr.output, sr."requeueAfter", sr."scheduleTimeoutAt", sr.error, sr."startedAt", sr."finishedAt", sr."timeoutAt", sr."cancelledAt", sr."cancelledReason", sr."cancelledError", sr."inputSchema"
RETURNING sr.id, sr."createdAt", sr."updatedAt", sr."deletedAt", sr."tenantId", sr."jobRunId", sr."stepId", sr."order", sr."workerId", sr."tickerId", sr.status, sr.input, sr.output, sr."requeueAfter", sr."scheduleTimeoutAt", sr.error, sr."startedAt", sr."finishedAt", sr."timeoutAt", sr."cancelledAt", sr."cancelledReason", sr."cancelledError", sr."inputSchema", sr."callerFiles", sr."gitRepoBranch"
`
type ResolveLaterStepRunsParams struct {
@@ -133,6 +219,8 @@ func (q *Queries) ResolveLaterStepRuns(ctx context.Context, db DBTX, arg Resolve
&i.CancelledReason,
&i.CancelledError,
&i.InputSchema,
&i.CallerFiles,
&i.GitRepoBranch,
); err != nil {
return nil, err
}
@@ -187,7 +275,7 @@ SET
WHERE
"id" = $12::uuid AND
"tenantId" = $13::uuid
RETURNING "StepRun".id, "StepRun"."createdAt", "StepRun"."updatedAt", "StepRun"."deletedAt", "StepRun"."tenantId", "StepRun"."jobRunId", "StepRun"."stepId", "StepRun"."order", "StepRun"."workerId", "StepRun"."tickerId", "StepRun".status, "StepRun".input, "StepRun".output, "StepRun"."requeueAfter", "StepRun"."scheduleTimeoutAt", "StepRun".error, "StepRun"."startedAt", "StepRun"."finishedAt", "StepRun"."timeoutAt", "StepRun"."cancelledAt", "StepRun"."cancelledReason", "StepRun"."cancelledError", "StepRun"."inputSchema"
RETURNING "StepRun".id, "StepRun"."createdAt", "StepRun"."updatedAt", "StepRun"."deletedAt", "StepRun"."tenantId", "StepRun"."jobRunId", "StepRun"."stepId", "StepRun"."order", "StepRun"."workerId", "StepRun"."tickerId", "StepRun".status, "StepRun".input, "StepRun".output, "StepRun"."requeueAfter", "StepRun"."scheduleTimeoutAt", "StepRun".error, "StepRun"."startedAt", "StepRun"."finishedAt", "StepRun"."timeoutAt", "StepRun"."cancelledAt", "StepRun"."cancelledReason", "StepRun"."cancelledError", "StepRun"."inputSchema", "StepRun"."callerFiles", "StepRun"."gitRepoBranch"
`
type UpdateStepRunParams struct {
@@ -247,6 +335,8 @@ func (q *Queries) UpdateStepRun(ctx context.Context, db DBTX, arg UpdateStepRunP
&i.CancelledReason,
&i.CancelledError,
&i.InputSchema,
&i.CallerFiles,
&i.GitRepoBranch,
)
return &i, err
}
@@ -278,27 +368,32 @@ func (q *Queries) UpdateStepRunInputSchema(ctx context.Context, db DBTX, arg Upd
const updateStepRunOverridesData = `-- name: UpdateStepRunOverridesData :one
UPDATE
"StepRun" as sr
"StepRun" AS sr
SET
"updatedAt" = CURRENT_TIMESTAMP,
"input" = jsonb_set("input", $1::text[], $2::jsonb, true)
"input" = jsonb_set("input", $1::text[], $2::jsonb, true),
"callerFiles" = jsonb_set("callerFiles", $3::text[], to_jsonb($4::text), true)
WHERE
sr."tenantId" = $3::uuid AND
sr."id" = $4::uuid
sr."tenantId" = $5::uuid AND
sr."id" = $6::uuid
RETURNING "input"
`
type UpdateStepRunOverridesDataParams struct {
Fieldpath []string `json:"fieldpath"`
Jsondata []byte `json:"jsondata"`
Tenantid pgtype.UUID `json:"tenantid"`
Steprunid pgtype.UUID `json:"steprunid"`
Fieldpath []string `json:"fieldpath"`
Jsondata []byte `json:"jsondata"`
Overrideskey []string `json:"overrideskey"`
Callerfile string `json:"callerfile"`
Tenantid pgtype.UUID `json:"tenantid"`
Steprunid pgtype.UUID `json:"steprunid"`
}
func (q *Queries) UpdateStepRunOverridesData(ctx context.Context, db DBTX, arg UpdateStepRunOverridesDataParams) ([]byte, error) {
row := db.QueryRow(ctx, updateStepRunOverridesData,
arg.Fieldpath,
arg.Jsondata,
arg.Overrideskey,
arg.Callerfile,
arg.Tenantid,
arg.Steprunid,
)
@@ -341,7 +341,8 @@ INSERT INTO "StepRun" (
"timeoutAt",
"cancelledAt",
"cancelledReason",
"cancelledError"
"cancelledError",
"callerFiles"
) VALUES (
COALESCE(sqlc.narg('id')::uuid, gen_random_uuid()),
CURRENT_TIMESTAMP,
@@ -363,7 +364,8 @@ INSERT INTO "StepRun" (
NULL,
NULL,
NULL,
NULL
NULL,
'{}'
) RETURNING *;
-- name: LinkStepRunParents :exec
@@ -305,7 +305,8 @@ INSERT INTO "StepRun" (
"timeoutAt",
"cancelledAt",
"cancelledReason",
"cancelledError"
"cancelledError",
"callerFiles"
) VALUES (
COALESCE($1::uuid, gen_random_uuid()),
CURRENT_TIMESTAMP,
@@ -327,8 +328,9 @@ INSERT INTO "StepRun" (
NULL,
NULL,
NULL,
NULL
) RETURNING id, "createdAt", "updatedAt", "deletedAt", "tenantId", "jobRunId", "stepId", "order", "workerId", "tickerId", status, input, output, "requeueAfter", "scheduleTimeoutAt", error, "startedAt", "finishedAt", "timeoutAt", "cancelledAt", "cancelledReason", "cancelledError", "inputSchema"
NULL,
'{}'
) RETURNING id, "createdAt", "updatedAt", "deletedAt", "tenantId", "jobRunId", "stepId", "order", "workerId", "tickerId", status, input, output, "requeueAfter", "scheduleTimeoutAt", error, "startedAt", "finishedAt", "timeoutAt", "cancelledAt", "cancelledReason", "cancelledError", "inputSchema", "callerFiles", "gitRepoBranch"
`
type CreateStepRunParams struct {
@@ -374,6 +376,8 @@ func (q *Queries) CreateStepRun(ctx context.Context, db DBTX, arg CreateStepRunP
&i.CancelledReason,
&i.CancelledError,
&i.InputSchema,
&i.CallerFiles,
&i.GitRepoBranch,
)
return &i, err
}
@@ -403,7 +407,7 @@ INSERT INTO "WorkflowRun" (
NULL, -- assuming error is not set on creation
NULL, -- assuming startedAt is not set on creation
NULL -- assuming finishedAt is not set on creation
) RETURNING "createdAt", "updatedAt", "deletedAt", "tenantId", "workflowVersionId", status, error, "startedAt", "finishedAt", "concurrencyGroupId", "displayName", id
) RETURNING "createdAt", "updatedAt", "deletedAt", "tenantId", "workflowVersionId", status, error, "startedAt", "finishedAt", "concurrencyGroupId", "displayName", id, "gitRepoBranch"
`
type CreateWorkflowRunParams struct {
@@ -434,6 +438,7 @@ func (q *Queries) CreateWorkflowRun(ctx context.Context, db DBTX, arg CreateWork
&i.ConcurrencyGroupId,
&i.DisplayName,
&i.ID,
&i.GitRepoBranch,
)
return &i, err
}
@@ -519,7 +524,7 @@ func (q *Queries) LinkStepRunParents(ctx context.Context, db DBTX, jobrunid pgty
const listStartableStepRuns = `-- name: ListStartableStepRuns :many
SELECT
child_run.id, child_run."createdAt", child_run."updatedAt", child_run."deletedAt", child_run."tenantId", child_run."jobRunId", child_run."stepId", child_run."order", child_run."workerId", child_run."tickerId", child_run.status, child_run.input, child_run.output, child_run."requeueAfter", child_run."scheduleTimeoutAt", child_run.error, child_run."startedAt", child_run."finishedAt", child_run."timeoutAt", child_run."cancelledAt", child_run."cancelledReason", child_run."cancelledError", child_run."inputSchema"
child_run.id, child_run."createdAt", child_run."updatedAt", child_run."deletedAt", child_run."tenantId", child_run."jobRunId", child_run."stepId", child_run."order", child_run."workerId", child_run."tickerId", child_run.status, child_run.input, child_run.output, child_run."requeueAfter", child_run."scheduleTimeoutAt", child_run.error, child_run."startedAt", child_run."finishedAt", child_run."timeoutAt", child_run."cancelledAt", child_run."cancelledReason", child_run."cancelledError", child_run."inputSchema", child_run."callerFiles", child_run."gitRepoBranch"
FROM
"StepRun" AS child_run
JOIN
@@ -578,6 +583,8 @@ func (q *Queries) ListStartableStepRuns(ctx context.Context, db DBTX, arg ListSt
&i.CancelledReason,
&i.CancelledError,
&i.InputSchema,
&i.CallerFiles,
&i.GitRepoBranch,
); err != nil {
return nil, err
}
@@ -591,7 +598,7 @@ func (q *Queries) ListStartableStepRuns(ctx context.Context, db DBTX, arg ListSt
const listWorkflowRuns = `-- name: ListWorkflowRuns :many
SELECT
runs."createdAt", runs."updatedAt", runs."deletedAt", runs."tenantId", runs."workflowVersionId", runs.status, runs.error, runs."startedAt", runs."finishedAt", runs."concurrencyGroupId", runs."displayName", runs.id,
runs."createdAt", runs."updatedAt", runs."deletedAt", runs."tenantId", runs."workflowVersionId", runs.status, runs.error, runs."startedAt", runs."finishedAt", runs."concurrencyGroupId", runs."displayName", runs.id, runs."gitRepoBranch",
workflow.id, workflow."createdAt", workflow."updatedAt", workflow."deletedAt", workflow."tenantId", workflow.name, workflow.description,
runtriggers.id, runtriggers."createdAt", runtriggers."updatedAt", runtriggers."deletedAt", runtriggers."tenantId", runtriggers."eventId", runtriggers."cronParentId", runtriggers."cronSchedule", runtriggers."scheduledId", runtriggers.input, runtriggers."parentId",
workflowversion.id, workflowversion."createdAt", workflowversion."updatedAt", workflowversion."deletedAt", workflowversion.version, workflowversion."order", workflowversion."workflowId", workflowversion.checksum,
@@ -693,6 +700,7 @@ func (q *Queries) ListWorkflowRuns(ctx context.Context, db DBTX, arg ListWorkflo
&i.WorkflowRun.ConcurrencyGroupId,
&i.WorkflowRun.DisplayName,
&i.WorkflowRun.ID,
&i.WorkflowRun.GitRepoBranch,
&i.Workflow.ID,
&i.Workflow.CreatedAt,
&i.Workflow.UpdatedAt,
@@ -783,7 +791,7 @@ WHERE "id" = (
FROM "JobRun"
WHERE "id" = $1::uuid
) AND "tenantId" = $2::uuid
RETURNING "WorkflowRun"."createdAt", "WorkflowRun"."updatedAt", "WorkflowRun"."deletedAt", "WorkflowRun"."tenantId", "WorkflowRun"."workflowVersionId", "WorkflowRun".status, "WorkflowRun".error, "WorkflowRun"."startedAt", "WorkflowRun"."finishedAt", "WorkflowRun"."concurrencyGroupId", "WorkflowRun"."displayName", "WorkflowRun".id
RETURNING "WorkflowRun"."createdAt", "WorkflowRun"."updatedAt", "WorkflowRun"."deletedAt", "WorkflowRun"."tenantId", "WorkflowRun"."workflowVersionId", "WorkflowRun".status, "WorkflowRun".error, "WorkflowRun"."startedAt", "WorkflowRun"."finishedAt", "WorkflowRun"."concurrencyGroupId", "WorkflowRun"."displayName", "WorkflowRun".id, "WorkflowRun"."gitRepoBranch"
`
type ResolveWorkflowRunStatusParams struct {
@@ -807,6 +815,7 @@ func (q *Queries) ResolveWorkflowRunStatus(ctx context.Context, db DBTX, arg Res
&i.ConcurrencyGroupId,
&i.DisplayName,
&i.ID,
&i.GitRepoBranch,
)
return &i, err
}
@@ -840,7 +849,7 @@ FROM
WHERE
workflowRun."id" = groupKeyRun."workflowRunId" AND
workflowRun."tenantId" = $1::uuid
RETURNING workflowrun."createdAt", workflowrun."updatedAt", workflowrun."deletedAt", workflowrun."tenantId", workflowrun."workflowVersionId", workflowrun.status, workflowrun.error, workflowrun."startedAt", workflowrun."finishedAt", workflowrun."concurrencyGroupId", workflowrun."displayName", workflowrun.id
RETURNING workflowrun."createdAt", workflowrun."updatedAt", workflowrun."deletedAt", workflowrun."tenantId", workflowrun."workflowVersionId", workflowrun.status, workflowrun.error, workflowrun."startedAt", workflowrun."finishedAt", workflowrun."concurrencyGroupId", workflowrun."displayName", workflowrun.id, workflowrun."gitRepoBranch"
`
type UpdateWorkflowRunGroupKeyParams struct {
@@ -864,6 +873,7 @@ func (q *Queries) UpdateWorkflowRunGroupKey(ctx context.Context, db DBTX, arg Up
&i.ConcurrencyGroupId,
&i.DisplayName,
&i.ID,
&i.GitRepoBranch,
)
return &i, err
}
@@ -612,7 +612,7 @@ func (q *Queries) ListWorkflows(ctx context.Context, db DBTX, arg ListWorkflowsP
const listWorkflowsLatestRuns = `-- name: ListWorkflowsLatestRuns :many
SELECT
DISTINCT ON (workflow."id") runs."createdAt", runs."updatedAt", runs."deletedAt", runs."tenantId", runs."workflowVersionId", runs.status, runs.error, runs."startedAt", runs."finishedAt", runs."concurrencyGroupId", runs."displayName", runs.id, workflow."id" as "workflowId"
DISTINCT ON (workflow."id") runs."createdAt", runs."updatedAt", runs."deletedAt", runs."tenantId", runs."workflowVersionId", runs.status, runs.error, runs."startedAt", runs."finishedAt", runs."concurrencyGroupId", runs."displayName", runs.id, runs."gitRepoBranch", workflow."id" as "workflowId"
FROM
"WorkflowRun" as runs
LEFT JOIN
@@ -683,6 +683,7 @@ func (q *Queries) ListWorkflowsLatestRuns(ctx context.Context, db DBTX, arg List
&i.WorkflowRun.ConcurrencyGroupId,
&i.WorkflowRun.DisplayName,
&i.WorkflowRun.ID,
&i.WorkflowRun.GitRepoBranch,
&i.WorkflowId,
); err != nil {
return nil, err
+191
View File
@@ -0,0 +1,191 @@
package prisma
import (
"context"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
"github.com/hatchet-dev/hatchet/internal/validator"
)
type githubRepository struct {
client *db.PrismaClient
v validator.Validator
}
func NewGithubRepository(client *db.PrismaClient, v validator.Validator) repository.GithubRepository {
return &githubRepository{
client: client,
v: v,
}
}
func (r *githubRepository) CreateInstallation(githubUserId int, opts *repository.CreateInstallationOpts) (*db.GithubAppInstallationModel, error) {
if err := r.v.Validate(opts); err != nil {
return nil, err
}
return r.client.GithubAppInstallation.CreateOne(
db.GithubAppInstallation.GithubAppOAuth.Link(
db.GithubAppOAuth.GithubUserID.Equals(githubUserId),
),
db.GithubAppInstallation.InstallationID.Set(opts.InstallationID),
db.GithubAppInstallation.AccountName.Set(opts.AccountName),
db.GithubAppInstallation.AccountID.Set(opts.AccountID),
db.GithubAppInstallation.Config.SetIfPresent(opts.Config),
db.GithubAppInstallation.AccountAvatarURL.SetIfPresent(opts.AccountAvatarURL),
db.GithubAppInstallation.InstallationSettingsURL.SetIfPresent(opts.InstallationSettingsURL),
).Exec(context.Background())
}
func (r *githubRepository) AddGithubUserIdToInstallation(installationID, accountID, githubUserId int) (*db.GithubAppInstallationModel, error) {
return r.client.GithubAppInstallation.FindUnique(
db.GithubAppInstallation.InstallationIDAccountID(
db.GithubAppInstallation.InstallationID.Equals(installationID),
db.GithubAppInstallation.AccountID.Equals(accountID),
),
).Update(
db.GithubAppInstallation.GithubAppOAuth.Link(
db.GithubAppOAuth.GithubUserID.Equals(githubUserId),
),
).Exec(context.Background())
}
func (r *githubRepository) ReadGithubAppInstallationByID(installationId string) (*db.GithubAppInstallationModel, error) {
return r.client.GithubAppInstallation.FindUnique(
db.GithubAppInstallation.ID.Equals(installationId),
).Exec(context.Background())
}
func (r *githubRepository) ReadGithubWebhook(tenantId, repoOwner, repoName string) (*db.GithubWebhookModel, error) {
return r.client.GithubWebhook.FindUnique(
db.GithubWebhook.TenantIDRepositoryOwnerRepositoryName(
db.GithubWebhook.TenantID.Equals(tenantId),
db.GithubWebhook.RepositoryOwner.Equals(repoOwner),
db.GithubWebhook.RepositoryName.Equals(repoName),
),
).Exec(context.Background())
}
func (r *githubRepository) ReadGithubWebhookById(id string) (*db.GithubWebhookModel, error) {
return r.client.GithubWebhook.FindUnique(
db.GithubWebhook.ID.Equals(id),
).Exec(context.Background())
}
func (r *githubRepository) ReadGithubAppOAuthByGithubUserID(githubUserId int) (*db.GithubAppOAuthModel, error) {
return r.client.GithubAppOAuth.FindUnique(
db.GithubAppOAuth.GithubUserID.Equals(githubUserId),
).Exec(context.Background())
}
func (r *githubRepository) ReadGithubAppInstallationByInstallationAndAccountID(installationID, accountID int) (*db.GithubAppInstallationModel, error) {
return r.client.GithubAppInstallation.FindUnique(
db.GithubAppInstallation.InstallationIDAccountID(
db.GithubAppInstallation.InstallationID.Equals(installationID),
db.GithubAppInstallation.AccountID.Equals(accountID),
),
).Exec(context.Background())
}
func (r *githubRepository) CreateGithubWebhook(tenantId string, opts *repository.CreateGithubWebhookOpts) (*db.GithubWebhookModel, error) {
if err := r.v.Validate(opts); err != nil {
return nil, err
}
return r.client.GithubWebhook.CreateOne(
db.GithubWebhook.Tenant.Link(
db.Tenant.ID.Equals(tenantId),
),
db.GithubWebhook.RepositoryOwner.Set(opts.RepoOwner),
db.GithubWebhook.RepositoryName.Set(opts.RepoName),
db.GithubWebhook.SigningSecret.Set(opts.SigningSecret),
).Exec(context.Background())
}
func (r *githubRepository) UpsertGithubAppOAuth(userId string, opts *repository.CreateGithubAppOAuthOpts) (*db.GithubAppOAuthModel, error) {
if err := r.v.Validate(opts); err != nil {
return nil, err
}
return r.client.GithubAppOAuth.UpsertOne(
db.GithubAppOAuth.GithubUserID.Equals(opts.GithubUserID),
).Create(
db.GithubAppOAuth.GithubUserID.Set(opts.GithubUserID),
db.GithubAppOAuth.AccessToken.Set(opts.AccessToken),
db.GithubAppOAuth.RefreshToken.SetIfPresent(opts.RefreshToken),
db.GithubAppOAuth.ExpiresAt.SetIfPresent(opts.ExpiresAt),
db.GithubAppOAuth.Users.Link(
db.User.ID.Equals(userId),
),
).Update(
db.GithubAppOAuth.AccessToken.Set(opts.AccessToken),
db.GithubAppOAuth.RefreshToken.SetIfPresent(opts.RefreshToken),
db.GithubAppOAuth.ExpiresAt.SetIfPresent(opts.ExpiresAt),
db.GithubAppOAuth.Users.Link(
db.User.ID.Equals(userId),
),
).Exec(context.Background())
}
func (r *githubRepository) CanUserAccessInstallation(installationId, userId string) (bool, error) {
installation, err := r.client.GithubAppInstallation.FindFirst(
db.GithubAppInstallation.ID.Equals(installationId),
db.GithubAppInstallation.GithubAppOAuth.Where(
db.GithubAppOAuth.Users.Some(
db.User.ID.Equals(userId),
),
),
).Exec(context.Background())
if err != nil {
return false, nil
}
return installation != nil, nil
}
func (r *githubRepository) DeleteInstallation(installationId, accountId int) (*db.GithubAppInstallationModel, error) {
return r.client.GithubAppInstallation.FindUnique(
db.GithubAppInstallation.InstallationIDAccountID(
db.GithubAppInstallation.InstallationID.Equals(installationId),
db.GithubAppInstallation.AccountID.Equals(accountId),
),
).Delete().Exec(context.Background())
}
func (r *githubRepository) ListGithubAppInstallationsByUserID(userId string) ([]db.GithubAppInstallationModel, error) {
return r.client.GithubAppInstallation.FindMany(
db.GithubAppInstallation.GithubAppOAuth.Where(
db.GithubAppOAuth.Users.Some(
db.User.ID.Equals(userId),
),
),
).Exec(context.Background())
}
func (r *githubRepository) UpdatePullRequest(tenantId, prId string, opts *repository.UpdatePullRequestOpts) (*db.GithubPullRequestModel, error) {
if err := r.v.Validate(opts); err != nil {
return nil, err
}
return r.client.GithubPullRequest.FindUnique(
db.GithubPullRequest.ID.Equals(prId),
).Update(
db.GithubPullRequest.PullRequestState.SetIfPresent(opts.State),
db.GithubPullRequest.PullRequestHeadBranch.SetIfPresent(opts.HeadBranch),
db.GithubPullRequest.PullRequestBaseBranch.SetIfPresent(opts.BaseBranch),
db.GithubPullRequest.PullRequestTitle.SetIfPresent(opts.Title),
).Exec(context.Background())
}
func (r *githubRepository) GetPullRequest(tenantId, repoOwner, repoName string, prNumber int) (*db.GithubPullRequestModel, error) {
return r.client.GithubPullRequest.FindUnique(
db.GithubPullRequest.TenantIDRepositoryOwnerRepositoryNamePullRequestNumber(
db.GithubPullRequest.TenantID.Equals(tenantId),
db.GithubPullRequest.RepositoryOwner.Equals(repoOwner),
db.GithubPullRequest.RepositoryName.Equals(repoName),
db.GithubPullRequest.PullRequestNumber.Equals(prNumber),
),
).Exec(context.Background())
}
+6
View File
@@ -19,6 +19,7 @@ type prismaRepository struct {
jobRun repository.JobRunRepository
stepRun repository.StepRunRepository
getGroupKeyRun repository.GetGroupKeyRunRepository
github repository.GithubRepository
step repository.StepRepository
dispatcher repository.DispatcherRepository
worker repository.WorkerRepository
@@ -72,6 +73,7 @@ func NewPrismaRepository(client *db.PrismaClient, pool *pgxpool.Pool, fs ...Pris
jobRun: NewJobRunRepository(client, pool, opts.v, opts.l),
stepRun: NewStepRunRepository(client, pool, opts.v, opts.l),
getGroupKeyRun: NewGetGroupKeyRunRepository(client, pool, opts.v, opts.l),
github: NewGithubRepository(client, opts.v),
step: NewStepRepository(client, opts.v),
dispatcher: NewDispatcherRepository(client, pool, opts.v, opts.l),
worker: NewWorkerRepository(client, opts.v),
@@ -117,6 +119,10 @@ func (r *prismaRepository) GetGroupKeyRun() repository.GetGroupKeyRunRepository
return r.getGroupKeyRun
}
func (r *prismaRepository) Github() repository.GithubRepository {
return r.github
}
func (r *prismaRepository) Step() repository.StepRepository {
return r.step
}
+59
View File
@@ -245,6 +245,12 @@ func (s *stepRunRepository) UpdateStepRunOverridesData(tenantId, stepRunId strin
pgTenantId := sqlchelpers.UUIDFromStr(tenantId)
pgStepRunId := sqlchelpers.UUIDFromStr(stepRunId)
callerFile := ""
if opts.CallerFile != nil {
callerFile = *opts.CallerFile
}
input, err := s.queries.UpdateStepRunOverridesData(
context.Background(),
tx,
@@ -256,6 +262,10 @@ func (s *stepRunRepository) UpdateStepRunOverridesData(tenantId, stepRunId strin
opts.OverrideKey,
},
Jsondata: opts.Data,
Overrideskey: []string{
opts.OverrideKey,
},
Callerfile: callerFile,
},
)
@@ -581,3 +591,52 @@ func (s *stepRunRepository) ListStartableStepRuns(tenantId, jobRunId, parentStep
return stepRuns, nil
}
func (s *stepRunRepository) ArchiveStepRunResult(tenantId, stepRunId string) error {
tx, err := s.pool.Begin(context.Background())
if err != nil {
return err
}
defer deferRollback(context.Background(), s.l, tx.Rollback)
_, err = s.queries.ArchiveStepRunResultFromStepRun(context.Background(), tx, dbsqlc.ArchiveStepRunResultFromStepRunParams{
Tenantid: sqlchelpers.UUIDFromStr(tenantId),
Steprunid: sqlchelpers.UUIDFromStr(stepRunId),
})
if err != nil {
return err
}
err = tx.Commit(context.Background())
if err != nil {
return err
}
return nil
}
func (s *stepRunRepository) ListArchivedStepRunResults(tenantId, stepRunId string) ([]db.StepRunResultArchiveModel, error) {
return s.client.StepRunResultArchive.FindMany(
db.StepRunResultArchive.StepRunID.Equals(stepRunId),
db.StepRunResultArchive.StepRun.Where(
db.StepRun.TenantID.Equals(tenantId),
),
).OrderBy(
db.StepRunResultArchive.Order.Order(db.DESC),
).Exec(context.Background())
}
func (s *stepRunRepository) GetFirstArchivedStepRunResult(tenantId, stepRunId string) (*db.StepRunResultArchiveModel, error) {
return s.client.StepRunResultArchive.FindFirst(
db.StepRunResultArchive.StepRunID.Equals(stepRunId),
db.StepRunResultArchive.StepRun.Where(
db.StepRun.TenantID.Equals(tenantId),
),
).OrderBy(
db.StepRunResultArchive.Order.Order(db.ASC),
).Exec(context.Background())
}
+35
View File
@@ -704,9 +704,44 @@ func (r *workflowRepository) GetWorkflowVersionById(tenantId, workflowVersionId
).Exec(context.Background())
}
func (r *workflowRepository) UpsertWorkflowDeploymentConfig(workflowId string, opts *repository.UpsertWorkflowDeploymentConfigOpts) (*db.WorkflowDeploymentConfigModel, error) {
if err := r.v.Validate(opts); err != nil {
return nil, err
}
// upsert the deployment config
deploymentConfig, err := r.client.WorkflowDeploymentConfig.UpsertOne(
db.WorkflowDeploymentConfig.WorkflowID.Equals(workflowId),
).Create(
db.WorkflowDeploymentConfig.Workflow.Link(
db.Workflow.ID.Equals(workflowId),
),
db.WorkflowDeploymentConfig.GitRepoName.Set(opts.GitRepoName),
db.WorkflowDeploymentConfig.GitRepoOwner.Set(opts.GitRepoOwner),
db.WorkflowDeploymentConfig.GitRepoBranch.Set(opts.GitRepoBranch),
db.WorkflowDeploymentConfig.GithubAppInstallation.Link(
db.GithubAppInstallation.ID.Equals(opts.GithubAppInstallationId),
),
).Update(
db.WorkflowDeploymentConfig.GitRepoName.Set(opts.GitRepoName),
db.WorkflowDeploymentConfig.GitRepoOwner.Set(opts.GitRepoOwner),
db.WorkflowDeploymentConfig.GitRepoBranch.Set(opts.GitRepoBranch),
db.WorkflowDeploymentConfig.GithubAppInstallation.Link(
db.GithubAppInstallation.ID.Equals(opts.GithubAppInstallationId),
),
).Exec(context.Background())
if err != nil {
return nil, err
}
return deploymentConfig, nil
}
func defaultWorkflowPopulator() []db.WorkflowRelationWith {
return []db.WorkflowRelationWith{
db.Workflow.Tags.Fetch(),
db.Workflow.DeploymentConfig.Fetch(),
db.Workflow.Versions.Fetch().OrderBy(
db.WorkflowVersion.Order.Order(db.SortOrderDesc),
).With(
@@ -370,6 +370,46 @@ func (w *workflowRunRepository) GetWorkflowRunById(tenantId, id string) (*db.Wor
).Exec(context.Background())
}
func (s *workflowRunRepository) CreateWorkflowRunPullRequest(tenantId, workflowRunId string, opts *repository.CreateWorkflowRunPullRequestOpts) (*db.GithubPullRequestModel, error) {
return s.client.GithubPullRequest.CreateOne(
db.GithubPullRequest.Tenant.Link(
db.Tenant.ID.Equals(tenantId),
),
db.GithubPullRequest.RepositoryOwner.Set(opts.RepositoryOwner),
db.GithubPullRequest.RepositoryName.Set(opts.RepositoryName),
db.GithubPullRequest.PullRequestID.Set(opts.PullRequestID),
db.GithubPullRequest.PullRequestTitle.Set(opts.PullRequestTitle),
db.GithubPullRequest.PullRequestNumber.Set(opts.PullRequestNumber),
db.GithubPullRequest.PullRequestHeadBranch.Set(opts.PullRequestHeadBranch),
db.GithubPullRequest.PullRequestBaseBranch.Set(opts.PullRequestBaseBranch),
db.GithubPullRequest.PullRequestState.Set(opts.PullRequestState),
db.GithubPullRequest.WorkflowRuns.Link(
db.WorkflowRun.ID.Equals(workflowRunId),
),
).Exec(context.Background())
}
func (s *workflowRunRepository) ListPullRequestsForWorkflowRun(tenantId, workflowRunId string, opts *repository.ListPullRequestsForWorkflowRunOpts) ([]db.GithubPullRequestModel, error) {
if err := s.v.Validate(opts); err != nil {
return nil, err
}
optionals := []db.GithubPullRequestWhereParam{
db.GithubPullRequest.WorkflowRuns.Some(
db.WorkflowRun.ID.Equals(workflowRunId),
db.WorkflowRun.TenantID.Equals(tenantId),
),
}
if opts.State != nil {
optionals = append(optionals, db.GithubPullRequest.PullRequestState.Equals(*opts.State))
}
return s.client.GithubPullRequest.FindMany(
optionals...,
).Exec(context.Background())
}
func defaultWorkflowRunPopulator() []db.WorkflowRunRelationWith {
return []db.WorkflowRunRelationWith{
db.WorkflowRun.WorkflowVersion.Fetch().With(
+1
View File
@@ -10,6 +10,7 @@ type Repository interface {
JobRun() JobRunRepository
StepRun() StepRunRepository
GetGroupKeyRun() GetGroupKeyRunRepository
Github() GithubRepository
Step() StepRepository
Dispatcher() DispatcherRepository
Ticker() TickerRepository
+7
View File
@@ -55,6 +55,7 @@ type UpdateStepRunOpts struct {
type UpdateStepRunOverridesDataOpts struct {
OverrideKey string
Data []byte
CallerFile *string
}
func StepRunStatusPtr(status db.StepRunStatus) *db.StepRunStatus {
@@ -94,4 +95,10 @@ type StepRunRepository interface {
CancelPendingStepRuns(tenantId, jobRunId, reason string) error
ListStartableStepRuns(tenantId, jobRunId, parentStepRunId string) ([]*dbsqlc.StepRun, error)
ArchiveStepRunResult(tenantId, stepRunId string) error
ListArchivedStepRunResults(tenantId, stepRunId string) ([]db.StepRunResultArchiveModel, error)
GetFirstArchivedStepRunResult(tenantId, stepRunId string) (*db.StepRunResultArchiveModel, error)
}
+16
View File
@@ -141,6 +141,20 @@ func (e *JobRunHasCycleError) Error() string {
return fmt.Sprintf("job %s has a cycle", e.JobName)
}
type UpsertWorkflowDeploymentConfigOpts struct {
// (required) the github app installation id
GithubAppInstallationId string `validate:"required,uuid"`
// (required) the github repository name
GitRepoName string `validate:"required"`
// (required) the github repository owner
GitRepoOwner string `validate:"required"`
// (required) the github repository branch
GitRepoBranch string `validate:"required"`
}
type WorkflowRepository interface {
// ListWorkflows returns all workflows for a given tenant.
ListWorkflows(tenantId string, opts *ListWorkflowsOpts) (*ListWorkflowsResult, error)
@@ -175,4 +189,6 @@ type WorkflowRepository interface {
// DeleteWorkflow deletes a workflow for a given tenant.
DeleteWorkflow(tenantId, workflowId string) (*db.WorkflowModel, error)
UpsertWorkflowDeploymentConfig(workflowId string, opts *UpsertWorkflowDeploymentConfigOpts) (*db.WorkflowDeploymentConfigModel, error)
}
+19
View File
@@ -219,6 +219,21 @@ type ListWorkflowRunsResult struct {
Count int
}
type CreateWorkflowRunPullRequestOpts struct {
RepositoryOwner string
RepositoryName string
PullRequestID int
PullRequestTitle string
PullRequestNumber int
PullRequestHeadBranch string
PullRequestBaseBranch string
PullRequestState string
}
type ListPullRequestsForWorkflowRunOpts struct {
State *string
}
type WorkflowRunRepository interface {
// ListWorkflowRuns returns workflow runs for a given workflow version id.
ListWorkflowRuns(tenantId string, opts *ListWorkflowRunsOpts) (*ListWorkflowRunsResult, error)
@@ -228,4 +243,8 @@ type WorkflowRunRepository interface {
// GetWorkflowRunById returns a workflow run by id.
GetWorkflowRunById(tenantId, runId string) (*db.WorkflowRunModel, error)
CreateWorkflowRunPullRequest(tenantId, workflowRunId string, opts *CreateWorkflowRunPullRequestOpts) (*db.GithubPullRequestModel, error)
ListPullRequestsForWorkflowRun(tenantId, workflowRunId string, opts *ListPullRequestsForWorkflowRunOpts) ([]db.GithubPullRequestModel, error)
}
@@ -1120,6 +1120,12 @@ func (x *WorkflowEvent) GetEventPayload() string {
return ""
}
func (x *WorkflowEvent) GetHangup() bool {
if x != nil {
return x.Hangup
}
return false
}
type OverridesData struct {
state protoimpl.MessageState
@@ -1132,6 +1138,8 @@ type OverridesData struct {
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
// the value to set
Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
// the filename of the caller
CallerFilename string `protobuf:"bytes,4,opt,name=callerFilename,proto3" json:"callerFilename,omitempty"`
}
func (x *OverridesData) Reset() {
@@ -1187,6 +1195,13 @@ func (x *OverridesData) GetValue() string {
return ""
}
func (x *OverridesData) GetCallerFilename() string {
if x != nil {
return x.CallerFilename
}
return ""
}
type OverridesDataResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1349,12 +1364,16 @@ var file_dispatcher_proto_rawDesc = []byte{
0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f,
0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x57, 0x0a, 0x0d, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
0x64, 0x65, 0x73, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52,
0x75, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70,
0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x61, 0x6e, 0x67, 0x75, 0x70,
0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x61, 0x6e, 0x67, 0x75, 0x70, 0x22, 0x7f,
0x0a, 0x0d, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x44, 0x61, 0x74, 0x61, 0x12,
0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a,
0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6c, 0x6c, 0x65,
0x72, 0x46, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x22,
0x17, 0x0a, 0x15, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x44, 0x61, 0x74, 0x61,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x4e, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f,
+8 -2
View File
@@ -365,10 +365,16 @@ func (s *DispatcherImpl) PutOverridesData(ctx context.Context, request *contract
return nil, fmt.Errorf("step run id is required")
}
input, err := s.repo.StepRun().UpdateStepRunOverridesData(tenant.ID, request.StepRunId, &repository.UpdateStepRunOverridesDataOpts{
opts := &repository.UpdateStepRunOverridesDataOpts{
OverrideKey: request.Path,
Data: []byte(request.Value),
})
}
if request.CallerFilename != "" {
opts.CallerFile = &request.CallerFilename
}
input, err := s.repo.StepRun().UpdateStepRunOverridesData(tenant.ID, request.StepRunId, opts)
if err != nil {
return nil, err
+457
View File
@@ -0,0 +1,457 @@
package worker
import (
"context"
"encoding/json"
"fmt"
"io"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"golang.org/x/sync/errgroup"
"github.com/steebchen/prisma-client-go/runtime/types"
"github.com/hatchet-dev/hatchet/internal/datautils"
"github.com/hatchet-dev/hatchet/internal/encryption"
"github.com/hatchet-dev/hatchet/internal/integrations/vcs"
"github.com/hatchet-dev/hatchet/internal/repository"
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
"github.com/hatchet-dev/hatchet/pkg/client"
"github.com/hatchet-dev/hatchet/pkg/worker"
)
const (
PullRequestWorkflow string = "create-pull-request"
StartPullRequest string = "pull_request:start"
)
type StartPullRequestEvent struct {
TenantID string `json:"tenant_id"`
StepRunID string `json:"step_run_id"`
BranchName string `json:"branch_name"`
}
type WorkerOpt func(*WorkerOpts)
type WorkerOpts struct {
client client.Client
repo repository.Repository
vcsProviders map[vcs.VCSRepositoryKind]vcs.VCSProvider
}
func defaultWorkerOpts() *WorkerOpts {
return &WorkerOpts{}
}
func WithRepository(r repository.Repository) WorkerOpt {
return func(opts *WorkerOpts) {
opts.repo = r
}
}
func WithVCSProviders(vcsProviders map[vcs.VCSRepositoryKind]vcs.VCSProvider) WorkerOpt {
return func(opts *WorkerOpts) {
opts.vcsProviders = vcsProviders
}
}
func WithClient(c client.Client) WorkerOpt {
return func(opts *WorkerOpts) {
opts.client = c
}
}
type WorkerImpl struct {
*worker.Worker
repo repository.Repository
vcsProviders map[vcs.VCSRepositoryKind]vcs.VCSProvider
}
func NewWorker(fs ...WorkerOpt) (*WorkerImpl, error) {
opts := defaultWorkerOpts()
for _, f := range fs {
f(opts)
}
if opts.repo == nil {
return nil, fmt.Errorf("repository is required. use WithRepository")
}
if opts.client == nil {
return nil, fmt.Errorf("client is required. use WithClient")
}
hatchetWorker, err := worker.NewWorker(
worker.WithClient(opts.client),
)
if err != nil {
return nil, fmt.Errorf("could not create worker: %w", err)
}
return &WorkerImpl{
Worker: hatchetWorker,
repo: opts.repo,
vcsProviders: opts.vcsProviders,
}, nil
}
func (w *WorkerImpl) Start(ctx context.Context) error {
err := w.On(
worker.Event(StartPullRequest),
&worker.WorkflowJob{
Name: PullRequestWorkflow,
Description: "Workflow that creates a new pull request.",
Timeout: "60s",
Steps: []*worker.WorkflowStep{
worker.Fn(w.handleStartPullRequest).SetName("start-pull-request"),
},
},
)
if err != nil {
return fmt.Errorf("could not register workflow: %w", err)
}
// start the worker
return w.Worker.Start(ctx)
}
func (w *WorkerImpl) handleStartPullRequest(ctx worker.HatchetContext) error {
var event StartPullRequestEvent
err := ctx.WorkflowInput(&event)
if err != nil {
return err
}
stepRun, err := w.repo.StepRun().GetStepRunById(event.TenantID, event.StepRunID)
if err != nil {
return fmt.Errorf("could not get step run: %w", err)
}
workflowRun, err := w.repo.WorkflowRun().GetWorkflowRunById(event.TenantID, stepRun.JobRun().WorkflowRunID)
if err != nil {
return fmt.Errorf("could not get workflow run: %w", err)
}
// read workflow
workflow, err := w.repo.Workflow().GetWorkflowById(workflowRun.WorkflowVersion().WorkflowID)
if err != nil {
return fmt.Errorf("could not get workflow: %w", err)
}
if deploymentConfig, ok := workflow.DeploymentConfig(); ok {
if installationId, ok := deploymentConfig.GithubAppInstallationID(); ok && installationId != "" {
git, err := vcs.GetVCSRepositoryFromWorkflow(w.vcsProviders, workflow)
if err != nil {
return fmt.Errorf("could not get VCS repository from workflow: %w", err)
}
callerFilepaths := map[string]string{}
callerFilepathsJSON, _ := stepRun.CallerFiles()
err = json.Unmarshal(callerFilepathsJSON, &callerFilepaths)
if err != nil {
return fmt.Errorf("could not unmarshal caller filepaths: %w", err)
}
callerFilepathsToRepoFiles := sync.Map{}
var errGroup errgroup.Group
for _, file := range callerFilepaths {
fileCp := file
errGroup.Go(func() error {
gitBranch, _ := stepRun.GitRepoBranch()
foundSearchPath, fileReader, err := searchForFile(fileCp, git, gitBranch)
if err != nil {
return fmt.Errorf("could not search for file: %w", err)
}
if fileReader != nil {
fileBytes, err := io.ReadAll(fileReader)
if err != nil {
return fmt.Errorf("could not read file: %w", err)
}
callerFilepathsToRepoFiles.Store(foundSearchPath, fileBytes)
}
return nil
})
}
if err = errGroup.Wait(); err != nil {
return fmt.Errorf("could not search for files: %w", err)
}
diffs, err := w.getStepRunOverrideDiffs(stepRun)
if err != nil {
return fmt.Errorf("could not get step run override diffs: %w", err)
}
newFiles := make(map[string][]byte)
callerFilepathsToRepoFiles.Range(func(key, value interface{}) bool {
fileBytes := value.([]byte)
for diffKey, diffValue := range diffs {
fileBytes, err = findAndReplace(fileBytes, diffKey, diffValue)
if err != nil {
return true
}
}
newFiles[key.(string)] = fileBytes
return true
})
prSuffix, err := encryption.GenerateRandomBytes(4)
if err != nil {
return fmt.Errorf("could not generate random bytes: %w", err)
}
var baseBranch *string
if event.BranchName != "" {
baseBranch = &event.BranchName
}
// create the pull request
_, err = git.CreateOrUpdatePullRequest(stepRun.TenantID, workflowRun.ID, &vcs.CreatePullRequestOpts{
GitRepoOwner: git.GetRepoOwner(),
GitRepoName: git.GetRepoName(),
BaseBranch: baseBranch,
Files: newFiles,
Title: fmt.Sprintf("[Hatchet] Update %s", workflow.Name),
HeadBranchName: fmt.Sprintf("hatchet/pr-%s", prSuffix),
})
if err != nil {
return fmt.Errorf("could not create pull request: %w", err)
}
}
}
return nil
}
func (w *WorkerImpl) getStepRunOverrideDiffs(stepRun *db.StepRunModel) (map[string]string, error) {
// get the first step run archived result, there will be at least one
archivedResult, err := w.repo.StepRun().GetFirstArchivedStepRunResult(stepRun.TenantID, stepRun.ID)
if err != nil {
return nil, fmt.Errorf("could not get first archived step run result: %w", err)
}
firstInput, err := getStepRunInput(archivedResult)
if err != nil {
return nil, fmt.Errorf("could not get input from archived result: %w", err)
}
secondInput, err := getStepRunInput(stepRun)
if err != nil {
return nil, fmt.Errorf("could not get input from step run: %w", err)
}
// compare the data
diff := map[string]string{}
for key, value := range firstInput.Overrides {
if secondValue, ok := secondInput.Overrides[key]; ok {
if value != secondValue {
newValue := formatNewValue(secondValue)
if newValue != "" {
diff[key] = newValue
}
}
}
}
return diff, nil
}
func formatNewValue(val interface{}) string {
switch v := val.(type) {
case string:
return strconv.Quote(v)
case float64, bool:
return fmt.Sprintf("%v", v)
case nil:
return "null"
default:
return ""
}
}
type inputtable interface {
Input() (value types.JSON, ok bool)
}
func getStepRunInput(in inputtable) (*datautils.StepRunData, error) {
input, ok := in.Input()
if !ok {
return nil, fmt.Errorf("could not get input from inputtable")
}
data := &datautils.StepRunData{}
if err := json.Unmarshal(input, data); err != nil || data == nil {
return nil, fmt.Errorf("could not unmarshal input: %w", err)
}
return data, nil
}
func searchForFile(targetPath string, vcs vcs.VCSRepository, ref string) (string, io.ReadCloser, error) {
if !filepath.IsAbs(targetPath) {
return "", nil, fmt.Errorf("filepath must be absolute")
}
searchPaths := getSearchPaths(targetPath)
var res io.ReadCloser
var foundSearchPath string
for _, searchPath := range searchPaths {
searchPathCp := searchPath
file, err := vcs.ReadFile(ref, searchPathCp)
if err == nil {
foundSearchPath = searchPathCp
res = file
}
}
return foundSearchPath, res, nil
}
// getSearchPaths returns paths from the most specific path to the least specific in the path. For
// example, if the caller file is under `/usr/local/hatchet/src/worker.py`, and the file is under ./src/worker.py
// in the repository, it will search in this order:
// - `/usr/local/hatchet/src/worker.py`
// - `/local/hatchet/src/worker.py`
// - `/hatchet/src/worker.py`
// - `/src/worker.py`
func getSearchPaths(targetPath string) []string {
base := filepath.Base(targetPath)
searchBases := []string{}
if base != "" && base != "/" && base != "." && base != ".." {
searchBases = append(searchBases, base)
}
currDir := targetPath
for {
currDir = filepath.Dir(currDir)
base := filepath.Base(currDir)
if base != "" && base != "/" && base != "." && base != ".." {
searchBases = append([]string{base}, searchBases...)
}
if currDir == "." || currDir == "/" {
break
}
}
searchPaths := []string{}
// construct search bases in reverse order
for i := range searchBases {
searchPath := strings.Join(searchBases[i:], "/")
if searchPath != "" {
searchPaths = append(searchPaths, searchPath)
}
}
return searchPaths
}
func searchWithRegex(input []byte, pattern string, replacements map[string]string) (string, error) {
// Compile the regular expression with named capturing groups
re, err := regexp.Compile(pattern)
if err != nil {
return "", err
}
// Convert input to string for easier manipulation and because we work with indexes
inputStr := string(input)
offset := 0 // Keep track of the offset caused by replacements
// Find all matches
matches := re.FindAllStringSubmatchIndex(inputStr, -1)
for _, match := range matches {
// Process each named group for replacement
for i := 2; i < len(match); i += 2 {
// Adjust group indexes by offset
groupStart, groupEnd := match[i]+offset, match[i+1]+offset
if groupStart == -1 || groupEnd == -1 {
continue // Skip unmatched groups
}
name := re.SubexpNames()[i/2] // Get the name of the current capturing group
if replacement, ok := replacements[name]; ok && name != "" {
// Perform the replacement in the input string
before := inputStr[:groupStart]
after := inputStr[groupEnd:]
inputStr = before + replacement + after
// Update the offset
offset += len(replacement) - (groupEnd - groupStart)
}
}
}
return inputStr, nil
}
func getPatternForValue(value string) string {
return `(?P<instance>\w+)\.override\(\s*(?P<param1>('|"|""")\s*` + value + `\s*('|"|"""))\s*,\s*(?P<override>[\s\S]*?)\s*\)`
}
func findAndReplace(input []byte, value, override string) ([]byte, error) {
pattern := getPatternForValue(value)
replacements := map[string]string{
"override": override,
}
modifiedInput, err := searchWithRegex(input, pattern, replacements)
if err != nil {
return nil, err
}
return []byte(modifiedInput), nil
}
+109
View File
@@ -0,0 +1,109 @@
package worker
import (
"reflect"
"testing"
)
func TestGetSearchPaths(t *testing.T) {
tests := []struct {
name string
targetPath string
want []string
}{
{
name: "Basic Path",
targetPath: "/usr/local/hatchet/src/worker.py",
want: []string{
"usr/local/hatchet/src/worker.py",
"local/hatchet/src/worker.py",
"hatchet/src/worker.py",
"src/worker.py",
"worker.py",
},
},
{
name: "Root Path",
targetPath: "/worker.py",
want: []string{"worker.py"},
},
{
name: "Empty Path",
targetPath: "",
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getSearchPaths(tt.targetPath); !reflect.DeepEqual(got, tt.want) {
t.Errorf("[%s] getSearchPaths() = %v, want %v", tt.name, got, tt.want)
}
})
}
}
func TestFindAndReplace(t *testing.T) {
tests := []struct {
name string
input string
value string
override string
expected string
expectError bool
}{
{
name: "Basic Replacement",
input: `context.override("test", "Original override")`,
value: "test",
override: `"New override"`,
expected: `context.override("test", "New override")`,
expectError: false,
},
{
name: "Multiple Replacement",
input: `context.override("test", "Original override"); context.override("test", "Another original")`,
value: "test",
override: `"New override"`,
expected: `context.override("test", "New override"); context.override("test", "New override")`,
expectError: false,
},
{
name: "Replace number",
input: `context.override("test", 1234)`,
value: "test",
override: "5678",
expected: `context.override("test", 5678)`,
expectError: false,
},
{
name: "No Match",
input: `context.override("no_match", "Original override")`,
value: "test",
override: `"New override"`,
expected: `context.override("no_match", "Original override")`,
expectError: false,
},
{
name: "Error Handling - Invalid Regex",
input: `context.override("test", "Original override")`,
value: "(", // This makes the regex invalid
override: `"New override"`,
expected: "",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := findAndReplace([]byte(tt.input), tt.value, tt.override)
if (err != nil) != tt.expectError {
t.Errorf("[%s] findAndReplace() error = %v, expectError %v", tt.name, err, tt.expectError)
return
}
if string(result) != tt.expected {
t.Errorf("[%s] findAndReplace() got = %v, want %v", tt.name, string(result), tt.expected)
}
})
}
}
+40 -6
View File
@@ -8,6 +8,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/hatchet-dev/hatchet/internal/config/client"
"github.com/hatchet-dev/hatchet/internal/logger"
"github.com/hatchet-dev/hatchet/internal/validator"
"github.com/hatchet-dev/hatchet/pkg/client/loader"
@@ -53,14 +54,27 @@ type ClientOpts struct {
initWorkflows bool
}
func defaultClientOpts() *ClientOpts {
// read from environment variables and hostname by default
func defaultClientOpts(cf *client.ClientConfigFile) *ClientOpts {
var clientConfig *client.ClientConfig
var err error
configLoader := &loader.ConfigLoader{}
clientConfig, err := configLoader.LoadClientConfig()
if cf == nil {
// read from environment variables and hostname by default
if err != nil {
panic(err)
clientConfig, err = configLoader.LoadClientConfig()
if err != nil {
panic(err)
}
} else {
clientConfig, err = loader.GetClientConfigFromConfigFile(cf)
if err != nil {
panic(err)
}
}
logger := logger.NewDefaultLogger("client")
@@ -88,6 +102,12 @@ func WithHostPort(host string, port int) ClientOpt {
}
}
func WithToken(token string) ClientOpt {
return func(opts *ClientOpts) {
opts.token = token
}
}
func InitWorkflows() ClientOpt {
return func(opts *ClientOpts) {
opts.initWorkflows = true
@@ -113,12 +133,26 @@ type sharedClientOpts struct {
// New creates a new client instance.
func New(fs ...ClientOpt) (Client, error) {
opts := defaultClientOpts()
opts := defaultClientOpts(nil)
for _, f := range fs {
f(opts)
}
return newFromOpts(opts)
}
func NewFromConfigFile(cf *client.ClientConfigFile, fs ...ClientOpt) (Client, error) {
opts := defaultClientOpts(cf)
for _, f := range fs {
f(opts)
}
return newFromOpts(opts)
}
func newFromOpts(opts *ClientOpts) (Client, error) {
// if no TLS, exit
if opts.tls == nil {
return nil, fmt.Errorf("tls config is required")
+8
View File
@@ -43,6 +43,14 @@ func LoadClientConfigFile(files ...[]byte) (*client.ClientConfigFile, error) {
}
func GetClientConfigFromConfigFile(cf *client.ClientConfigFile) (res *client.ClientConfig, err error) {
f := client.BindAllEnv
_, err = loaderutils.LoadConfigFromViper(f, cf)
if err != nil {
return nil, fmt.Errorf("could not load config from viper: %w", err)
}
// if token is empty, throw an error
if cf.Token == "" {
return nil, fmt.Errorf("API token is required. Set it via the HATCHET_CLIENT_TOKEN environment variable.")
@@ -0,0 +1,300 @@
-- CreateEnum
CREATE TYPE "VcsProvider" AS ENUM ('GITHUB');
-- AlterTable
ALTER TABLE "StepRun" ADD COLUMN "callerFiles" JSONB,
ADD COLUMN "gitRepoBranch" TEXT;
-- AlterTable
ALTER TABLE "WorkflowRun" ADD COLUMN "gitRepoBranch" TEXT;
-- CreateTable
CREATE TABLE "WorkflowDeploymentConfig" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"workflowId" UUID NOT NULL,
"gitRepoName" TEXT NOT NULL,
"gitRepoOwner" TEXT NOT NULL,
"gitRepoBranch" TEXT NOT NULL,
"githubAppInstallationId" UUID,
CONSTRAINT "WorkflowDeploymentConfig_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StepRunResultArchive" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"stepRunId" UUID NOT NULL,
"order" SMALLSERIAL NOT NULL,
"input" JSONB,
"output" JSONB,
"error" TEXT,
"startedAt" TIMESTAMP(3),
"finishedAt" TIMESTAMP(3),
"timeoutAt" TIMESTAMP(3),
"cancelledAt" TIMESTAMP(3),
"cancelledReason" TEXT,
"cancelledError" TEXT,
CONSTRAINT "StepRunResultArchive_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TenantVcsProvider" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"vcsProvider" "VcsProvider" NOT NULL,
"config" JSONB,
CONSTRAINT "TenantVcsProvider_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubAppInstallation" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"githubAppOAuthId" UUID NOT NULL,
"installationId" INTEGER NOT NULL,
"accountName" TEXT NOT NULL,
"accountId" INTEGER NOT NULL,
"accountAvatarURL" TEXT,
"installationSettingsURL" TEXT,
"config" JSONB,
"tenantId" UUID,
"tenantVcsProviderId" UUID,
CONSTRAINT "GithubAppInstallation_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubAppOAuth" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"githubUserID" INTEGER NOT NULL,
"accessToken" BYTEA NOT NULL,
"refreshToken" BYTEA,
"expiresAt" TIMESTAMP(3),
CONSTRAINT "GithubAppOAuth_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubPullRequest" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"repositoryOwner" TEXT NOT NULL,
"repositoryName" TEXT NOT NULL,
"pullRequestID" INTEGER NOT NULL,
"pullRequestTitle" TEXT NOT NULL,
"pullRequestNumber" INTEGER NOT NULL,
"pullRequestHeadBranch" TEXT NOT NULL,
"pullRequestBaseBranch" TEXT NOT NULL,
"pullRequestState" TEXT NOT NULL,
CONSTRAINT "GithubPullRequest_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubPullRequestComment" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"pullRequestID" UUID NOT NULL,
"moduleID" TEXT NOT NULL,
"commentID" INTEGER NOT NULL,
CONSTRAINT "GithubPullRequestComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubWebhook" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"tenantId" UUID NOT NULL,
"repositoryOwner" TEXT NOT NULL,
"repositoryName" TEXT NOT NULL,
"signingSecret" BYTEA NOT NULL,
CONSTRAINT "GithubWebhook_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_GithubAppInstallationToGithubWebhook" (
"A" UUID NOT NULL,
"B" UUID NOT NULL
);
-- CreateTable
CREATE TABLE "_GithubAppOAuthToUser" (
"A" UUID NOT NULL,
"B" UUID NOT NULL
);
-- CreateTable
CREATE TABLE "_GithubPullRequestToWorkflowRun" (
"A" UUID NOT NULL,
"B" UUID NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "WorkflowDeploymentConfig_id_key" ON "WorkflowDeploymentConfig"("id");
-- CreateIndex
CREATE UNIQUE INDEX "WorkflowDeploymentConfig_workflowId_key" ON "WorkflowDeploymentConfig"("workflowId");
-- CreateIndex
CREATE UNIQUE INDEX "StepRunResultArchive_id_key" ON "StepRunResultArchive"("id");
-- CreateIndex
CREATE UNIQUE INDEX "TenantVcsProvider_id_key" ON "TenantVcsProvider"("id");
-- CreateIndex
CREATE UNIQUE INDEX "TenantVcsProvider_tenantId_vcsProvider_key" ON "TenantVcsProvider"("tenantId", "vcsProvider");
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppInstallation_id_key" ON "GithubAppInstallation"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppInstallation_installationId_accountId_key" ON "GithubAppInstallation"("installationId", "accountId");
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppOAuth_id_key" ON "GithubAppOAuth"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GithubAppOAuth_githubUserID_key" ON "GithubAppOAuth"("githubUserID");
-- CreateIndex
CREATE UNIQUE INDEX "GithubPullRequest_id_key" ON "GithubPullRequest"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GithubPullRequest_tenantId_repositoryOwner_repositoryName_p_key" ON "GithubPullRequest"("tenantId", "repositoryOwner", "repositoryName", "pullRequestNumber");
-- CreateIndex
CREATE UNIQUE INDEX "GithubPullRequestComment_id_key" ON "GithubPullRequestComment"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GithubWebhook_id_key" ON "GithubWebhook"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GithubWebhook_tenantId_repositoryOwner_repositoryName_key" ON "GithubWebhook"("tenantId", "repositoryOwner", "repositoryName");
-- CreateIndex
CREATE UNIQUE INDEX "_GithubAppInstallationToGithubWebhook_AB_unique" ON "_GithubAppInstallationToGithubWebhook"("A", "B");
-- CreateIndex
CREATE INDEX "_GithubAppInstallationToGithubWebhook_B_index" ON "_GithubAppInstallationToGithubWebhook"("B");
-- CreateIndex
CREATE UNIQUE INDEX "_GithubAppOAuthToUser_AB_unique" ON "_GithubAppOAuthToUser"("A", "B");
-- CreateIndex
CREATE INDEX "_GithubAppOAuthToUser_B_index" ON "_GithubAppOAuthToUser"("B");
-- CreateIndex
CREATE UNIQUE INDEX "_GithubPullRequestToWorkflowRun_AB_unique" ON "_GithubPullRequestToWorkflowRun"("A", "B");
-- CreateIndex
CREATE INDEX "_GithubPullRequestToWorkflowRun_B_index" ON "_GithubPullRequestToWorkflowRun"("B");
-- AddForeignKey
ALTER TABLE "WorkflowDeploymentConfig" ADD CONSTRAINT "WorkflowDeploymentConfig_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WorkflowDeploymentConfig" ADD CONSTRAINT "WorkflowDeploymentConfig_githubAppInstallationId_fkey" FOREIGN KEY ("githubAppInstallationId") REFERENCES "GithubAppInstallation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "StepRunResultArchive" ADD CONSTRAINT "StepRunResultArchive_stepRunId_fkey" FOREIGN KEY ("stepRunId") REFERENCES "StepRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TenantVcsProvider" ADD CONSTRAINT "TenantVcsProvider_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_githubAppOAuthId_fkey" FOREIGN KEY ("githubAppOAuthId") REFERENCES "GithubAppOAuth"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_tenantVcsProviderId_fkey" FOREIGN KEY ("tenantVcsProviderId") REFERENCES "TenantVcsProvider"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubPullRequest" ADD CONSTRAINT "GithubPullRequest_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubPullRequestComment" ADD CONSTRAINT "GithubPullRequestComment_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubPullRequestComment" ADD CONSTRAINT "GithubPullRequestComment_pullRequestID_fkey" FOREIGN KEY ("pullRequestID") REFERENCES "GithubPullRequest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GithubWebhook" ADD CONSTRAINT "GithubWebhook_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppInstallationToGithubWebhook" ADD CONSTRAINT "_GithubAppInstallationToGithubWebhook_A_fkey" FOREIGN KEY ("A") REFERENCES "GithubAppInstallation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppInstallationToGithubWebhook" ADD CONSTRAINT "_GithubAppInstallationToGithubWebhook_B_fkey" FOREIGN KEY ("B") REFERENCES "GithubWebhook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppOAuthToUser" ADD CONSTRAINT "_GithubAppOAuthToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "GithubAppOAuth"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubAppOAuthToUser" ADD CONSTRAINT "_GithubAppOAuthToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubPullRequestToWorkflowRun" ADD CONSTRAINT "_GithubPullRequestToWorkflowRun_A_fkey" FOREIGN KEY ("A") REFERENCES "GithubPullRequest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_GithubPullRequestToWorkflowRun" ADD CONSTRAINT "_GithubPullRequestToWorkflowRun_B_fkey" FOREIGN KEY ("B") REFERENCES "WorkflowRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
INSERT INTO
"Tenant" (
"id",
"createdAt",
"updatedAt",
"deletedAt",
"name",
"slug"
)
VALUES
(
'8d420720-ef03-41dc-9c73-1c93f276db97',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
'internal',
'internal'
) ON CONFLICT DO NOTHING;
CREATE OR REPLACE FUNCTION prevent_internal_name_or_slug()
RETURNS trigger AS $$
BEGIN
IF NEW."name" = 'internal' OR NEW."slug" = 'internal' THEN
RAISE EXCEPTION 'Values "internal" for "name" or "slug" are not allowed.';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_name_or_slug_before_insert_or_update
BEFORE INSERT OR UPDATE ON "Tenant"
FOR EACH ROW EXECUTE FUNCTION prevent_internal_name_or_slug();
+274 -20
View File
@@ -33,7 +33,8 @@ model User {
// the user sessions
sessions UserSession[]
memberships TenantMember[]
memberships TenantMember[]
githubAppOAuths GithubAppOAuth[]
}
model UserOAuth {
@@ -101,24 +102,29 @@ model Tenant {
name String
slug String @unique
events Event[]
workflows Workflow[]
jobs Job[]
steps Step[]
triggers WorkflowTriggers[]
workflowRuns WorkflowRun[]
workflowRunTriggers WorkflowRunTriggeredBy[]
jobRuns JobRun[]
jobRunLookupDatas JobRunLookupData[]
stepRuns StepRun[]
workers Worker[]
members TenantMember[]
workflowTags WorkflowTag[]
actions Action[]
services Service[]
invites TenantInviteLink[]
apiTokens APIToken[]
groupKeyRuns GetGroupKeyRun[]
events Event[]
workflows Workflow[]
jobs Job[]
steps Step[]
triggers WorkflowTriggers[]
workflowRuns WorkflowRun[]
workflowRunTriggers WorkflowRunTriggeredBy[]
jobRuns JobRun[]
jobRunLookupDatas JobRunLookupData[]
stepRuns StepRun[]
workers Worker[]
members TenantMember[]
workflowTags WorkflowTag[]
actions Action[]
services Service[]
invites TenantInviteLink[]
apiTokens APIToken[]
groupKeyRuns GetGroupKeyRun[]
vcsProviders TenantVcsProvider[]
githubAppInstallations GithubAppInstallation[]
githubPullRequests GithubPullRequest[]
githubPullRequestComments GithubPullRequestComment[]
githubWebhooks GithubWebhook[]
}
enum TenantMemberRole {
@@ -263,12 +269,33 @@ model Workflow {
versions WorkflowVersion[]
// the tags for this workflow
tags WorkflowTag[]
tags WorkflowTag[]
deploymentConfig WorkflowDeploymentConfig?
// workflow names are unique per tenant
@@unique([tenantId, name])
}
model WorkflowDeploymentConfig {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the parent workflow
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade)
workflowId String @unique @db.Uuid
gitRepoName String
gitRepoOwner String
gitRepoBranch String
// Github-related deployment config
githubAppInstallation GithubAppInstallation? @relation(fields: [githubAppInstallationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
githubAppInstallationId String? @db.Uuid
}
model WorkflowVersion {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
@@ -551,6 +578,11 @@ model WorkflowRun {
// the run finished at
finishedAt DateTime?
// (optional) the branch for the github repo
gitRepoBranch String?
pullRequests GithubPullRequest[]
}
model GetGroupKeyRun {
@@ -809,6 +841,54 @@ model StepRun {
// errors while cancelling the run
cancelledError String?
// a map of override values to caller files for the step run
callerFiles Json?
// the github branch that this is running on
gitRepoBranch String?
archivedResults StepRunResultArchive[]
}
model StepRunResultArchive {
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the parent step run
stepRun StepRun @relation(fields: [stepRunId], references: [id], onDelete: Cascade, onUpdate: Cascade)
stepRunId String @db.Uuid
order Int @default(autoincrement()) @db.SmallInt
// the run input
input Json?
// the run output
output Json?
// the run error
error String?
// the run started at
startedAt DateTime?
// the run finished at
finishedAt DateTime?
// the run timeout at
timeoutAt DateTime?
// the run cancelled at
cancelledAt DateTime?
// the reason for why the run was cancelled
cancelledReason String?
// errors while cancelling the run
cancelledError String?
}
model Dispatcher {
@@ -909,3 +989,177 @@ model Service {
@@unique([tenantId, name])
}
enum VcsProvider {
GITHUB
}
model TenantVcsProvider {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the parent tenant
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade)
tenantId String @db.Uuid
vcsProvider VcsProvider
// the provider name
ghInstallations GithubAppInstallation[]
// the provider's configuration
config Json?
@@unique([tenantId, vcsProvider])
}
model GithubAppInstallation {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the oauth id for the user that linked this installation
githubAppOAuth GithubAppOAuth @relation(fields: [githubAppOAuthId], references: [id], onDelete: Cascade, onUpdate: Cascade)
githubAppOAuthId String @db.Uuid
// the installation id
installationId Int
accountName String
accountId Int
// optionals
accountAvatarURL String?
installationSettingsURL String?
// the installation's configuration
config Json?
webhooks GithubWebhook[]
deploymentConfigs WorkflowDeploymentConfig[]
Tenant Tenant? @relation(fields: [tenantId], references: [id])
tenantId String? @db.Uuid
TenantVcsProvider TenantVcsProvider? @relation(fields: [tenantVcsProviderId], references: [id])
tenantVcsProviderId String? @db.Uuid
@@unique([installationId, accountId])
}
model GithubAppOAuth {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the oauth provider's user id
githubUserID Int @unique
// a list of users this github account is linked to
users User[]
// a list of installations this github account is linked to
installations GithubAppInstallation[]
// the oauth provider's access token
accessToken Bytes @db.ByteA
// the oauth provider's refresh token
refreshToken Bytes? @db.ByteA
// the oauth provider's expiry time
expiresAt DateTime?
}
model GithubPullRequest {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the parent tenant
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade)
tenantId String @db.Uuid
// the repository owner
repositoryOwner String
// the repository name
repositoryName String
// the pull request id
pullRequestID Int
// the pull request title
pullRequestTitle String
// the pull request number
pullRequestNumber Int
// the pull request head branch
pullRequestHeadBranch String
// the pull request base branch
pullRequestBaseBranch String
// the pull request state
pullRequestState String
// the pull request comments
pullRequestComments GithubPullRequestComment[]
workflowRuns WorkflowRun[]
@@unique([tenantId, repositoryOwner, repositoryName, pullRequestNumber])
}
model GithubPullRequestComment {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the parent tenant
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade)
tenantId String @db.Uuid
pullRequest GithubPullRequest @relation(fields: [pullRequestID], references: [id], onDelete: Cascade, onUpdate: Cascade)
pullRequestID String @db.Uuid
// the module id
moduleID String
// the comment id
commentID Int
}
model GithubWebhook {
// base fields
id String @id @unique @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
deletedAt DateTime?
// the parent tenant
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade)
tenantId String @db.Uuid
// the repository owner
repositoryOwner String
// the repository name
repositoryName String
// the webhook signing secret
signingSecret Bytes @db.ByteA
// the webhook's installations
installations GithubAppInstallation[]
@@unique([tenantId, repositoryOwner, repositoryName])
}
+2
View File
@@ -12,6 +12,8 @@ class MyWorkflow:
@hatchet.step()
def step1(self, context : Context):
context.overrides("test", "test")
print("executed step1", context.workflow_input())
return {
"step1": "step1",
+11 -1
View File
@@ -1,9 +1,16 @@
import inspect
from multiprocessing import Event
import os
from .clients.dispatcher import Action, DispatcherClient
from .dispatcher_pb2 import OverridesData
from .logger import logger
import json
def get_caller_file_path():
caller_frame = inspect.stack()[2]
return caller_frame.filename
class Context:
def __init__(self, action: Action, client: DispatcherClient):
self.data = json.loads(action.action_payload)
@@ -46,11 +53,14 @@ class Context:
if name in self.overrides_data:
return self.overrides_data[name]
caller_file = get_caller_file_path()
self.client.put_overrides_data(
OverridesData(
stepRunId=self.stepRunId,
path=name,
value=json.dumps(default)
value=json.dumps(default),
callerFilename=caller_file
)
)
+18 -20
View File
@@ -15,9 +15,7 @@ _sym_db = _symbol_database.Default()
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64ispatcher.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"N\n\x15WorkerRegisterRequest\x12\x12\n\nworkerName\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x63tions\x18\x02 \x03(\t\x12\x10\n\x08services\x18\x03 \x03(\t\"P\n\x16WorkerRegisterResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\x12\x12\n\nworkerName\x18\x03 \x01(\t\"\xf2\x01\n\x0e\x41ssignedAction\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x15\n\rworkflowRunId\x18\x02 \x01(\t\x12\x18\n\x10getGroupKeyRunId\x18\x03 \x01(\t\x12\r\n\x05jobId\x18\x04 \x01(\t\x12\x0f\n\x07jobName\x18\x05 \x01(\t\x12\x10\n\x08jobRunId\x18\x06 \x01(\t\x12\x0e\n\x06stepId\x18\x07 \x01(\t\x12\x11\n\tstepRunId\x18\x08 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\t \x01(\t\x12\x1f\n\nactionType\x18\n \x01(\x0e\x32\x0b.ActionType\x12\x15\n\ractionPayload\x18\x0b \x01(\t\"\'\n\x13WorkerListenRequest\x12\x10\n\x08workerId\x18\x01 \x01(\t\",\n\x18WorkerUnsubscribeRequest\x12\x10\n\x08workerId\x18\x01 \x01(\t\"?\n\x19WorkerUnsubscribeResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"\xe1\x01\n\x13GroupKeyActionEvent\x12\x10\n\x08workerId\x18\x01 \x01(\t\x12\x15\n\rworkflowRunId\x18\x02 \x01(\t\x12\x18\n\x10getGroupKeyRunId\x18\x03 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x04 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\teventType\x18\x06 \x01(\x0e\x32\x18.GroupKeyActionEventType\x12\x14\n\x0c\x65ventPayload\x18\x07 \x01(\t\"\xec\x01\n\x0fStepActionEvent\x12\x10\n\x08workerId\x18\x01 \x01(\t\x12\r\n\x05jobId\x18\x02 \x01(\t\x12\x10\n\x08jobRunId\x18\x03 \x01(\t\x12\x0e\n\x06stepId\x18\x04 \x01(\t\x12\x11\n\tstepRunId\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x06 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\'\n\teventType\x18\x08 \x01(\x0e\x32\x14.StepActionEventType\x12\x14\n\x0c\x65ventPayload\x18\t \x01(\t\"9\n\x13\x41\x63tionEventResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"9\n SubscribeToWorkflowEventsRequest\x12\x15\n\rworkflowRunId\x18\x01 \x01(\t\"\xd0\x01\n\rWorkflowEvent\x12\x15\n\rworkflowRunId\x18\x01 \x01(\t\x12#\n\x0cresourceType\x18\x02 \x01(\x0e\x32\r.ResourceType\x12%\n\teventType\x18\x03 \x01(\x0e\x32\x12.ResourceEventType\x12\x12\n\nresourceId\x18\x04 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0c\x65ventPayload\x18\x06 \x01(\t\"?\n\rOverridesData\x12\x11\n\tstepRunId\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\"\x17\n\x15OverridesDataResponse*N\n\nActionType\x12\x12\n\x0eSTART_STEP_RUN\x10\x00\x12\x13\n\x0f\x43\x41NCEL_STEP_RUN\x10\x01\x12\x17\n\x13START_GET_GROUP_KEY\x10\x02*\xa2\x01\n\x17GroupKeyActionEventType\x12 \n\x1cGROUP_KEY_EVENT_TYPE_UNKNOWN\x10\x00\x12 \n\x1cGROUP_KEY_EVENT_TYPE_STARTED\x10\x01\x12\"\n\x1eGROUP_KEY_EVENT_TYPE_COMPLETED\x10\x02\x12\x1f\n\x1bGROUP_KEY_EVENT_TYPE_FAILED\x10\x03*\x8a\x01\n\x13StepActionEventType\x12\x1b\n\x17STEP_EVENT_TYPE_UNKNOWN\x10\x00\x12\x1b\n\x17STEP_EVENT_TYPE_STARTED\x10\x01\x12\x1d\n\x19STEP_EVENT_TYPE_COMPLETED\x10\x02\x12\x1a\n\x16STEP_EVENT_TYPE_FAILED\x10\x03*e\n\x0cResourceType\x12\x19\n\x15RESOURCE_TYPE_UNKNOWN\x10\x00\x12\x1a\n\x16RESOURCE_TYPE_STEP_RUN\x10\x01\x12\x1e\n\x1aRESOURCE_TYPE_WORKFLOW_RUN\x10\x02*\xde\x01\n\x11ResourceEventType\x12\x1f\n\x1bRESOURCE_EVENT_TYPE_UNKNOWN\x10\x00\x12\x1f\n\x1bRESOURCE_EVENT_TYPE_STARTED\x10\x01\x12!\n\x1dRESOURCE_EVENT_TYPE_COMPLETED\x10\x02\x12\x1e\n\x1aRESOURCE_EVENT_TYPE_FAILED\x10\x03\x12!\n\x1dRESOURCE_EVENT_TYPE_CANCELLED\x10\x04\x12!\n\x1dRESOURCE_EVENT_TYPE_TIMED_OUT\x10\x05\x32\xe4\x03\n\nDispatcher\x12=\n\x08Register\x12\x16.WorkerRegisterRequest\x1a\x17.WorkerRegisterResponse\"\x00\x12\x33\n\x06Listen\x12\x14.WorkerListenRequest\x1a\x0f.AssignedAction\"\x00\x30\x01\x12R\n\x19SubscribeToWorkflowEvents\x12!.SubscribeToWorkflowEventsRequest\x1a\x0e.WorkflowEvent\"\x00\x30\x01\x12?\n\x13SendStepActionEvent\x12\x10.StepActionEvent\x1a\x14.ActionEventResponse\"\x00\x12G\n\x17SendGroupKeyActionEvent\x12\x14.GroupKeyActionEvent\x1a\x14.ActionEventResponse\"\x00\x12<\n\x10PutOverridesData\x12\x0e.OverridesData\x1a\x16.OverridesDataResponse\"\x00\x12\x46\n\x0bUnsubscribe\x12\x19.WorkerUnsubscribeRequest\x1a\x1a.WorkerUnsubscribeResponse\"\x00\x42GZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contractsb\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64ispatcher.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"N\n\x15WorkerRegisterRequest\x12\x12\n\nworkerName\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x63tions\x18\x02 \x03(\t\x12\x10\n\x08services\x18\x03 \x03(\t\"P\n\x16WorkerRegisterResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\x12\x12\n\nworkerName\x18\x03 \x01(\t\"\xf2\x01\n\x0e\x41ssignedAction\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x15\n\rworkflowRunId\x18\x02 \x01(\t\x12\x18\n\x10getGroupKeyRunId\x18\x03 \x01(\t\x12\r\n\x05jobId\x18\x04 \x01(\t\x12\x0f\n\x07jobName\x18\x05 \x01(\t\x12\x10\n\x08jobRunId\x18\x06 \x01(\t\x12\x0e\n\x06stepId\x18\x07 \x01(\t\x12\x11\n\tstepRunId\x18\x08 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\t \x01(\t\x12\x1f\n\nactionType\x18\n \x01(\x0e\x32\x0b.ActionType\x12\x15\n\ractionPayload\x18\x0b \x01(\t\"\'\n\x13WorkerListenRequest\x12\x10\n\x08workerId\x18\x01 \x01(\t\",\n\x18WorkerUnsubscribeRequest\x12\x10\n\x08workerId\x18\x01 \x01(\t\"?\n\x19WorkerUnsubscribeResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"\xe1\x01\n\x13GroupKeyActionEvent\x12\x10\n\x08workerId\x18\x01 \x01(\t\x12\x15\n\rworkflowRunId\x18\x02 \x01(\t\x12\x18\n\x10getGroupKeyRunId\x18\x03 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x04 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\teventType\x18\x06 \x01(\x0e\x32\x18.GroupKeyActionEventType\x12\x14\n\x0c\x65ventPayload\x18\x07 \x01(\t\"\xec\x01\n\x0fStepActionEvent\x12\x10\n\x08workerId\x18\x01 \x01(\t\x12\r\n\x05jobId\x18\x02 \x01(\t\x12\x10\n\x08jobRunId\x18\x03 \x01(\t\x12\x0e\n\x06stepId\x18\x04 \x01(\t\x12\x11\n\tstepRunId\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x06 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\'\n\teventType\x18\x08 \x01(\x0e\x32\x14.StepActionEventType\x12\x14\n\x0c\x65ventPayload\x18\t \x01(\t\"9\n\x13\x41\x63tionEventResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"9\n SubscribeToWorkflowEventsRequest\x12\x15\n\rworkflowRunId\x18\x01 \x01(\t\"\xe0\x01\n\rWorkflowEvent\x12\x15\n\rworkflowRunId\x18\x01 \x01(\t\x12#\n\x0cresourceType\x18\x02 \x01(\x0e\x32\r.ResourceType\x12%\n\teventType\x18\x03 \x01(\x0e\x32\x12.ResourceEventType\x12\x12\n\nresourceId\x18\x04 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0c\x65ventPayload\x18\x06 \x01(\t\x12\x0e\n\x06hangup\x18\x07 \x01(\x08\"W\n\rOverridesData\x12\x11\n\tstepRunId\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\x16\n\x0e\x63\x61llerFilename\x18\x04 \x01(\t\"\x17\n\x15OverridesDataResponse*N\n\nActionType\x12\x12\n\x0eSTART_STEP_RUN\x10\x00\x12\x13\n\x0f\x43\x41NCEL_STEP_RUN\x10\x01\x12\x17\n\x13START_GET_GROUP_KEY\x10\x02*\xa2\x01\n\x17GroupKeyActionEventType\x12 \n\x1cGROUP_KEY_EVENT_TYPE_UNKNOWN\x10\x00\x12 \n\x1cGROUP_KEY_EVENT_TYPE_STARTED\x10\x01\x12\"\n\x1eGROUP_KEY_EVENT_TYPE_COMPLETED\x10\x02\x12\x1f\n\x1bGROUP_KEY_EVENT_TYPE_FAILED\x10\x03*\x8a\x01\n\x13StepActionEventType\x12\x1b\n\x17STEP_EVENT_TYPE_UNKNOWN\x10\x00\x12\x1b\n\x17STEP_EVENT_TYPE_STARTED\x10\x01\x12\x1d\n\x19STEP_EVENT_TYPE_COMPLETED\x10\x02\x12\x1a\n\x16STEP_EVENT_TYPE_FAILED\x10\x03*e\n\x0cResourceType\x12\x19\n\x15RESOURCE_TYPE_UNKNOWN\x10\x00\x12\x1a\n\x16RESOURCE_TYPE_STEP_RUN\x10\x01\x12\x1e\n\x1aRESOURCE_TYPE_WORKFLOW_RUN\x10\x02*\xde\x01\n\x11ResourceEventType\x12\x1f\n\x1bRESOURCE_EVENT_TYPE_UNKNOWN\x10\x00\x12\x1f\n\x1bRESOURCE_EVENT_TYPE_STARTED\x10\x01\x12!\n\x1dRESOURCE_EVENT_TYPE_COMPLETED\x10\x02\x12\x1e\n\x1aRESOURCE_EVENT_TYPE_FAILED\x10\x03\x12!\n\x1dRESOURCE_EVENT_TYPE_CANCELLED\x10\x04\x12!\n\x1dRESOURCE_EVENT_TYPE_TIMED_OUT\x10\x05\x32\xe4\x03\n\nDispatcher\x12=\n\x08Register\x12\x16.WorkerRegisterRequest\x1a\x17.WorkerRegisterResponse\"\x00\x12\x33\n\x06Listen\x12\x14.WorkerListenRequest\x1a\x0f.AssignedAction\"\x00\x30\x01\x12R\n\x19SubscribeToWorkflowEvents\x12!.SubscribeToWorkflowEventsRequest\x1a\x0e.WorkflowEvent\"\x00\x30\x01\x12?\n\x13SendStepActionEvent\x12\x10.StepActionEvent\x1a\x14.ActionEventResponse\"\x00\x12G\n\x17SendGroupKeyActionEvent\x12\x14.GroupKeyActionEvent\x1a\x14.ActionEventResponse\"\x00\x12<\n\x10PutOverridesData\x12\x0e.OverridesData\x1a\x16.OverridesDataResponse\"\x00\x12\x46\n\x0bUnsubscribe\x12\x19.WorkerUnsubscribeRequest\x1a\x1a.WorkerUnsubscribeResponse\"\x00\x42GZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contractsb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -25,16 +23,16 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dispatcher_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
_globals['DESCRIPTOR']._options = None
_globals['DESCRIPTOR']._serialized_options = b'ZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contracts'
_globals['_ACTIONTYPE']._serialized_start=1498
_globals['_ACTIONTYPE']._serialized_end=1576
_globals['_GROUPKEYACTIONEVENTTYPE']._serialized_start=1579
_globals['_GROUPKEYACTIONEVENTTYPE']._serialized_end=1741
_globals['_STEPACTIONEVENTTYPE']._serialized_start=1744
_globals['_STEPACTIONEVENTTYPE']._serialized_end=1882
_globals['_RESOURCETYPE']._serialized_start=1884
_globals['_RESOURCETYPE']._serialized_end=1985
_globals['_RESOURCEEVENTTYPE']._serialized_start=1988
_globals['_RESOURCEEVENTTYPE']._serialized_end=2210
_globals['_ACTIONTYPE']._serialized_start=1538
_globals['_ACTIONTYPE']._serialized_end=1616
_globals['_GROUPKEYACTIONEVENTTYPE']._serialized_start=1619
_globals['_GROUPKEYACTIONEVENTTYPE']._serialized_end=1781
_globals['_STEPACTIONEVENTTYPE']._serialized_start=1784
_globals['_STEPACTIONEVENTTYPE']._serialized_end=1922
_globals['_RESOURCETYPE']._serialized_start=1924
_globals['_RESOURCETYPE']._serialized_end=2025
_globals['_RESOURCEEVENTTYPE']._serialized_start=2028
_globals['_RESOURCEEVENTTYPE']._serialized_end=2250
_globals['_WORKERREGISTERREQUEST']._serialized_start=53
_globals['_WORKERREGISTERREQUEST']._serialized_end=131
_globals['_WORKERREGISTERRESPONSE']._serialized_start=133
@@ -56,11 +54,11 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_globals['_SUBSCRIBETOWORKFLOWEVENTSREQUEST']._serialized_start=1138
_globals['_SUBSCRIBETOWORKFLOWEVENTSREQUEST']._serialized_end=1195
_globals['_WORKFLOWEVENT']._serialized_start=1198
_globals['_WORKFLOWEVENT']._serialized_end=1406
_globals['_OVERRIDESDATA']._serialized_start=1408
_globals['_OVERRIDESDATA']._serialized_end=1471
_globals['_OVERRIDESDATARESPONSE']._serialized_start=1473
_globals['_OVERRIDESDATARESPONSE']._serialized_end=1496
_globals['_DISPATCHER']._serialized_start=2213
_globals['_DISPATCHER']._serialized_end=2697
_globals['_WORKFLOWEVENT']._serialized_end=1422
_globals['_OVERRIDESDATA']._serialized_start=1424
_globals['_OVERRIDESDATA']._serialized_end=1511
_globals['_OVERRIDESDATARESPONSE']._serialized_start=1513
_globals['_OVERRIDESDATARESPONSE']._serialized_end=1536
_globals['_DISPATCHER']._serialized_start=2253
_globals['_DISPATCHER']._serialized_end=2737
# @@protoc_insertion_point(module_scope)
+6 -3
View File
@@ -197,17 +197,20 @@ class WorkflowEvent(_message.Message):
resourceId: str
eventTimestamp: _timestamp_pb2.Timestamp
eventPayload: str
def __init__(self, workflowRunId: _Optional[str] = ..., resourceType: _Optional[_Union[ResourceType, str]] = ..., eventType: _Optional[_Union[ResourceEventType, str]] = ..., resourceId: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., eventPayload: _Optional[str] = ...) -> None: ...
hangup: bool
def __init__(self, workflowRunId: _Optional[str] = ..., resourceType: _Optional[_Union[ResourceType, str]] = ..., eventType: _Optional[_Union[ResourceEventType, str]] = ..., resourceId: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., eventPayload: _Optional[str] = ..., hangup: bool = ...) -> None: ...
class OverridesData(_message.Message):
__slots__ = ("stepRunId", "path", "value")
__slots__ = ("stepRunId", "path", "value", "callerFilename")
STEPRUNID_FIELD_NUMBER: _ClassVar[int]
PATH_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
CALLERFILENAME_FIELD_NUMBER: _ClassVar[int]
stepRunId: str
path: str
value: str
def __init__(self, stepRunId: _Optional[str] = ..., path: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
callerFilename: str
def __init__(self, stepRunId: _Optional[str] = ..., path: _Optional[str] = ..., value: _Optional[str] = ..., callerFilename: _Optional[str] = ...) -> None: ...
class OverridesDataResponse(_message.Message):
__slots__ = ()
@@ -16,6 +16,7 @@ import {
AcceptInviteRequest,
CreateAPITokenRequest,
CreateAPITokenResponse,
CreatePullRequestFromStepRun,
CreateTenantInviteRequest,
CreateTenantRequest,
EventData,
@@ -25,7 +26,11 @@ import {
EventOrderByDirection,
EventOrderByField,
EventSearch,
LinkGithubRepositoryRequest,
ListAPITokensResponse,
ListGithubAppInstallationsResponse,
ListGithubBranchesResponse,
ListGithubReposResponse,
RejectInviteRequest,
ReplayEventRequest,
RerunStepRunRequest,
@@ -89,11 +94,11 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
* @description Starts the OAuth flow
*
* @tags User
* @name UserUpdateOauthStart
* @name UserUpdateGoogleOauthStart
* @summary Start OAuth flow
* @request GET:/api/v1/users/google/start
*/
userUpdateOauthStart = (params: RequestParams = {}) =>
userUpdateGoogleOauthStart = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/google/start`,
method: 'GET',
@@ -103,16 +108,76 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
* @description Completes the OAuth flow
*
* @tags User
* @name UserUpdateOauthCallback
* @name UserUpdateGoogleOauthCallback
* @summary Complete OAuth flow
* @request GET:/api/v1/users/google/callback
*/
userUpdateOauthCallback = (params: RequestParams = {}) =>
userUpdateGoogleOauthCallback = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/google/callback`,
method: 'GET',
...params,
});
/**
* @description Starts the OAuth flow
*
* @tags User
* @name UserUpdateGithubOauthStart
* @summary Start OAuth flow
* @request GET:/api/v1/users/github/start
* @secure
*/
userUpdateGithubOauthStart = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/github/start`,
method: 'GET',
secure: true,
...params,
});
/**
* @description Completes the OAuth flow
*
* @tags User
* @name UserUpdateGithubOauthCallback
* @summary Complete OAuth flow
* @request GET:/api/v1/users/github/callback
* @secure
*/
userUpdateGithubOauthCallback = (params: RequestParams = {}) =>
this.request<any, void>({
path: `/api/v1/users/github/callback`,
method: 'GET',
secure: true,
...params,
});
/**
* @description Github App global webhook
*
* @tags Github
* @name GithubUpdateGlobalWebhook
* @summary Github app global webhook
* @request POST:/api/v1/github/webhook
*/
githubUpdateGlobalWebhook = (params: RequestParams = {}) =>
this.request<void, APIErrors>({
path: `/api/v1/github/webhook`,
method: 'POST',
...params,
});
/**
* @description Github App tenant webhook
*
* @tags Github
* @name GithubUpdateTenantWebhook
* @summary Github app tenant webhook
* @request POST:/api/v1/github/webhook/{webhook}
*/
githubUpdateTenantWebhook = (webhook: string, params: RequestParams = {}) =>
this.request<void, APIErrors>({
path: `/api/v1/github/webhook/${webhook}`,
method: 'POST',
...params,
});
/**
* @description Gets the current user
*
@@ -624,6 +689,52 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
format: 'json',
...params,
});
/**
* @description Link a github repository to a workflow
*
* @tags Workflow
* @name WorkflowUpdateLinkGithub
* @summary Link github repository
* @request POST:/api/v1/workflows/{workflow}/link-github
* @secure
*/
workflowUpdateLinkGithub = (
workflow: string,
data: LinkGithubRepositoryRequest,
params: RequestParams = {}
) =>
this.request<Workflow, APIErrors>({
path: `/api/v1/workflows/${workflow}/link-github`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
});
/**
* @description Create a pull request for a workflow
*
* @tags Workflow
* @name StepRunUpdateCreatePr
* @summary Create pull request
* @request POST:/api/v1/step-runs/{step-run}/create-pr
* @secure
*/
stepRunUpdateCreatePr = (
stepRun: string,
data: CreatePullRequestFromStepRun,
params: RequestParams = {}
) =>
this.request<CreatePullRequestFromStepRun, APIErrors>({
path: `/api/v1/step-runs/${stepRun}/create-pr`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
});
/**
* @description Get all workflow runs for a tenant
*
@@ -763,4 +874,60 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
format: 'json',
...params,
});
/**
* @description List Github App installations
*
* @tags Github
* @name GithubAppListInstallations
* @summary List Github App installations
* @request GET:/api/v1/github-app/installations
* @secure
*/
githubAppListInstallations = (params: RequestParams = {}) =>
this.request<ListGithubAppInstallationsResponse, APIErrors>({
path: `/api/v1/github-app/installations`,
method: 'GET',
secure: true,
format: 'json',
...params,
});
/**
* @description List Github App repositories
*
* @tags Github
* @name GithubAppListRepos
* @summary List Github App repositories
* @request GET:/api/v1/github-app/installations/{gh-installation}/repos
* @secure
*/
githubAppListRepos = (ghInstallation: string, params: RequestParams = {}) =>
this.request<ListGithubReposResponse, APIErrors>({
path: `/api/v1/github-app/installations/${ghInstallation}/repos`,
method: 'GET',
secure: true,
format: 'json',
...params,
});
/**
* @description List Github App branches
*
* @tags Github
* @name GithubAppListBranches
* @summary List Github App branches
* @request GET:/api/v1/github-app/installations/{gh-installation}/repos/{gh-repo-owner}/{gh-repo-name}/branches
* @secure
*/
githubAppListBranches = (
ghInstallation: string,
ghRepoOwner: string,
ghRepoName: string,
params: RequestParams = {}
) =>
this.request<ListGithubBranchesResponse, APIErrors>({
path: `/api/v1/github-app/installations/${ghInstallation}/repos/${ghRepoOwner}/${ghRepoName}/branches`,
method: 'GET',
secure: true,
format: 'json',
...params,
});
}
@@ -320,6 +320,24 @@ export interface Workflow {
lastRun?: WorkflowRun;
/** The jobs of the workflow. */
jobs?: Job[];
deployment?: WorkflowDeploymentConfig;
}
export interface WorkflowDeploymentConfig {
metadata: APIResourceMeta;
/** The repository name. */
gitRepoName: string;
/** The repository owner. */
gitRepoOwner: string;
/** The repository branch. */
gitRepoBranch: string;
/** The Github App installation. */
githubAppInstallation?: GithubAppInstallation;
/**
* The id of the Github App installation.
* @format uuid
*/
githubAppInstallationId: string;
}
export interface WorkflowVersionMeta {
@@ -579,3 +597,48 @@ export interface RerunStepRunRequest {
export interface TriggerWorkflowRunRequest {
input: object;
}
export interface LinkGithubRepositoryRequest {
/**
* The repository name.
* @minLength 36
* @maxLength 36
*/
installationId: string;
/** The repository name. */
gitRepoName: string;
/** The repository owner. */
gitRepoOwner: string;
/** The repository branch. */
gitRepoBranch: string;
}
export interface GithubBranch {
branch_name: string;
is_default: boolean;
}
export interface GithubRepo {
repo_owner: string;
repo_name: string;
}
export interface GithubAppInstallation {
metadata: APIResourceMeta;
installation_settings_url: string;
account_name: string;
account_avatar_url: string;
}
export interface ListGithubAppInstallationsResponse {
pagination: PaginationResponse;
rows: GithubAppInstallation[];
}
export type ListGithubReposResponse = GithubRepo[];
export type ListGithubBranchesResponse = GithubBranch[];
export interface CreatePullRequestFromStepRun {
branchName: string;
}