From 0aa283c21e2acca6f2a74b2e2ed7396f23fc19ed Mon Sep 17 00:00:00 2001 From: Jeremy <1028480+kaosx5s@users.noreply.github.com> Date: Tue, 16 May 2023 13:44:32 -0700 Subject: [PATCH 1/4] Update readme, changelog and setup for dependencies Signed-off-by: kaosx5s <> --- .github/workflows/python-app.yml | 2 +- CHANGELOG.md | 12 ++++++++++++ README.md | 14 +++++++++----- setup.py | 4 ++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 05dd3b6..e2ab386 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8] + python-version: [3.7, 3.8, 3.9, 3.10, 3.11] steps: - uses: actions/checkout@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 249754c..2a4449d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [3.5.0] - 2023-05-16 +### Added +- Added support to pass in a git token on init + +## [3.4.0] - 2023-02-24 +### Added +- Added method to return the github client + +## [3.3.0] - 2022-05-12 +### Added +- Added support for serialization options when dumping a file + ## [3.2.0] - 2021-03-26 ### Added - Added plain text file support diff --git a/README.md b/README.md index 66edaa9..cea1681 100644 --- a/README.md +++ b/README.md @@ -130,24 +130,27 @@ if __name__ == '__main__': # Dependencies - `config.yaml` (required) - list of repositories you wish to modify -- `GIT_USERNAME` (required) - your Github username -- `GIT_PASSWORD` (required) - Github personal access token that grants write access to the specified repositories +- `GIT_USERNAME` (optional) - your Github username (combine with `GIT_PASSWORD` OR use `GIT_TOKEN`) +- `GIT_PASSWORD` (optional) - Github personal access token that grants write access to the specified repositories +- `GIT_TOKEN` (optional) - Github personal access token that grants write access to the specified repositories + +A Github Personal Access Token can also be passed in via the `token=` named parameter. (Added in `3.5.0`) # Development The simplest way to hit the ground running if you want to contribute with code is using docker. Launch a python container -``` +```shell localhost$ docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) python:3.7-stretch bash ``` Install the project and test dependencies in developer mode -``` +```shell container# pip install -e .[test] ``` Run the tests -``` +```shell container# pytest =========================================== test session starts ============================================ platform linux -- Python 3.7.1, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 @@ -170,3 +173,4 @@ collected 33 items ## Contributors - [Jeremy Chavez](https://github.com/kaosx5s) - [Etienne Grignon](https://github.com/sharpyy) +- [Anshul Vohra](https://github.com/AnshulV98) diff --git a/setup.py b/setup.py index 751d3b3..63e16c9 100644 --- a/setup.py +++ b/setup.py @@ -6,14 +6,14 @@ setup_reqs = ["pytest", "pytest-cov", "pytest-runner", "flake8"] setuptools.setup( name="gordian", - version="3.4.0", + version="3.5.0", author="Intuit", author_email="cg-sre@intuit.com", description="A tool to search and replace files in a Git repo", long_description=long_description, long_description_content_type='text/markdown', url="https://github.com/argoproj-labs/gordian", - install_requires=["pygithub", "pyyaml", "jsonpatch", "deepdiff", "retry"], + install_requires=["pygithub", "pyyaml", "jsonpatch", "deepdiff", "retry2"], setup_requires=setup_reqs, extras_require={"test": setup_reqs}, tests_require=setup_reqs, From 3e3c127576d303bebcfa37df87e89f4935648c9e Mon Sep 17 00:00:00 2001 From: Jeremy <1028480+kaosx5s@users.noreply.github.com> Date: Tue, 16 May 2023 13:44:52 -0700 Subject: [PATCH 2/4] Add support for passing in the git token on init Signed-off-by: kaosx5s <> --- gordian/repo.py | 13 ++++++----- tests/test_repo.py | 57 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/gordian/repo.py b/gordian/repo.py index 05ccebc..1b0392a 100644 --- a/gordian/repo.py +++ b/gordian/repo.py @@ -3,8 +3,6 @@ import datetime import logging import os -import sys -import time from retry import retry from gordian.files import * from gordian.files.plaintext_file import PlainTextFile @@ -16,7 +14,7 @@ class Repo: - def __init__(self, repo_name, github_api_url=None, branch=None, github=None, files=None, semver_label=None, target_branch='master', fork=False): + def __init__(self, repo_name, github_api_url=None, branch=None, github=None, files=None, semver_label=None, target_branch='master', fork=False, token=None): if github_api_url is None: self.github_api_url = BASE_URL else: @@ -26,11 +24,14 @@ def __init__(self, repo_name, github_api_url=None, branch=None, github=None, fil self._github = github else: if "GIT_TOKEN" in os.environ: - logger.debug('Using git token') - self._github = Github(base_url=self.github_api_url, login_or_token=os.environ['GIT_TOKEN']) + logger.debug('Using git token from environment variables') + token = os.getenv('GIT_TOKEN') + + if token: + self._github = Github(base_url=self.github_api_url, login_or_token=token) else: logger.debug('Using git username and password') - self._github = Github(base_url=self.github_api_url, login_or_token=os.environ['GIT_USERNAME'], password=os.environ['GIT_PASSWORD']) + self._github = Github(base_url=self.github_api_url, login_or_token=os.getenv('GIT_USERNAME'), password=os.getenv('GIT_PASSWORD')) if files is None: files = [] diff --git a/tests/test_repo.py b/tests/test_repo.py index 5170421..95aa639 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -1,5 +1,6 @@ import unittest import pytest +import os from gordian.repo import Repo from unittest.mock import MagicMock, patch, call from gordian.files import YamlFile @@ -86,15 +87,15 @@ def test_get_files(self): self.repo._source_repo.get_contents.assert_has_calls([call('', 'refs/heads/target'), call('directory', 'refs/heads/target')]) self.assertEquals(self.repo.files, [repository_file]) - def test__set_target_branch(self): + def test_set_target_branch(self): self.repo._set_target_branch('master') self.assertEqual(self.repo.source_branch, 'refs/heads/master') - def test__set_target_branch_source_branch(self): + def test_set_target_branch_source_branch(self): self.repo._set_target_branch('master', 'something') self.assertEqual(self.repo.source_branch, 'refs/heads/something') - def test__set_target_branch_reset_file_cache(self): + def test_set_target_branch_reset_file_cache(self): self.repo._set_target_branch('master') cached_files = ['cached_file', 'cached_file', 'cached_file'] self.repo.files = cached_files @@ -127,7 +128,7 @@ def test_create_pr_no_labels(self): pr.set_labels.assert_not_called() repo._source_repo.create_pull.assert_not_called() - def test__get_new_version_major(self): + def test_get_new_version_major(self): version_file = MagicMock() version_file.decoded_content = '1.2.3'.encode('utf-8') self.repo.version_file = version_file @@ -135,7 +136,7 @@ def test__get_new_version_major(self): self.repo._get_new_version() self.assertEqual(self.repo.new_version, '2.0.0') - def test__get_new_version_minor(self): + def test_get_new_version_minor(self): version_file = MagicMock() version_file.decoded_content = '1.2.3'.encode('utf-8') self.repo.version_file = version_file @@ -143,7 +144,7 @@ def test__get_new_version_minor(self): self.repo._get_new_version() self.assertEqual(self.repo.new_version, '1.3.0') - def test__get_new_version_patch(self): + def test_get_new_version_patch(self): version_file = MagicMock() version_file.decoded_content = '1.2.3'.encode('utf-8') self.repo.version_file = version_file @@ -151,5 +152,47 @@ def test__get_new_version_patch(self): self.repo._get_new_version() self.assertEqual(self.repo.new_version, '1.2.4') + @patch('gordian.repo.Github') + def test_init_with_passed_token(self, mock_git): + Repo('test_repo', token='abcdef') + args = {'login_or_token': 'abcdef', 'base_url': 'https://api.github.com'} + mock_git.assert_called_with(**args) + + @patch.dict(os.environ, {'GIT_TOKEN': '12345'}) + @patch('gordian.repo.Github') + def test_init_with_token_from_env(self, mock_git): + Repo('test_repo') + args = {'login_or_token': '12345', 'base_url': 'https://api.github.com'} + + mock_git.assert_called_with(**args) + + @patch.dict(os.environ, {'GIT_USERNAME': 'test-user', 'GIT_PASSWORD': 'test-pass'}) + @patch('gordian.repo.Github') + def test_init_with_user_pass_env(self, mock_git): + Repo('test_repo') + args = {'login_or_token':'test-user', 'password':'test-pass', 'base_url': 'https://api.github.com'} + + mock_git.assert_called_with(**args) + + @patch('gordian.repo.Github') + def test_create_file(self, mock_git): + repo = Repo('test_repo', github=mock_git) + repo.create_file('test', 'test', 'test file create') + + repo._source_repo.create_file.assert_called_once() + self.assertTrue(repo.dirty) + + @patch('gordian.repo.Github') + def test_delete_file(self, mock_git): + mock_file = MagicMock() + repo = Repo('test_repo', github=mock_git) + repo.delete_file(mock_file, 'test file delete') + + repo._source_repo.delete_file.assert_called_once() + self.assertTrue(repo.dirty) + def test__get_github_client(self): - self.assertIsNotNone(self.repo.get_github_client) \ No newline at end of file + repo = Repo('test_repo', branch='', github=self.mock_git) + + self.assertIsNotNone(repo.get_github_client()) + self.assertEqual(repo.get_github_client(), self.mock_git) From a099d6aaf7121336d5cc2881c9917da618db3a5a Mon Sep 17 00:00:00 2001 From: Jeremy <1028480+kaosx5s@users.noreply.github.com> Date: Tue, 16 May 2023 14:14:28 -0700 Subject: [PATCH 3/4] Update python-version in workflow Signed-off-by: kaosx5s <> --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index e2ab386..cbb79da 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, 3.10, 3.11] + python-version: [3.7, 3.8, 3.9, '3.10', 3.11] steps: - uses: actions/checkout@v1 From 181c96669de54d95489c4914854a14f067d65ab3 Mon Sep 17 00:00:00 2001 From: Jeremy <1028480+kaosx5s@users.noreply.github.com> Date: Wed, 17 May 2023 10:03:50 -0700 Subject: [PATCH 4/4] Updates based on PR feedback Signed-off-by: kaosx5s <> --- CHANGELOG.md | 2 +- README.md | 29 +++++++++++++++++++++++++---- gordian/repo.py | 13 +++++++------ 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a4449d..7afbf2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ## [3.5.0] - 2023-05-16 ### Added -- Added support to pass in a git token on init +- Added support to pass in a github token, username and password as named parameters ## [3.4.0] - 2023-02-24 ### Added diff --git a/README.md b/README.md index cea1681..492245b 100644 --- a/README.md +++ b/README.md @@ -130,11 +130,32 @@ if __name__ == '__main__': # Dependencies - `config.yaml` (required) - list of repositories you wish to modify -- `GIT_USERNAME` (optional) - your Github username (combine with `GIT_PASSWORD` OR use `GIT_TOKEN`) -- `GIT_PASSWORD` (optional) - Github personal access token that grants write access to the specified repositories -- `GIT_TOKEN` (optional) - Github personal access token that grants write access to the specified repositories +- `GIT_USERNAME` (optional) - your Github username +- `GIT_PASSWORD` (optional) - your Github password or Personal Access Token +- `GIT_TOKEN` (optional) - Github Personal Access Token that grants write access to the specified repositories -A Github Personal Access Token can also be passed in via the `token=` named parameter. (Added in `3.5.0`) +# Authentication +Two methods of authentication are available: +- Using a Personal Access Token +- Using a Github Username & Password + +A Github Personal Access Token, Github Username and Github Password can also be passed in via the `token=`, `username=` and `password=` named parameters. The passed value will always take precedence over any environment variable. (Added in `3.5.0`) + +## Authentication - Personal Access Token +A Personal Access Token can be used in two ways: +- Setting the `GIT_TOKEN` environment variable +- Passing the `token=` named parameter + +The Personal Access Token must have `write` access to any specified repositories you wish to submit changes to. + +## Authentication - Github Username & Password +A Github Username and Password combination can be used in two ways: +- Setting the `GIT_USERNAME` and `GIT_PASSWORD` environment variables +- Passing the `username=` and `password=` parameters + +The user must have `write` access to any specified repositories you wish to submit changes to. + +The `GIT_PASSWORD` or `password=` may also contain a Personal Access Token instead of the account password. # Development The simplest way to hit the ground running if you want to contribute with code is using docker. diff --git a/gordian/repo.py b/gordian/repo.py index 1b0392a..ace55db 100644 --- a/gordian/repo.py +++ b/gordian/repo.py @@ -14,7 +14,7 @@ class Repo: - def __init__(self, repo_name, github_api_url=None, branch=None, github=None, files=None, semver_label=None, target_branch='master', fork=False, token=None): + def __init__(self, repo_name, github_api_url=None, branch=None, github=None, files=None, semver_label=None, target_branch='master', fork=False, token=None, username=None, password=None): if github_api_url is None: self.github_api_url = BASE_URL else: @@ -23,15 +23,16 @@ def __init__(self, repo_name, github_api_url=None, branch=None, github=None, fil if github is not None: self._github = github else: - if "GIT_TOKEN" in os.environ: - logger.debug('Using git token from environment variables') - token = os.getenv('GIT_TOKEN') + username = os.getenv('GIT_USERNAME', username) + password = os.getenv('GIT_PASSWORD', password) + token = os.getenv('GIT_TOKEN', token) if token: + logger.debug('Using git token for authentication') self._github = Github(base_url=self.github_api_url, login_or_token=token) else: - logger.debug('Using git username and password') - self._github = Github(base_url=self.github_api_url, login_or_token=os.getenv('GIT_USERNAME'), password=os.getenv('GIT_PASSWORD')) + logger.debug('Using git username and password for authentication') + self._github = Github(base_url=self.github_api_url, login_or_token=username, password=password) if files is None: files = []