Skip to content

Commit

Permalink
Merge pull request #2 from cgalibern/feature-release
Browse files Browse the repository at this point in the history
feature-release
  • Loading branch information
cgalibern authored Sep 13, 2021
2 parents 0e11344 + 0b0626e commit bff79e7
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 10 deletions.
76 changes: 70 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +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.

bundled `runner_lib method: http_post`
when SECRET env var is defined signature is verified

bundled `runner_lib method: http_post, release`

## how to use

Expand All @@ -13,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 @@ -45,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 @@ -65,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 @@ -78,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 @@ -87,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 @@ -96,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 @@ -125,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}
6 changes: 5 additions & 1 deletion src/signature.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import hmac
import logging
import os
import connexion

secret = os.environ.get('SECRET', 'This is not a secret')
secret = os.environ.get('SECRET')


def verify(func):
def wrapper(*argv, **kwargs):
if secret is None:
logging.info("signature verify skipped (undefined SECRET env var)")
return func(*argv, **kwargs)
data = connexion.request.data
_, request_digest = connexion.request.headers['X-Hub-Signature'].split('=')
digest = hmac.new(secret.encode(), msg=data, digestmod='sha1').hexdigest()
Expand Down
Loading

0 comments on commit bff79e7

Please sign in to comment.