Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
oshoval committed Nov 25, 2021
1 parent bc5d4c4 commit 4f3a5ab
Showing 21 changed files with 875 additions and 146 deletions.
19 changes: 3 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Setup Python
uses: actions/setup-python@v2.2.2
with:
# Version range or exact version of a Python version to use, using SemVer's version range syntax.
python-version: "3.6"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest tox pylint
pip install -r requirements.txt
pip install tox
- name: Test with tox
run: |
tox
tox
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/
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM fedora:32

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

RUN pip install requests jira
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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.
61 changes: 60 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,61 @@
# 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.

`--issue`: Use `./main.py --issue=<issue_id>` in order to create an issue for
a specified issue id.
No additional checks are performed in this case.

## 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).
Empty file added github2jira/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions github2jira/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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._vars = {name: None for name in var_names}

@property
def vars(self):
return self._vars

def Load(self):
for var_name in self._vars.keys():
value = os.getenv(var_name)
if value is None:
raise NameError(f"can't find {var_name}")
self._vars[var_name] = value
123 changes: 123 additions & 0 deletions github2jira/githublib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# 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 github2jira.config import Config

SECONDS_PER_WEEK = 7 * 24 * 60 * 60
# max github pages to process
GITHUB_MAX_PAGES = 20
# process upto x weeks back
MAX_DELTA_WEEKS = 4


class GithubEnv:
TOKEN = "GITHUB_TOKEN"
OWNER = "GITHUB_OWNER"
REPO = "GITHUB_REPO"
LABEL = "GITHUB_LABEL"


_ENV_VAR_NAMES = [GithubEnv.TOKEN, GithubEnv.OWNER, GithubEnv.REPO, GithubEnv.LABEL]


def config():
c = Config(_ENV_VAR_NAMES)
c.Load()
return c


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

@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"]

@property
def epoch(self):
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
timestamp = self.issue["created_at"]
return int(datetime.strptime(timestamp, TIME_FORMAT).timestamp())

@property
def labels(self):
return [l["name"] for l in self.issue["labels"]]


class Github:
def __init__(self, cfg):
owner = cfg.vars[GithubEnv.OWNER]
repo = cfg.vars[GithubEnv.REPO]
self.query_url = f"https://api.github.com/repos/{owner}/{repo}/issues"
self.headers = {"Authorization": f"token {cfg.vars[GithubEnv.TOKEN]}"}
self.expected_label = cfg.vars[GithubEnv.LABEL]

def issue_by_id(self, issue_id):
r = requests.get(f"{self.query_url}/{issue_id}", headers=self.headers)
issue = r.json()
if issue.get("url", None) is None:
return None
return Issue(issue)

def issues(self):
return self._filter(self._open_issues())

def _filter(self, issues):
for issue in issues:
if "pull" in issue.url:
continue

if self.expected_label in issue.labels and issue_in_window(
issue, MAX_DELTA_WEEKS
):
yield issue

def _open_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 issue in issues:
yield Issue(issue)


def issue_in_window(issue, max_delta_weeks):
epoch = issue.epoch
epoch_time_now = int(time.time())
return (epoch_time_now - epoch) < (max_delta_weeks * SECONDS_PER_WEEK)
102 changes: 102 additions & 0 deletions github2jira/jiralib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 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 json

from jira import JIRA

from github2jira.config import Config


class JiraEnv:
SERVER = "JIRA_SERVER"
USERNAME = "JIRA_USERNAME"
TOKEN = "JIRA_TOKEN"
PROJECT = "JIRA_PROJECT"
PROJECT_ID = "JIRA_PROJECT_ID"
COMPONENT = "JIRA_COMPONENT"


_ENV_VAR_NAMES = [
JiraEnv.SERVER,
JiraEnv.USERNAME,
JiraEnv.TOKEN,
JiraEnv.PROJECT,
JiraEnv.PROJECT_ID,
JiraEnv.COMPONENT,
]


def config():
c = Config(_ENV_VAR_NAMES)
c.Load()
return c


