mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 21:52:02 +08:00
As we're now using a GitHub app to dispatch workflow requests from Drone to GHA, this patch updates the workflow runner script to handle app authentication.
183 lines
5.2 KiB
Go
183 lines
5.2 KiB
Go
// Copyright 2022 Gravitational, Inc
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package github
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/google/go-github/v41/github"
|
|
"github.com/gravitational/trace"
|
|
)
|
|
|
|
type RunIDSet map[int64]struct{}
|
|
|
|
// Equals performs a deep comparison of the set values
|
|
func (s RunIDSet) Equals(other RunIDSet) bool {
|
|
if len(s) != len(other) {
|
|
return false
|
|
}
|
|
for k := range s {
|
|
if _, present := other[k]; !present {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Insert adds an element to a set. Adding an element multiple times is not
|
|
// considered an error.
|
|
func (s RunIDSet) Insert(runID int64) {
|
|
s[runID] = struct{}{}
|
|
}
|
|
|
|
// NotIn returns the elements that are in `s` but not in `other`
|
|
func (s RunIDSet) NotIn(other RunIDSet) RunIDSet {
|
|
result := make(RunIDSet)
|
|
for k := range s {
|
|
if _, present := other[k]; !present {
|
|
result[k] = struct{}{}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// WorkflowRuns defines the minimal API used to lst and query GitHub action
|
|
// runner workflows and jobs
|
|
type WorkflowRuns interface {
|
|
GetWorkflowRunByID(ctx context.Context, owner, repo string, runID int64) (*github.WorkflowRun, *github.Response, error)
|
|
ListWorkflowRunsByFileName(ctx context.Context, owner, repo, workflowFileName string, opts *github.ListWorkflowRunsOptions) (*github.WorkflowRuns, *github.Response, error)
|
|
}
|
|
|
|
// ListWorkflowRuns returns a set of RunIDs, representing the set of all for
|
|
// workflow runs created since the supplied start time.
|
|
func ListWorkflowRuns(ctx context.Context, actions WorkflowRuns, owner, repo, path, ref string, since time.Time) (RunIDSet, error) {
|
|
listOptions := github.ListWorkflowRunsOptions{
|
|
ListOptions: github.ListOptions{
|
|
PerPage: 100,
|
|
},
|
|
Branch: ref,
|
|
Created: ">" + since.Format(time.RFC3339),
|
|
}
|
|
|
|
runIDs := make(RunIDSet)
|
|
|
|
for {
|
|
runs, resp, err := actions.ListWorkflowRunsByFileName(ctx, owner, repo, path, &listOptions)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "Failed to fetch runs")
|
|
}
|
|
|
|
for _, r := range runs.WorkflowRuns {
|
|
runIDs.Insert(r.GetID())
|
|
}
|
|
|
|
if resp.NextPage == 0 {
|
|
break
|
|
}
|
|
|
|
listOptions.Page = resp.NextPage
|
|
}
|
|
|
|
return runIDs, nil
|
|
}
|
|
|
|
// WorkflowJobLister defines the minimal GitHub client interafce required to
|
|
// list query and compose workflow jobs.
|
|
type WorkflowJobLister interface {
|
|
ListWorkflowJobs(ctx context.Context, owner, repo string, runID int64, opts *github.ListWorkflowJobsOptions) (*github.Jobs, *github.Response, error)
|
|
}
|
|
|
|
// ListWorkflowJobs lists the jobs for a given workflow run in the specified
|
|
// repository.
|
|
func ListWorkflowJobs(ctx context.Context, lister WorkflowJobLister, owner, repo string, runID int64) ([]*github.WorkflowJob, error) {
|
|
listOptions := github.ListWorkflowJobsOptions{}
|
|
result := []*github.WorkflowJob{}
|
|
for {
|
|
jobs, resp, err := lister.ListWorkflowJobs(ctx, owner, repo, runID, &listOptions)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "Failed to fetch workflow jobs")
|
|
}
|
|
|
|
result = append(result, jobs.Jobs...)
|
|
|
|
if resp.NextPage == 0 {
|
|
break
|
|
}
|
|
|
|
listOptions.Page = resp.NextPage
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// WaitForRun blocks until the specified workflow run completes, and returns the overall
|
|
// workflow status.
|
|
func WaitForRun(ctx context.Context, actions WorkflowRuns, owner, repo, path, ref string, runID int64) (string, error) {
|
|
ticker := time.NewTicker(30 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
run, _, err := actions.GetWorkflowRunByID(ctx, owner, repo, runID)
|
|
if err != nil {
|
|
return "", trace.Wrap(err, "Failed polling run")
|
|
}
|
|
|
|
log.Printf("Workflow status: %s", run.GetStatus())
|
|
|
|
if run.GetStatus() == "completed" {
|
|
return run.GetConclusion(), nil
|
|
}
|
|
|
|
case <-ctx.Done():
|
|
return "", ctx.Err()
|
|
}
|
|
}
|
|
}
|
|
|
|
// InstallationLister defines the minimal interface for listing GitHub App Installations
|
|
// via the GitHub API.
|
|
type InstallationLister interface {
|
|
ListInstallations(ctx context.Context, opts *github.ListOptions) ([]*github.Installation, *github.Response, error)
|
|
}
|
|
|
|
// FindAppInstallID finds the ID of an app installation on a given GitHub account.
|
|
// The App ID is inferred by the credentials used by the `lister` to authenticate
|
|
// with the GitHub API
|
|
func FindAppInstallID(ctx context.Context, lister InstallationLister, owner string) (int64, error) {
|
|
listOptions := github.ListOptions{PerPage: 100}
|
|
for {
|
|
installations, response, err := lister.ListInstallations(ctx, &listOptions)
|
|
if err != nil {
|
|
return 0, trace.Wrap(err, "Failed to list installations")
|
|
}
|
|
|
|
for _, inst := range installations {
|
|
if inst.GetAccount().GetLogin() == owner {
|
|
return inst.GetID(), nil
|
|
}
|
|
}
|
|
|
|
if response.NextPage == 0 {
|
|
return 0, trace.NotFound("No such installation found")
|
|
}
|
|
|
|
listOptions.Page = response.NextPage
|
|
}
|
|
}
|