autoupdate

package
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2024 License: AGPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package autoupdate provides automatic serialized updating of GitHub pull request branches with their base-branch.

The autoupdater can be used in combination with a automerge feature like the one from GitHub and specific branch protection rules to provide a serialized merge-queue. The GitHub branch protection rules must be configured to require >=1 status checks to pass before merging and branches being up to date before merging.

The autoupdater reacts on GitHub webhook-events and interacts with the GitHub API. When a webhook event for an enqueue condition is received, the pull request is enqueued for autoupdates. When all required Status Checks succeed for the PR, it is uptodate with it's base branch and all configured mandatory PR reviewers approved, the auto-merge feature will merge the PR into it's base branch. Per base-branch, the autoupdater updates automatically the first PR in the queue with it's base branch. It serializes updating per basebranch, to avoid a race between multiples pull requests that result in unnecessary CI runs and merges. When updating a pull request branch with it's base-branch is not possible, a failed status check for the pull request was reported, or the PR became stale updates for it are suspended. This prevents that pull requests that can not be merged block the autoupdate fifo-queue. When a webhook event is received about a positive status check report, the base branch or the pull request branch changed, updates will be resumed. The pull request will be enqueued in the fifo list for updates again.

pull requests are removed from the autoupdate queue, when it was closed, or an inverse trigger event (auto_merge_disabled, unlabeled) was received.

Components

The main components are the queue and autoupdater.

The autoupdater manages and coordinates operations on queues. It listens for GitHub Webhook events, creates/removes removes, enqueues/dequeues pull requests for updates and triggers update operations on queues. It can also synchronize the queue with the current state at GitHub, by querying information via the GitHub API. It also provides a minimal webinterface to view the current state.

Queues serialize updates per base-branch. For each base-branch the Autoupdater manages one queue. pull requests in the queue are either in active or suspended state. If they are active, they are queued in FIFO datastructure and update operations can be run on the first element in the queue. If they are suspended they are currently not considered for autoupdates and stored in a separate datastructure.

Index

Constants

View Source
const DefStaleTimeout = 3 * time.Hour

DefStaleTimeout is the default stale timeout. A pull request is considered as stale, when it is the first element in the queue it's state has not changed for longer then this timeout.

Variables

View Source
var (
	ErrAlreadyExists = errors.New("already exist")
	ErrNotFound      = errors.New("not found")
)

Functions

This section is empty.

Types

type Autoupdater

type Autoupdater struct {
	// contains filtered or unexported fields
}

Autoupdater implements processing webhook events, querying the GitHub API and enqueueing/dequeuing/triggering updating pull requests with the base-branch. Pull request branch updates are serialized per base-branch.

func NewAutoupdater

func NewAutoupdater(
	ghClient GithubClient,
	eventChan <-chan *github_prov.Event,
	retryer Retryer,
	monitoredRepositories []Repository,
	triggerOnAutomerge bool,
	triggerLabels []string,
	headLabel string,
	opts ...Opt,
) *Autoupdater

NewAutoupdater creates an Autoupdater instance. Only webhook events for repositories listed in monitoredRepositories are processed. At least one trigger (triggerOnAutomerge or a label in triggerLabels) must be provided to trigger enqueuing pull requests for autoupdates via webhook events. When multiple event triggers are configured, the autoupdater reacts on each received Event individually. headLabel is the name of the GitHub label that is applied to the PR that is the first in the queue.

func (*Autoupdater) ChangeBaseBranch

func (a *Autoupdater) ChangeBaseBranch(
	ctx context.Context,
	oldBaseBranch, newBaseBranch *BaseBranch,
	prNumber int,
) error

ChangeBaseBranch dequeues a Pull Request from the queue oldBaseBranch and enqueues it at the queue for newBaseBranch.

func (*Autoupdater) Dequeue

func (a *Autoupdater) Dequeue(_ context.Context, baseBranch *BaseBranch, prNumber int) (*PullRequest, error)

Dequeue removes the pull request with number prNumber from the autoupdate queue of baseBranch. This disables keeping the pull request update with baseBranch. If no pull request is queued with prNumber a ErrNotFound error is returned.

If the pull request was the only element in the baseBranch queue, the queue is removed.

func (*Autoupdater) Enqueue

func (a *Autoupdater) Enqueue(_ context.Context, baseBranch *BaseBranch, pr *PullRequest) error

Enqueue appends the pull request to the autoupdate queue for baseBranch. When it becomes the first element in the queue, it will be kept uptodate with it's baseBranch. If the pr is already enqueued a ErrAlreadyExists error is returned.

If no queue for the baseBranch exist, it will be created.

func (*Autoupdater) HTTPService

func (a *Autoupdater) HTTPService() *HTTPService

func (*Autoupdater) InitSync added in v0.13.0

func (a *Autoupdater) InitSync(ctx context.Context) error

