Skip to content

Commit

Permalink
Implement github release runner for github 'release' events where act…
Browse files Browse the repository at this point in the history
…ion is 'published'

When release event action is 'published' and job definition listen 'release' events
It will install release assets file from <release_files> to <release_base_dir>/<event release tag_name>/

      for each <file> from <release_files>
        download <repository html_url>/<event release tag_name>/<file>
        install downloaded file to <release_base_dir>/<event release tag_name>/

Example:

    # job definition:

        {
          "https://github.com/orga/repo": {
            "release": {
              "runner_lib": "release_example",
              "release_files": ["README.md", "package.json"],
              "release_base_dir": "/repo"
            }
          }
        }

    # log
        INFO:root:loaded lib: context.github_release
        INFO:root:create new job for https://github.com/orga/repo, event release
        INFO:root:loaded lib: runner.release_example
        INFO:root:loaded lib: job_payload.null
        INFO:root:download https://github.com/orga/repo/releases/download/v1.0.42/README.md ...
        INFO:root:installed /repo/1/1.0.42/README.md
        INFO:root:download https://github.com/orga/repo/releases/download/v1.0.42/package.json ...
        INFO:root:installed /repo/1/1.0.42/package.json
        INFO:root:job status: 200, response: {
          "message": "release v1.0.42 https://github.com/orga/repo, event: release, deployed file: {'/repo/1/1.0.42/README.md', '/repo/1/1.0.42/package.json'},  current link updated:...",
          "description": {
            "action": "published",
            "name": "https://github.com/orga/repo",
            "tag_name": "v1.0.42",
            "draft": false,
            "prerelease": false,
            "target_commitish": "main",
            "html_url": "https://github.com/orga/repo"
          }
        }
  • Loading branch information
cgalibern committed Sep 13, 2021
1 parent 5b13bd0 commit 0b0626e
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 9 deletions.
74 changes: 68 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# webhook job runner (support github webhook)

webhook job processor supporting github webhook for pull request, push.
webhook job processor supporting github webhook for pull request, push, release.

when SECRET env var is defined signature is verified

bundled `runner_lib method: http_post`
bundled `runner_lib method: http_post, release`

## how to use

Expand All @@ -15,6 +15,11 @@ bundled `runner_lib method: http_post`
mkdir -p ~/extra_lib/job_payload/
cp <your python job_payload python files> ~/extra_lib/job_payload/

if custom runner

mkdir -p ~/extra_lib/runner/
cp <your python runner python files> ~/extra_lib/runner/

* Prepare your job definition file

see bellow for `jobs config file` details
Expand Down Expand Up @@ -47,11 +52,38 @@ job config file is a json file, that define keys.
"uri": "uri used to create job",
"credentials": {},
"payload_lib": "a_lib_to_create_job_payload_for_uri"
"tls": true
"tls": true,
"release_files": ["asset_file1_to_release", "asset_file2_to_release"],
"release_base_dir": "base dir path for release"
}
}
}

# Needed prop for default 'release' event:
- release_files
- release_base_dir

# Needed prop for 'pull_request:...', 'push' events with 'http_post' runner_lib
- uri
- credentials
- payload_lib
- tls

## bundled runner libs

### http_post

create payload from <payload_lib>
POST <uri> header=<credentials> json=payload verify=<tls>

### release

When release event action is 'published'
for each <file> from <release_files>
download <repository html_url>/<event release tag_name>/<file>
install downloaded file to <release_base_dir>/<event release tag_name>/

## job config examples
### jobs config file example for github

Here is an example of github webhook listener for repository https://github.com/opensvc/webhook
Expand All @@ -67,8 +99,8 @@ Pull request open will launch the following job:


{
"https://github.com/opensvc/webhook": {
"pr_opened": {
"https://github.com/opensvc/repository": {
"pull_request:opened": {
"runner_lib": "http_post",
"uri": "https://rundeck.domain/api/27/job/job-xxx-pr/run",
"credentials": {"X-Rundeck-Auth-Token": "the-job-token"},
Expand All @@ -80,6 +112,19 @@ Pull request open will launch the following job:
"credentials": {"X-Rundeck-Auth-Token": "the-job-token"},
"payload_lib": "job-example-pr-push"
}
"release": {
"runner_lib": "release",
"release_files": ["index.js", "index.html"],
"release_base_dir": "/deploy_dir"
}
},
"https://github.com/opensvc/other": {
"release": {
"runner_lib": "release_custom1",
"release_files": ["index.js", "index.html"],
"release_base_dir": "/deploy_dir"
}
}
}

## extra_lib directory
Expand All @@ -89,6 +134,9 @@ Pull request open will launch the following job:
|_ job-example-pr-opened.py
|_ job-example-push.py

|_ runner/
|_ release_custom1.py

## payload libs
### depending on your jobs, you may create a payload lib for each jobs
a job payload lib must define a `class JobPayloadProvider(PayloadProviderAbstract)`
Expand All @@ -98,7 +146,7 @@ This class must define method: `def __call__(context: Context):` that will provi
* `context` attribute that can be used to create job payload
context objects are instance of `ContextAbstract` sub classes

### minimum lib
### minimum payload lib

from context.github_push import Context
from job_payload.payload_abstract import PayloadProviderAbstract
Expand Down Expand Up @@ -127,6 +175,20 @@ This class must define method: `def __call__(context: Context):` that will provi
}
}

