This has been tested with: Installer committing to a branch: Includes secrets Other committing to a branch: Does not include secrets Installer / Other committing to a fork: Does not include secrets (forks track installed apps separately) Installer committing to a PR branch: Includes secrets Other committing to a PR branch: Does not include secrets Lixquid (1): Add support for secrets on owner-created events buildssrht/gql.go | 232 +++++++++++++++++++++++++--------- buildssrht/operations.graphql | 4 +- main.go | 33 ++--- 3 files changed, 191 insertions(+), 78 deletions(-) -- 2.38.5
Unfortunately I've had to disable this feature. See [1] for details. I have a branch with WIP code to re-enable it [2], but not sure when I'll be able to finish it. It assumes personal tokens always have the fixed scope hardcoded in hottub, because there is no way to discover a token's scope at the moment. Adding scope support to meta.sr.ht's OAuth 2.0 token introspection endpoint would fix that. I assume you're using this feature by filling in a personal token manually? [1]: https://git.sr.ht/~emersion/hottub/commit/41fb29afd3f333cb77cf3aee81bad6da1838fb8a [2]: https://git.sr.ht/~emersion/hottub/log/secrets
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~emersion/public-inbox/patches/42428/mbox | git am -3Learn more about email & git
From: Lixquid <lixquid@lixquid.com> This is a stop-gap solution until fully flexible per-repository, per-action configuration is implemented. --- buildssrht/gql.go | 232 +++++++++++++++++++++++++--------- buildssrht/operations.graphql | 4 +- main.go | 33 ++--- 3 files changed, 191 insertions(+), 78 deletions(-) diff --git a/buildssrht/gql.go b/buildssrht/gql.go index 78d6afb..c594a7e 100644 --- a/buildssrht/gql.go @@ -1,11 +1,12 @@ -// Code generated by gqlclientgen - DO NOT EDIT +// Code generated by gqlclientgen - DO NOT EDIT. package buildssrht import ( "context" + "encoding/json" + "fmt" gqlclient "git.sr.ht/~emersion/gqlclient" - "time" ) type AccessKind string @@ -25,8 +26,8 @@ const ( ) type Artifact struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` // Original path in the guest Path string `json:"path"` // Size in bytes @@ -46,6 +47,8 @@ type EmailTrigger struct { InReplyTo *string `json:"inReplyTo,omitempty"` } +func (*EmailTrigger) isTrigger() {} + type EmailTriggerInput struct { To string `json:"to"` Cc *string `json:"cc,omitempty"` @@ -53,24 +56,54 @@ type EmailTriggerInput struct { } type Entity struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Updated gqlclient.Time `json:"updated"` // The canonical name of this entity. For users, this is their username // prefixed with '~'. Additional entity types will be supported in the future. CanonicalName string `json:"canonicalName"` + + // Underlying value of the GraphQL interface + Value EntityValue `json:"-"` +} + +func (base *Entity) UnmarshalJSON(b []byte) error { + type Raw Entity + var data struct { + *Raw + TypeName string `json:"__typename"` + } + data.Raw = (*Raw)(base) + err := json.Unmarshal(b, &data) + if err != nil { + return err + } + switch data.TypeName { + case "User": + base.Value = new(User) + case "": + return nil + default: + return fmt.Errorf("gqlclient: interface Entity: unknown __typename %q", data.TypeName) + } + return json.Unmarshal(b, base.Value) +} + +// EntityValue is one of: User +type EntityValue interface { + isEntity() } type File string type Job struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - Status JobStatus `json:"status"` - Manifest string `json:"manifest"` - Note *string `json:"note,omitempty"` - Tags []*string `json:"tags"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Updated gqlclient.Time `json:"updated"` + Status JobStatus `json:"status"` + Manifest string `json:"manifest"` + Note *string `json:"note,omitempty"` + Tags []*string `json:"tags"` // Name of the build image Image string `json:"image"` // Name of the build runner which picked up this job, or null if the job is @@ -97,12 +130,12 @@ type JobCursor struct { } type JobGroup struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Note *string `json:"note,omitempty"` - Owner *Entity `json:"owner"` - Jobs []*Job `json:"jobs"` - Triggers []*Trigger `json:"triggers"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Note *string `json:"note,omitempty"` + Owner *Entity `json:"owner"` + Jobs []*Job `json:"jobs"` + Triggers []*Trigger `json:"triggers"` } type JobStatus string @@ -126,26 +159,64 @@ type Log struct { } type PGPKey struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Uuid string `json:"uuid"` - Name *string `json:"name,omitempty"` - PrivateKey Binary `json:"privateKey"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Uuid string `json:"uuid"` + Name *string `json:"name,omitempty"` + PrivateKey Binary `json:"privateKey"` } +func (*PGPKey) isSecret() {} + type SSHKey struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Uuid string `json:"uuid"` - Name *string `json:"name,omitempty"` - PrivateKey Binary `json:"privateKey"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Uuid string `json:"uuid"` + Name *string `json:"name,omitempty"` + PrivateKey Binary `json:"privateKey"` } +func (*SSHKey) isSecret() {} + type Secret struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Uuid string `json:"uuid"` - Name *string `json:"name,omitempty"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Uuid string `json:"uuid"` + Name *string `json:"name,omitempty"` + + // Underlying value of the GraphQL interface + Value SecretValue `json:"-"` +} + +func (base *Secret) UnmarshalJSON(b []byte) error { + type Raw Secret + var data struct { + *Raw + TypeName string `json:"__typename"` + } + data.Raw = (*Raw)(base) + err := json.Unmarshal(b, &data) + if err != nil { + return err + } + switch data.TypeName { + case "SSHKey": + base.Value = new(SSHKey) + case "PGPKey": + base.Value = new(PGPKey) + case "SecretFile": + base.Value = new(SecretFile) + case "": + return nil + default: + return fmt.Errorf("gqlclient: interface Secret: unknown __typename %q", data.TypeName) + } + return json.Unmarshal(b, base.Value) +} + +// SecretValue is one of: SSHKey | PGPKey | SecretFile +type SecretValue interface { + isSecret() } // A cursor for enumerating a list of secrets @@ -159,28 +230,30 @@ type SecretCursor struct { } type SecretFile struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Uuid string `json:"uuid"` - Name *string `json:"name,omitempty"` - Path string `json:"path"` - Mode int32 `json:"mode"` - Data Binary `json:"data"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Uuid string `json:"uuid"` + Name *string `json:"name,omitempty"` + Path string `json:"path"` + Mode int32 `json:"mode"` + Data Binary `json:"data"` } +func (*SecretFile) isSecret() {} + type Settings struct { SshUser string `json:"sshUser"` BuildTimeout string `json:"buildTimeout"` } type Task struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - Name string `json:"name"` - Status TaskStatus `json:"status"` - Log *Log `json:"log,omitempty"` - Job *Job `json:"job"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Updated gqlclient.Time `json:"updated"` + Name string `json:"name"` + Status TaskStatus `json:"status"` + Log *Log `json:"log,omitempty"` + Job *Job `json:"job"` } type TaskStatus string @@ -198,6 +271,38 @@ const ( // build manifest, but are similar in functionality. type Trigger struct { Condition TriggerCondition `json:"condition"` + + // Underlying value of the GraphQL interface + Value TriggerValue `json:"-"` +} + +func (base *Trigger) UnmarshalJSON(b []byte) error { + type Raw Trigger + var data struct { + *Raw + TypeName string `json:"__typename"` + } + data.Raw = (*Raw)(base) + err := json.Unmarshal(b, &data) + if err != nil { + return err + } + switch data.TypeName { + case "EmailTrigger": + base.Value = new(EmailTrigger) + case "WebhookTrigger": + base.Value = new(WebhookTrigger) + case "": + return nil + default: + return fmt.Errorf("gqlclient: interface Trigger: unknown __typename %q", data.TypeName) + } + return json.Unmarshal(b, base.Value) +} + +// TriggerValue is one of: EmailTrigger | WebhookTrigger +type TriggerValue interface { + isTrigger() } type TriggerCondition string @@ -223,19 +328,21 @@ const ( ) type User struct { - Id int32 `json:"id"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - CanonicalName string `json:"canonicalName"` - Username string `json:"username"` - Email string `json:"email"` - Url *string `json:"url,omitempty"` - Location *string `json:"location,omitempty"` - Bio *string `json:"bio,omitempty"` + Id int32 `json:"id"` + Created gqlclient.Time `json:"created"` + Updated gqlclient.Time `json:"updated"` + CanonicalName string `json:"canonicalName"` + Username string `json:"username"` + Email string `json:"email"` + Url *string `json:"url,omitempty"` + Location *string `json:"location,omitempty"` + Bio *string `json:"bio,omitempty"` // Jobs submitted by this user. Jobs *JobCursor `json:"jobs"` } +func (*User) isEntity() {} + type Version struct { Major int32 `json:"major"` Minor int32 `json:"minor"` @@ -243,8 +350,8 @@ type Version struct { // If this API version is scheduled for deprecation, this is the date on which // it will stop working; or null if this API version is not scheduled for // deprecation. - DeprecationDate time.Time `json:"deprecationDate,omitempty"` - Settings *Settings `json:"settings"` + DeprecationDate gqlclient.Time `json:"deprecationDate,omitempty"` + Settings *Settings `json:"settings"` } type WebhookTrigger struct { @@ -252,15 +359,18 @@ type WebhookTrigger struct { Url string `json:"url"` } +func (*WebhookTrigger) isTrigger() {} + type WebhookTriggerInput struct { Url string `json:"url"` } -func SubmitJob(client *gqlclient.Client, ctx context.Context, manifest string, tags []string, note *string) (submit *Job, err error) { - op := gqlclient.NewOperation("mutation submitJob ($manifest: String!, $tags: [String!], $note: String) {\n\tsubmit(manifest: $manifest, secrets: false, tags: $tags, note: $note) {\n\t\tid\n\t\towner {\n\t\t\tcanonicalName\n\t\t}\n\t}\n}\n") +func SubmitJob(client *gqlclient.Client, ctx context.Context, manifest string, tags []string, note *string, includeSecrets bool) (submit *Job, err error) { + op := gqlclient.NewOperation("mutation submitJob ($manifest: String!, $tags: [String!], $note: String, $includeSecrets: Boolean!) {\n\tsubmit(manifest: $manifest, secrets: $includeSecrets, tags: $tags, note: $note) {\n\t\tid\n\t\towner {\n\t\t\tcanonicalName\n\t\t}\n\t}\n}\n") op.Var("manifest", manifest) op.Var("tags", tags) op.Var("note", note) + op.Var("includeSecrets", includeSecrets) var respData struct { Submit *Job } diff --git a/buildssrht/operations.graphql b/buildssrht/operations.graphql index 228c9af..216c1fa 100644 --- a/buildssrht/operations.graphql @@ -1,5 +1,5 @@ -mutation submitJob($manifest: String!, $tags: [String!], $note: String) { - submit(manifest: $manifest, secrets: false, tags: $tags, note: $note) { +mutation submitJob($manifest: String!, $tags: [String!], $note: String, $includeSecrets: Boolean!) { + submit(manifest: $manifest, secrets: $includeSecrets, tags: $tags, note: $note) { id owner { canonicalName diff --git a/main.go b/main.go index da71cb0..963abc7 100644 --- a/main.go +++ b/main.go @@ -265,13 +265,14 @@ func main() { } ctx := &checkSuiteContext{ - Context: r.Context(), - gh: newInstallationClient(atr, event.Installation), - srht: createSrhtClient(buildssrhtEndpoint, installation), - baseRepo: event.Repo, - headRepo: event.Repo, - headCommit: event.CheckSuite.HeadCommit, - headSHA: event.CheckSuite.GetHeadSHA(), + Context: r.Context(), + gh: newInstallationClient(atr, event.Installation), + srht: createSrhtClient(buildssrhtEndpoint, installation), + baseRepo: event.Repo, + headRepo: event.Repo, + headCommit: event.CheckSuite.HeadCommit, + headSHA: event.CheckSuite.GetHeadSHA(), + ownerSubmitted: event.Sender.GetLogin() == installation.Owner, } if len(event.CheckSuite.PullRequests) == 1 { ctx.pullRequest = event.CheckSuite.PullRequests[0] @@ -298,13 +299,14 @@ func main() { } ctx := &checkSuiteContext{ - Context: r.Context(), - gh: newInstallationClient(atr, event.Installation), - srht: createSrhtClient(buildssrhtEndpoint, installation), - baseRepo: event.Repo, - headRepo: event.PullRequest.Head.Repo, - headSHA: event.PullRequest.Head.GetSHA(), - pullRequest: event.PullRequest, + Context: r.Context(), + gh: newInstallationClient(atr, event.Installation), + srht: createSrhtClient(buildssrhtEndpoint, installation), + baseRepo: event.Repo, + headRepo: event.PullRequest.Head.Repo, + headSHA: event.PullRequest.Head.GetSHA(), + pullRequest: event.PullRequest, + ownerSubmitted: event.Sender.GetLogin() == installation.Owner, } var repoCommit *github.RepositoryCommit @@ -359,6 +361,7 @@ type checkSuiteContext struct { baseRepo, headRepo *github.Repository headSHA string headCommit *github.Commit + ownerSubmitted bool pullRequest *github.PullRequest // may be nil headBranch string // may be empty @@ -471,7 +474,7 @@ func startJob(ctx *checkSuiteContext, filename string) error { [%v]: %v`, title, shortHash, commit.Author.GetName(), shortHash, commitURL) - job, err := buildssrht.SubmitJob(ctx.srht.GQL, ctx, string(manifestBuf), tags, ¬e) + job, err := buildssrht.SubmitJob(ctx.srht.GQL, ctx, string(manifestBuf), tags, ¬e, ctx.ownerSubmitted) if err != nil { return fmt.Errorf("failed to submit sr.ht job: %v", err) } -- 2.38.5