InitSync does an initial synchronization of the autoupdater queues with the pull request state at GitHub. This is intended to be run before Autoupdater is started. Pull request information is queried from github. If a PR meets a condition to be enqueued for auto-updates it is enqueued. If it meets a condition for not being autoupdated, it is dequeued. If a PR has the [a.headLabel] set it is removed.

func (*Autoupdater) Resume

func (a *Autoupdater) Resume(_ context.Context, baseBranch *BaseBranch, prNumber int) error

Resume resumes updates for a pull request. If the pull request is not queued for updates and in suspended state ErrNotFound is returned.

func (*Autoupdater) ResumeAllForBaseBranch

func (a *Autoupdater) ResumeAllForBaseBranch(_ context.Context, baseBranch *BaseBranch)

ResumeAllForBaseBranch resumes updates for all Pull Requests that are based on baseBranch and for which updates are currently suspended.

func (*Autoupdater) ResumeIfStatusPositive

func (a *Autoupdater) ResumeIfStatusPositive(ctx context.Context, owner, repo string, branchNames []string)

ResumeIfStatusPositive calls ScheduleResumePRIfStatusPositive for all queued PRs of the passed branchNames.

func (*Autoupdater) SetPRStaleSinceIfNewer added in v0.10.0

func (a *Autoupdater) SetPRStaleSinceIfNewer(
	_ context.Context,
	baseBranch *BaseBranch,
	prNumber int,
	updatedAt time.Time,
) error

SetPRStaleSinceIfNewer sets the staleSince timestamp of the PR to updatedAt, if it is newer then the current staleSince timestamp. If the PR is not queued for autoupdates, ErrNotFound is returned.

func (*Autoupdater) SetPRStaleSinceIfNewerByBranch added in v0.10.0

func (a *Autoupdater) SetPRStaleSinceIfNewerByBranch(
	_ context.Context,
	owner, repo string,
	branchNames []string,
	updatedAt time.Time,
) (notFound []string)

SetPRStaleSinceIfNewerByBranch sets the staleSince timestamp of the PRs for the given branch names to updatdAt, if it is newer then the current staleSince timestamp. The method returns a list of branch names for that no queued PR could be found.

func (*Autoupdater) Start

func (a *Autoupdater) Start()

Start starts the event-loop in a go-routine. The event-loop reads events from the eventChan and processes them.

func (*Autoupdater) Stop

func (a *Autoupdater) Stop()

Stop stops the event-loop and waits until it terminates. All queues will be deleted, operations that are in progress will be canceled.

func (*Autoupdater) SuspendUpdates

func (a *Autoupdater) SuspendUpdates(
	_ context.Context,
	owner string,
	repo string,
	branchNames []string,
) ([]*PullRequest, []error)

SuspendUpdates suspend updates for all pull requests that are queued and their branch name matches one of branchNames. It returns a list of pull requests for which updates were suspended.

func (*Autoupdater) TriggerUpdateIfFirst

func (a *Autoupdater) TriggerUpdateIfFirst(
	ctx context.Context,
	baseBranch *BaseBranch,
	prSpec PRSpecifier,
) (*PullRequest, error)

TriggerUpdateIfFirst schedules the update operation for the first pull request in the queue if it matches prSpec. If an update was triggered, the PullRequest is returned. If the first PR does not match prSpec, ErrNotFound is returned.

func (*Autoupdater) TriggerUpdateIfFirstAllQueues

func (a *Autoupdater) TriggerUpdateIfFirstAllQueues(
	ctx context.Context,
	repoOwner string,
	repo string,
	prSpec PRSpecifier,
) (*PullRequest, error)

TriggerUpdateIfFirstAllQueues does the same then _triggerUpdateIfFirst but does not require to specify the base branch name.

func (*Autoupdater) UpdateBranch

func (a *Autoupdater) UpdateBranch(ctx context.Context, baseBranch *BaseBranch) error

UpdateBranch triggers updating the first PR queued for updates for the given baseBranch.

See documentation on queue for more information.

type BaseBranch

type BaseBranch struct {
	BranchID
	Logfields []zap.Field
}

BaseBranch represents a base branch of a pull request

func NewBaseBranch

func NewBaseBranch(owner, repo, branch string) (*BaseBranch, error)

func (*BaseBranch) String

func (b *BaseBranch) String() string

type BranchID

type BranchID struct {
	RepositoryOwner string
	Repository      string
	Branch          string
}

BranchID identifies a github branch uniquely

func (*BranchID) String

func (b *BranchID) String() string

type DryGithubClient

type DryGithubClient struct {
	// contains filtered or unexported fields
}

DryGithubClient is a github-client that does not do any changes on github. All operations that could cause a change are simulated and always succeed. All all other operations are forwarded to a wrapped GithubClient.

func NewDryGithubClient