### example custom release lib

from runner.release_runner import Runner as ReleaseRunner


class Runner(ReleaseRunner):
def extra_action(self, job):
return "current link updated:..."
def release_dir(self, job):
tag_name = job.context.tag_name.lstrip("v")
api_version = tag_name.split(".")[0]
return "%s/%s/%s" % (job.release_base_dir, api_version, tag_name)

## Example of github event listener for github pull requests

from job.factory import JobFactory
Expand Down
17 changes: 17 additions & 0 deletions extra_lib_examples/runner/release_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from runner.release import Runner as ReleaseRunner


class Runner(ReleaseRunner):
def extra_action(self, job):
return " current link updated:..."

def release_dir(self, job):
"""
define release dir from event tag name value
examples:
v1.9.5 -> <release_base_dir>/1/1.9.5/
v2.0.0 -> <release_base_dir>/2/2.0.0/
"""
tag_name = job.context.tag_name.lstrip("v")
api_version = tag_name.split(".")[0]
return "%s/%s/%s" % (job.release_base_dir, api_version, tag_name)
63 changes: 63 additions & 0 deletions src/context/github_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from context.github_abstract import GitHubAbstract


class Context(GitHubAbstract):
"""
Context class for github release events
"""

@property
def event(self):
return 'release'

@property
def action(self):
return self.payload['action']

@property
def release(self):
return self.payload['release']

@property
def download_url(self):
return "%s/releases/download/%s" % (self.html_url, self.tag_name)

@property
def release_name(self):
return self.release['name']

@property
def tag_name(self):
return self.release['tag_name']

@property
def draft(self):
return self.release['draft']

@property
def prerelease(self):
return self.release['prerelease']

@property
def target_commitish(self):
return self.release['target_commitish']

@property
def repository(self):
return self.payload['repository']

@property
def html_url(self):
return self.repository['html_url']

@property
def description(self):
return {
'action': self.action,
'name': self.name,
'tag_name': self.tag_name,
'draft': self.draft,
'prerelease': self.prerelease,
'target_commitish': self.target_commitish,
'html_url': self.html_url,
}
4 changes: 4 additions & 0 deletions src/controllers/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ def pull_request(body):
@verify
def push(body):
return job_factory.create(context_lib_name='github_push', payload=body).run()

@verify
def release(body):
return job_factory.create(context_lib_name='github_release', payload=body).run()
10 changes: 8 additions & 2 deletions src/job/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@
@dataclass(order=True)
class Job:
runner_lib: str = 'null_runner'
context: ContextAbstract = field(default_factory=ContextAbstract)
runner = None

# for http runners
uri: str = None
credentials: dict = field(default_factory=dict)
tls: bool = True
payload_lib: str = None
context: ContextAbstract = field(default_factory=ContextAbstract)
runner = None
payload_provider = None

# for release runners
release_files: list = field(default_factory=list)
release_base_dir: str = ""

def execute(self):
if self.runner is not None:
self.runner.execute(self)
Expand Down
62 changes: 62 additions & 0 deletions src/runner/release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging
import os

import requests

from runner.runner_abstract import RunnerAbstract


def download_to(url, dest):
logging.info("download %s ...", url)
r = requests.get(url, allow_redirects=True)
open(dest, 'wb').write(r.content)
logging.info("installed %s", dest)


class Runner(RunnerAbstract):
def download_url(self, job, name):
return "%s/%s" % (job.context.download_url, name)

def extra_action(self, job):
"""
extra actions to be run after asset files installed
return message
"""
return ""

def release_dir(self, job):
"""
default destination dir for release files (<release_base_dir>/<event release tag_name>/)
"""
return "%s/%s" % (job.release_base_dir, job.context.tag_name)

def execute(self, job):
"""
runner example:
- download assets files from DEPLOY_FILES list
- install files to release_dir/
"""
if job.context.action != "published":
self._data = {
"message": f'release action {job.context.action} {job.context.name} nothing to do'
}
return
if not job.release_base_dir:
self._data = {
"message": f'release action {job.context.action} {job.context.name} skipped (undefined release_base_dir)'
}
return
dest_dir = self.release_dir(job)
os.makedirs(dest_dir, exist_ok=True)
deployed_files = set()
for file in job.release_files:
dest = f"{dest_dir}/{file}"
download_to(self.download_url(job, file), dest)
deployed_files.add(dest)
extra_action_message = self.extra_action(job)
message = f'release {job.context.release_name} {job.context.name}, '\
f'event: {job.context.event}, '\
f'deployed file: {deployed_files}'
if extra_action_message:
message = f'{message}, {extra_action_message}'
self._data = {'message': message}
Loading

0 comments on commit 0b0626e

Please sign in to comment.