class Jira:
def __init__(self, cfg):
self.project = cfg.vars[JiraEnv.PROJECT]
self.project_id = cfg.vars[JiraEnv.PROJECT_ID]
self.server = cfg.vars[JiraEnv.SERVER]
self.component = cfg.vars[JiraEnv.COMPONENT]

jiraOptions = {"server": self.server}
self.jira = JIRA(
options=jiraOptions,
basic_auth=(cfg.vars[JiraEnv.USERNAME], cfg.vars[JiraEnv.TOKEN]),
)

def issue_exists(self, git_issue):
repo = git_issue.repo
id = git_issue.id
query = f'project={self.project} AND text ~ "GITHUB:{repo}-{id}"'
issues = self.jira.search_issues(query)
return len(issues) != 0

def create_issue(self, git_issue):
issue_data = self._create_issue_data(git_issue)
created_issue = self.jira.create_issue(issue_data)

issue_url = f"{self.server}/browse/{created_issue}"
print(f"Created issue {issue_url} for {git_issue.url}")

def _create_issue_data(self, git_issue):
issue_data = {
"project": {"id": self.project_id},
"summary": f"[GITHUB:{git_issue.repo}-{git_issue.id}] {git_issue.title}",
"description": git_issue.url,
"issuetype": {"name": "Task"},
}

if self.component != "":
issue_data["components"] = [{"name": self.component}]

return issue_data


class DryRunJira:
def __init__(self, jira):
self.jira = jira

def issue_exists(self, git_issue):
return self.jira.issue_exists(git_issue)

def create_issue(self, git_issue):
json_data = json.dumps(
self.jira._create_issue_data(git_issue), sort_keys=True, indent=4
)
print(f"Dryrun would create the following for {git_issue.url}\n{json_data}")
42 changes: 42 additions & 0 deletions github2jira/ticketmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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.
#

# how many tickets can be opened on each cycle
FLOOD_PROTECTION_LIMIT = 3


class TicketManager:
def __init__(self, jira):
self.issues_created = 0
self.jira = jira

def create(self, issue):
if not self.jira.issue_exists(issue):
if self._flood_protection_reached():
print("Flood protection reached, skipping creation of", issue.url)
return False

self.jira.create_issue(issue)
self.issues_created += 1
return True
else:
print("Issue for", issue.url, "already exists")

return False

def _flood_protection_reached(self):
return self.issues_created == FLOOD_PROTECTION_LIMIT
62 changes: 62 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/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 argparse

from github2jira.ticketmanager import TicketManager
import github2jira.jiralib as jiralib
import github2jira.githublib as githublib


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--dryrun", default=False, action="store_true")
parser.add_argument("--issue")
args = parser.parse_args()

jira = jiralib.Jira(jiralib.config())

if args.dryrun:
print("INFO: dryrun mode enabled")
jira = jiralib.DryRunJira(jira)

github = githublib.Github(githublib.config())
ticket_manager = TicketManager(jira)

if args.issue is None:
process_issues(github, ticket_manager)
else:
process_issue(args.issue, github, ticket_manager)


def process_issues(github, ticket_manager):
for issue in github.issues():
ticket_manager.create(issue)


def process_issue(issue, github, ticket_manager):
issue = github.issue_by_id(issue)
if issue is not None:
ticket_manager.create(issue)
else:
print("Issue not found")


if __name__ == "__main__":
main()
32 changes: 32 additions & 0 deletions manifests/cronjob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cron-github
spec:
schedule: "0 */1 * * *"
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: github
image: quay.io/oshoval/github:latest
command:
- /bin/sh
- -ce
- |
source /app/secret.txt
git clone https://github.com/oshoval/github2jira.git
./github2jira/main.py
volumeMounts:
- name: configs
mountPath: /app/secret.txt
subPath: secret.txt
restartPolicy: Never
volumes:
- name: configs
configMap:
name: git-token
22 changes: 22 additions & 0 deletions manifests/gitpod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
apiVersion: v1
kind: Pod
metadata:
name: github
namespace: default
spec:
containers:
- image: quay.io/oshoval/github:latest
name: github
command:
- /bin/bash
- -c
- sleep infinity
volumeMounts:
- name: configs
mountPath: /app/secret.txt
subPath: secret.txt
volumes:
- name: configs
configMap:
name: git-token
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
requests
jira
pytest
responses
47 changes: 47 additions & 0 deletions test_ticketmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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.
#