func NewDryGithubClient(clt GithubClient, logger *zap.Logger) *DryGithubClient

func (*DryGithubClient) AddLabel added in v0.13.0

func (*DryGithubClient) CreateIssueComment

func (c *DryGithubClient) CreateIssueComment(context.Context, string, string, int, string) error

func (*DryGithubClient) ListPullRequests

func (c *DryGithubClient) ListPullRequests(ctx context.Context, owner, repo, state, sort, sortDirection string) githubclt.PRIterator

func (*DryGithubClient) ReadyForMerge added in v0.12.0

func (*DryGithubClient) RemoveLabel added in v0.13.0

func (*DryGithubClient) UpdateBranch

type GithubClient

type GithubClient interface {
	UpdateBranch(ctx context.Context, owner, repo string, pullRequestNumber int) (*githubclt.UpdateBranchResult, error)
	CreateIssueComment(ctx context.Context, owner, repo string, issueOrPRNr int, comment string) error
	ListPullRequests(ctx context.Context, owner, repo, state, sort, sortDirection string) githubclt.PRIterator
	ReadyForMerge(ctx context.Context, owner, repo string, prNumber int) (*githubclt.ReadyForMergeStatus, error)
	RemoveLabel(ctx context.Context, owner, repo string, pullRequestOrIssueNumber int, label string) error
	AddLabel(ctx context.Context, owner, repo string, pullRequestOrIssueNumber int, label string) error
}

GithubClient defines the methods of a GithubAPI Client that are used by the autoupdate implementation.

type HTTPService

type HTTPService struct {
	// contains filtered or unexported fields
}

func NewHTTPService

func NewHTTPService(autoupdater *Autoupdater) *HTTPService

func (*HTTPService) HandlerListFunc

func (h *HTTPService) HandlerListFunc(respWr http.ResponseWriter, _ *http.Request)

func (*HTTPService) HandlerStaticFiles

func (h *HTTPService) HandlerStaticFiles() http.Handler

func (*HTTPService) RegisterHandlers

func (h *HTTPService) RegisterHandlers(mux *http.ServeMux, endpoint string)

type Opt

type Opt func(*Autoupdater)

func DryRun

func DryRun(enabled bool) Opt

DryRun is an option for NewAutoupdater. If it is enabled all GitHub API operation that could result in a change will be simulated and always succeed.

type PRBranch

type PRBranch struct {
	BranchName string
}

func (*PRBranch) LogField

func (p *PRBranch) LogField() zap.Field

func (*PRBranch) String

func (p *PRBranch) String() string

func (*PRBranch) Type

func (p *PRBranch) Type() PRSpecifierType

type PRNumber

type PRNumber struct {
	Number int
}

func (*PRNumber) LogField

func (p *PRNumber) LogField() zap.Field

func (*PRNumber) String

func (p *PRNumber) String() string

func (*PRNumber) Type

func (p *PRNumber) Type() PRSpecifierType

type PRSpecifier

type PRSpecifier interface {
	Type() PRSpecifierType
	String() string
	LogField() zap.Field
}

type PRSpecifierType

type PRSpecifierType uint8
const (
	PRSpecifierTypeUndefined PRSpecifierType = iota
	PullRequestNumber
	PullRequestBranch
)

type PullRequest

type PullRequest struct {
	Number    int
	Branch    string
	Author    string
	Title     string
	Link      string
	LogFields []zap.Field

	EnqueuedSince time.Time
	// contains filtered or unexported fields
}

func NewPullRequest

func NewPullRequest(nr int, branch, author, title, link string) (*PullRequest, error)

func NewPullRequestFromEvent

func NewPullRequestFromEvent(ev *github.PullRequest) (*PullRequest, error)

func (*PullRequest) Equal

func (p *PullRequest) Equal(other *PullRequest) bool

Equal returns true if p and other are of type PullRequest and its Number field contains the same value.

func (*PullRequest) GetStateUnchangedSince

func (p *PullRequest) GetStateUnchangedSince() time.Time

func (*PullRequest) SetStateUnchangedSince

func (p *PullRequest) SetStateUnchangedSince(t time.Time)

func (*PullRequest) SetStateUnchangedSinceIfNewer

func (p *PullRequest) SetStateUnchangedSinceIfNewer(t time.Time)

func (*PullRequest) SetStateUnchangedSinceIfZero

func (p *PullRequest) SetStateUnchangedSinceIfZero(t time.Time)

type Repository

type Repository struct {
	OwnerLogin     string
	RepositoryName string
}

Repository is a datastructure that uniquely identifies a GitHub Repository.

func (*Repository) String

func (r *Repository) String() string

type Retryer

type Retryer interface {
	Run(context.Context, func(context.Context) error, []zap.Field) error
}

Retryer defines methods for running GithubClient operations repeately if they fail with a temporary error.

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL