Skip to content

Commit

Permalink
Introduce create ticket script
Browse files Browse the repository at this point in the history
The script automates github issue to Jita ticket mirroring.

Signed-off-by: Or Shoval <oshoval@redhat.com>
  • Loading branch information
oshoval committed Nov 11, 2021
1 parent cb03a1a commit aa93bed
Show file tree
Hide file tree
Showing 13 changed files with 544 additions and 130 deletions.
130 changes: 2 additions & 128 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,129 +1,3 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
__pycache__
secret.txt
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM fedora:32

RUN dnf install -y python3 git pip \
&& dnf clean all \
&& rm -rf /var/cache/yum

RUN pip install requests jira

CMD [“echo”, “Hello World”]

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
Copyright [2021] [Red Hat, Inc]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
# github2jira
Scrap github issues and create Jira tickets
github2jira automates mirroring of github issues to Jira tickets.

The tool scans github for issues that match the desired criteria,
and for each one of them creates a Jira ticket (unless it already exists).

## One time configuration
1. Create github token https://github.com/settings/tokens, refer it as `GITHUB_TOKEN`
2. Make sure you have a Jira bot access (either a user:pass or user:token), refer as `JIRA_USERNAME`,`JIRA_TOKEN`
3. Get your Jira project id, refer as `JIRA_PROJECT_ID`
`curl -s -u JIRA_USERNAME:JIRA_TOKEN -X GET -H "Content-Type: application/json" <JIRA_SERVER>/rest/api/latest/project/<JIRA_PROJECT> | jq .id`

## Running manually

1. export the following envvars:
```
export JIRA_SERVER=<..> # for example https://nmstate.atlassian.net
export JIRA_PROJECT=<..> # name of the Jira project (ticket names are JIRA_PROJECT-#)
export JIRA_PROJECT_ID=<..> # see "One time configuration" section
export JIRA_COMPONENT=<..> # which component to set in the created tickets
export GITHUB_OWNER=<..> # the x of https://github.com/x/y
export GITHUB_REPO=<..> # the y of https://github.com/x/y
export GITHUB_LABEL=<..> # which label to filter
export JIRA_USERNAME=<..> # see "One time configuration" section
export JIRA_TOKEN=<..> # see "One time configuration" section
export GITHUB_TOKEN=<..> # see "One time configuration" section
```

2. Run `./main.py` in order to fetch github issues and create a ticket for them

### Additional settings

`dryrun`: Use `./main.py --dryrun` in order to run the tool in dryrun mode.
dryrun mode will fetch github issues, and report what Jira tickets it would create,
but without creating them.

## Running as k8s payload

In order to have a fully automated mirroring process,
it is suggested to run the tool as a cron jon.

One of the methods to achieve it, is to run it as k8s CronJob payload.

### One time configuration: Build docker image for the script

1. From the project folder, run `docker build -f Dockerfile -t <image> .`
once its done, push it to your image repository, or rename and push to a local registry.

### Deploy as k8s payload

1. Create secret.txt with the exports from the section above (include the export command).

2. Create a configmap for the txt file
`kubectl create configmap git-token --from-file=secret.txt`

3. Deploy either a pod or a CronJob (see manifests folder).
35 changes: 35 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
#
# This file is part of the github2jira project
#
# 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.
#
# Copyright 2021 Red Hat, Inc.
#

import os


class Config:
def __init__(self, var_names):
self.var_names = var_names
self.vars = {}

def Load(self):
for var_name in self.var_names:
value = os.getenv(var_name)
if value is None:
print(f"Error: cant find {var_name}")
return False
self.vars[var_name] = value
return True
102 changes: 102 additions & 0 deletions githublib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
#
# This file is part of the github2jira project
#
# 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.
#
# Copyright 2021 Red Hat, Inc.
#

import time
import requests

from datetime import datetime

from config import Config

SECONDS_PER_WEEK = 604800
# max github pages to process
GITHUB_MAX_PAGES = 20
# process upto x weeks back
MAX_DELTA_WEEKS = 4


class GithubConfig(Config):
def __init__(self):
super().__init__(
["GITHUB_TOKEN", "GITHUB_OWNER", "GITHUB_REPO", "GITHUB_LABEL"]
)


class Issue:
def __init__(self, issue):
self.issue = issue

def filter(self, expected_label):
if expected_label == "":
return True
for label in self.issue["labels"]:
if label["name"] == expected_label:
return True
return False

def age_relevant(self, max_delta):
epoch_time_now = int(time.time())
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
timestamp = self.issue["created_at"]
epoch = int(datetime.strptime(timestamp, TIME_FORMAT).timestamp())
return (epoch_time_now - epoch) < (max_delta * SECONDS_PER_WEEK)

@property
def repo(self):
return self.issue["html_url"].split("/")[4]

@property
def id(self):
return self.issue["number"]

@property
def url(self):
return self.issue["html_url"]

@property
def title(self):
return self.issue["title"]


class Github:
def __init__(self, cfg):
self.owner = cfg.vars["GITHUB_OWNER"]
self.repo = cfg.vars["GITHUB_REPO"]
self.expected_label = cfg.vars["GITHUB_LABEL"]
self.query_url = f"https://api.github.com/repos/{self.owner}/{self.repo}/issues"
self.headers = {"Authorization": f"token {cfg.vars['GITHUB_TOKEN']}"}

def issues(self):
for page in range(1, GITHUB_MAX_PAGES):
params = {"state": "open", "page": page, "per_page": "100"}
r = requests.get(self.query_url, headers=self.headers, params=params)
issues = r.json()

if len(issues) == 0:
return

for element in issues:
issue = Issue(element)
if "pull" in issue.url:
continue

if issue.filter(self.expected_label) and issue.age_relevant(
MAX_DELTA_WEEKS
):
yield (issue)
Loading

0 comments on commit aa93bed

Please sign in to comment.