from github2jira.ticketmanager import TicketManager, FLOOD_PROTECTION_LIMIT
from github2jira.githublib import Issue


class MockJira:
def __init__(self):
self.counter = 0

def issue_exists(self, git_issue):
self.counter += 1
return self.counter % 2 != 0

def create_issue(self, git_issue):
return


def test_ticketmanager_create():
jira = MockJira()
ticket_manager = TicketManager(jira)

raw_issue = {}
raw_issue["html_url"] = "dummy"
git_issue = Issue(raw_issue)

issues_created = 0
for i in range(FLOOD_PROTECTION_LIMIT * 3):
if ticket_manager.create(git_issue):
issues_created += 1

assert issues_created == FLOOD_PROTECTION_LIMIT
Empty file added tests/__init__.py
Empty file.
43 changes: 43 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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
from datetime import datetime

import github2jira.githublib as githublib

PROJECT_ID = "100"
REPO = "repo"
ISSUE_ID = "10"
TITLE = "title"
COMPONENT = "dummy"


def get_raw_issue():
raw_issue = {}
raw_issue["html_url"] = f"https://github.com/owner/{REPO}/issues/{ISSUE_ID}"
raw_issue["number"] = ISSUE_ID
raw_issue["labels"] = [{"name": "sig/network"}, {"name": "sig/compute"}]
raw_issue["title"] = TITLE
raw_issue["url"] = f"https://api.github.com/repos/owner/{REPO}/issues/10"

ts_epoch = int(time.time()) - githublib.SECONDS_PER_WEEK * 0.5
raw_issue["created_at"] = datetime.fromtimestamp(ts_epoch).strftime(
"%Y-%m-%dT%H:%M:%SZ"
)

return raw_issue
42 changes: 42 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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

from unittest import mock
import pytest

import github2jira.githublib as githublib


def mockenv(**envvars):
return mock.patch.dict(os.environ, envvars)


@mockenv(
GITHUB_TOKEN="dummy",
GITHUB_OWNER="owner",
GITHUB_REPO="repo",
GITHUB_LABEL="sig/network",
)
def test_config_load():
githublib.config()


def test_negative_config_load():
with pytest.raises(Exception):
githublib.config()
101 changes: 101 additions & 0 deletions tests/test_github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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

from unittest import mock
import responses

import github2jira.githublib as githublib
from tests.common import get_raw_issue


def mockenv(**envvars):
return mock.patch.dict(os.environ, envvars)


def test_issue_properties():
raw_issue = get_raw_issue()
issue = githublib.Issue(raw_issue)

assert issue.repo == "repo"
assert issue.id == raw_issue["number"]
assert issue.url == raw_issue["html_url"]
assert issue.title == "title"
assert issue.labels == ["sig/network", "sig/compute"]

assert githublib.issue_in_window(issue, 1)
assert not githublib.issue_in_window(issue, 0.2)


@mockenv(
GITHUB_TOKEN="dummy",
GITHUB_OWNER="owner",
GITHUB_REPO="repo",
GITHUB_LABEL="sig/network",
)
@responses.activate
def test_githublib_issues():
github = githublib.Github(githublib.config())

entry1 = get_raw_issue()
entry2 = get_raw_issue()
entry2["html_url"] = "https://github.com/kubevirt/kubevirt/issues/11"
entry3 = get_raw_issue()
entry3["labels"][0]["name"] = "sig/na"
entry3["html_url"] = "https://github.com/kubevirt/kubevirt/issues/12"
entry4 = get_raw_issue()
entry4["html_url"] = "https://github.com/kubevirt/kubevirt/issues/13"

responses.add(responses.GET, github.query_url, json=[entry1, entry2], status=200)
responses.add(responses.GET, github.query_url, json=[entry3], status=200)
responses.add(responses.GET, github.query_url, json=[entry4], status=200)
responses.add(responses.GET, github.query_url, json=[], status=200)

