Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
Signed-off-by: (╯°□°)╯︵ uᴉǝssnH ɐɟɐʇsoW <[email protected]>
  • Loading branch information
mostafahussein committed Apr 19, 2023
1 parent 2e20ef5 commit eecb5d8
Show file tree
Hide file tree
Showing 14 changed files with 655 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: build

on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- '**/*.md'
pull_request:
branches:
- main
paths-ignore:
- '**/*.md'

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v3

- name: setup dependencies
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: enable docker buildx
uses: docker/setup-buildx-action@master

- name: lint
run: make lint

- name: build
run: make dry-run
41 changes: 41 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: release
on:
push:
tags:
- v*
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v3

- name: setup dependencies
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Enable experimental features for the Docker
run: |
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
mkdir -p ~/.docker
echo $'{\n "experimental": "enabled"\n}' | sudo tee ~/.docker/config.json
sudo service docker restart
- name: enable docker buildx
uses: docker/setup-buildx-action@master

- name: login to gh registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: prepare a release
run: make build-release

- name: publish image
run: make push
env:
VERSION: ${{ github.ref_name }}
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*.exe
*.exe~
*.dll
*.so
*.dylib

*.test

*.out

go.work

.idea/*

dist/

.release-env.env
48 changes: 48 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
project_name: workflow-watcher
before:
hooks:
- go mod tidy
builds:
- main: ./
binary: workflow-watcher
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
mod_timestamp: "{{ .CommitTimestamp }}"
dockers:
- use: buildx
goos: linux
goarch: amd64
image_templates:
- "ghcr.io/mostafahussein/{{ .ProjectName }}:{{ .Version }}-amd64"
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source=https://github.com/mostafahussein/workflow-watcher"
- "--label=org.opencontainers.image.authors=Mostafa Hussein <[email protected]>"
- use: buildx
goos: linux
goarch: arm64
image_templates:
- "ghcr.io/mostafahussein/{{ .ProjectName }}:{{ .Version }}-arm64"
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source=https://github.com/mostafahussein/workflow-watcher"
- "--label=org.opencontainers.image.authors=Mostafa Hussein <[email protected]>"
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-SNAPSHOT"
source:
rlcp: true
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM gcr.io/distroless/static:nonroot
COPY workflow-watcher /usr/local/bin/workflow-watcher
CMD ["/usr/local/bin/workflow-watcher"]
55 changes: 55 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
IMAGE_REPO = ghcr.io/mostafahussein/workflow-watcher
PACKAGE_NAME := github.com/mostafahussein/workflow-watcher
GOLANG_CROSS_VERSION ?= v1.20
SYSROOT_DIR ?= sysroots
SYSROOT_ARCHIVE ?= sysroots.tar.bz2

.PHONY: sysroot-pack
sysroot-pack:
@tar cf - $(SYSROOT_DIR) -P | pv -s $[$(du -sk $(SYSROOT_DIR) | awk '{print $1}') * 1024] | pbzip2 > $(SYSROOT_ARCHIVE)

.PHONY: sysroot-unpack
sysroot-unpack:
@pv $(SYSROOT_ARCHIVE) | pbzip2 -cd | tar -xf -

.PHONY: dry-run
dry-run:
@docker run \
--rm \
-e CGO_ENABLED=1 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-v `pwd`/sysroot:/sysroot \
-w /go/src/$(PACKAGE_NAME) \
goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \
--clean --skip-validate --skip-publish --snapshot

.PHONY: lint
lint:
docker run --rm -v $$(pwd):/app -w /app golangci/golangci-lint:v1.52.2 golangci-lint run -v

.PHONY: build-release
build-release:
docker run \
--rm \
-e CGO_ENABLED=1 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-v `pwd`/sysroot:/sysroot \
-w /go/src/$(PACKAGE_NAME) \
goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \
release --clean --skip-publish

.PHONY: push
push:
@if [ -z "$$VERSION" ]; then \
echo "VERSION is required"; \
exit 1; \
fi
export IMAGE_TAG=$(shell echo $$VERSION | sed -e s/^v//); \
docker push $(IMAGE_REPO):$$IMAGE_TAG-amd64; \
docker push $(IMAGE_REPO):$$IMAGE_TAG-arm64; \
docker manifest create $(IMAGE_REPO):$$IMAGE_TAG \
--amend $(IMAGE_REPO):$$IMAGE_TAG-amd64 \
--amend $(IMAGE_REPO):$$IMAGE_TAG-arm64; \
docker manifest push $(IMAGE_REPO):$$IMAGE_TAG;
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Workflow Watcher

[![ci](https://github.com/mostafahussein/workflow-watcher/actions/workflows/build.yaml/badge.svg)](https://github.com/mostafahussein/workflow-watcher/actions/workflows/build.yaml)

Pause a GitHub Actions workflow and wait for another workflow to complete before continuing.

Sometimes, a commit can result in cache invalidation, such as updating application dependencies, and you want to apply this commit to multiple branches. To maintain the ***Build Once, Deploy Anywhere*** principle in such cases, you can either wait until a specific branch is built before promoting your artifact to the next environment, or configure your workflow to check if there is an existing workflow running for the same commit in case you reset the other branches to a specific branch that contains the desired commits.


The way this action works is the following:

1. Workflow comes to the `workflow-watcher` action.
2. `workflow-watcher` will check if there is a workflow already running for the specified commit.
3. If and once the previously detected workflow is completed successfully, the workflow will continue.
4. If the previously detected workflow failed for any reason, then the workflow will exit with a failed status.


## Usage

```yaml
steps:
- uses: mostafahussein/[email protected]
if: ${{ github.ref != 'refs/heads/develop' }}
with:
secret: ${{ secrets.GH_TOKEN }}
repository-name: ${{ github.repository }}
repository-owner: ${{ github.repository_owner }}
head-sha: ${{ github.sha }}
base-branch: "develop"
polling-interval: 60

```

- `head-sha` is the commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "[Events that trigger workflows.](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)" For example, `ffac537e6cbbf934b08745a378932722df287a53`.
- `base-branch` is the branch that will be used as a source for your final artifact. For example, the testing branch will used the same artifact from the develop branch once the build is done, in this case the `base-branch` value should be `develop`
- `polling-interval` determines how often a poll occurs to check for a updates from Github API, by default it will be **30 seconds**, in case you need more time or having issues with Github rate limiting, you can set your own polling interval.

## Timeout

If you'd like to force a timeout of your workflow pause, you can specify `timeout-minutes` at either the [step](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes) level or the [job](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes) level.

For instance, if you want your workflow watcher step to timeout after an hour you could do the following:

```yaml
steps:
- uses: mostafahussein/[email protected]
timeout-minutes: 60
...
```

## Limitations

* While the workflow is paused, it will still continue to consume a concurrent job allocation out of the [max concurrent jobs](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits).
* A job (including a paused job) will be failed [after 6 hours](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits).
* A paused job is still running compute/instance/virtual machine and will continue to incur costs.

## Development

### Running test code

To test out your code in an action, you need to build the image and push it to a different container registry repository. For instance, if I want to test some code I won't build the image with the main image repository. Prior to this, comment out the label binding the image to a repo:

```dockerfile
# LABEL org.opencontainers.image.source https://github.com/mostafahussein/workflow-watcher
```

Build the image:

```
$ VERSION=1.1.1-rc.1 make IMAGE_REPO=ghcr.io/mostafahussein/workflow-watcher-test build
```

*Note: The image version can be whatever you want, as this image wouldn't be pushed to production. It is only for testing.*

Push the image to your container registry:

```
$ VERSION=1.1.1-rc.1 make IMAGE_REPO=ghcr.io/mostafahussein/workflow-watcher-test push
```

To test out the image you will need to modify `action.yaml` so that it points to your new image that you're testing:

```yaml
image: docker://ghcr.io/mostafahussein/workflow-watcher-test:1.1.0-rc.1
```
Then to test out the image, run a workflow specifying your dev branch:
```yaml
- name: Watch Workflow on Develop branch
uses: your-github-user/workflow-watcher@your-dev-branch
if: ${{ github.ref != 'refs/heads/develop' }}
with:
secret: ${{ secrets.GH_TOKEN }}
repository-name: ${{ github.repository }}
repository-owner: ${{ github.repository_owner }}
head-sha: ${{ github.sha }}
base-branch: "develop"

```

For `uses`, this should point to your repo and dev branch.

## Credits

- Author: [Mostafa Hussein](https://github.com/mostafahussein)
- Inspired by: [Manual Workflow Approval](https://github.com/trstringer/manual-approval)
28 changes: 28 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Workflow Watcher
description: Pause a workflow and wait for another workflow to finish first
branding:
icon: pause
color: yellow
inputs:
repository-name:
description: Repository Name
required: true
repository-owner:
description: Repository Owner
required: true
head-sha:
description: Commit Sha
required: true
base-branch:
description: Base branch
required: true
polling-interval:
description: determines how often a poll occurs to check for a updates from Github API (value in seconds)
required: false
default: 30
secret:
description: Secret
required: true
runs:
using: docker
image: docker://ghcr.io/mostafahussein/workflow-watcher:1.0.0
20 changes: 20 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

type workflowStatus string

const (
envVarRepoName string = "INPUT_REPOSITORY-NAME"
envVarHeadSha string = "INPUT_HEAD-SHA"
envVarBaseBranch string = "INPUT_BASE-BRANCH"
envVarRepoOwner string = "INPUT_REPOSITORY-OWNER"
envVarPollingInterval string = "INPUT_POLLING-INTERVAL"
envVarToken string = "INPUT_SECRET"

workflowStatusFailed workflowStatus = "failure"
workflowStatusCompleted workflowStatus = "completed"
workflowConclusionSuccess workflowStatus = "success"
workflowConclusionFailed workflowStatus = "failure"
workflowConclusionCancelled workflowStatus = "cancelled"
workflowConclusionSkipped workflowStatus = "skipped"
workflowConclusionTimeOut workflowStatus = "timed_out"
)
23 changes: 23 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module github.com/mostafahussein/workflow-watcher

go 1.19

require (
github.com/google/go-github/v51 v51.0.0
github.com/tidwall/gjson v1.14.4
golang.org/x/oauth2 v0.7.0
)

require (
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)
Loading

0 comments on commit eecb5d8

Please sign in to comment.