issues = list(github.issues())

assert len(issues) == 3
assert issues[0].url == "https://github.com/owner/repo/issues/10"
assert issues[1].url == "https://github.com/kubevirt/kubevirt/issues/11"
assert issues[2].url == "https://github.com/kubevirt/kubevirt/issues/13"


@mockenv(
GITHUB_TOKEN="dummy",
GITHUB_OWNER="owner",
GITHUB_REPO="repo",
GITHUB_LABEL="sig/network",
)
@responses.activate
def test_githublib_issue_by_id():
github = githublib.Github(githublib.config())

entry1 = get_raw_issue()
issue_id = 777
issue_id_na = 1
responses.add(
responses.GET, f"{github.query_url}/{issue_id}", json=entry1, status=200
)
responses.add(
responses.GET,
f"{github.query_url}/{issue_id_na}",
json={"message": "Not Found"},
status=200,
)

assert github.issue_by_id(issue_id) is not None
assert github.issue_by_id(issue_id_na) is None
65 changes: 65 additions & 0 deletions tests/test_jiralib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# 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

from unittest import mock

from unittest.mock import patch
import pytest

from github2jira.githublib import Issue
import tests.common as common
from tests.common import get_raw_issue


def mockenv(**envvars):
return mock.patch.dict(os.environ, envvars)


class MockedJIRA:
def __init__(self, options=None, basic_auth=None):
return


@pytest.fixture
def mocked_jira():
return MockedJIRA()


@mockenv(
JIRA_SERVER="dummy",
JIRA_USERNAME="dummy",
JIRA_TOKEN="dummy",
JIRA_PROJECT="dummy",
JIRA_PROJECT_ID=common.PROJECT_ID,
JIRA_COMPONENT=common.COMPONENT,
)
@patch("jira.JIRA")
def test_initialization(self, mocked_jira):
from github2jira.jiralib import Jira, config

jira = Jira(config())
issue = Issue(get_raw_issue())
issue_data = jira._create_issue_data(issue)
assert issue_data == {
"project": {"id": common.PROJECT_ID},
"summary": f"[GITHUB:{common.REPO}-{common.ISSUE_ID}] {common.TITLE}",
"description": f"https://github.com/owner/{common.REPO}/issues/{common.ISSUE_ID}",
"issuetype": {"name": "Task"},
"components": [{"name": common.COMPONENT}],
}
83 changes: 83 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[tox]
envlist = pytest, black, flake8, pylint, yamllint, py36, py37, py38
skip_missing_interpreters = True
skipsdist=True

[testenv:pytest]
deps =
-rrequirements.txt
pytest-cov==2.8.1
pytest==5.3.1

commands =
pytest \
--log-level=DEBUG \
--durations=5 \
--cov-report=term \
--cov-report=xml \
--cov-report=html:htmlcov-{envname} \
{posargs}

[testenv:black]
skip_install = true
py36: basepython=python3.6
py37: basepython=python3.7
py38: basepython=python3.8
changedir = {toxinidir}
deps =
black==21.6b0
# style configured via pyproject.toml
commands =
black \
--check \
--diff \
{posargs} \
./

[testenv:flake8]
py36: basepython=python3.6
py37: basepython=python3.7
py38: basepython=python3.8
skip_install = true
changedir = {toxinidir}
deps =
flake8==3.7.9
commands =
flake8 \
--statistics {posargs} \
main.py \
github2jira/ \
tests/

[testenv:pylint]
py36: basepython=python3.6
py37: basepython=python3.7
py38: basepython=python3.8
sitepackages = true
skip_install = true
changedir = {toxinidir}
deps =
-rrequirements.txt
pylint==2.4.4
commands =
pylint \
--errors-only \
{posargs} \
main.py \
github2jira/ \
tests/

[testenv:yamllint]
py36: basepython=python3.6
py37: basepython=python3.7
py38: basepython=python3.8
skip_install = true
changedir = {toxinidir}
deps =
yamllint==1.23.0
commands =
yamllint manifests/

[flake8]
show_source = True
max-line-length=90

0 comments on commit 4f3a5ab

Please sign in to comment.