From 394f93f8ab540c3e3874f3f0182f4333e55ea923 Mon Sep 17 00:00:00 2001 From: Guilherme Pim Date: Tue, 21 Jun 2022 18:01:02 -0300 Subject: [PATCH 1/4] create/get/delete branches :) --- actions/create_branch.py | 40 ++++++++++++++++++++++++++++++++++++++ actions/create_branch.yaml | 29 +++++++++++++++++++++++++++ actions/delete_branch.py | 24 +++++++++++++++++++++++ actions/delete_branch.yaml | 25 ++++++++++++++++++++++++ actions/get_branch.py | 26 +++++++++++++++++++++++++ actions/get_branch.yaml | 25 ++++++++++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 actions/create_branch.py create mode 100644 actions/create_branch.yaml create mode 100644 actions/delete_branch.py create mode 100644 actions/delete_branch.yaml create mode 100644 actions/get_branch.py create mode 100644 actions/get_branch.yaml diff --git a/actions/create_branch.py b/actions/create_branch.py new file mode 100644 index 0000000..35b7bda --- /dev/null +++ b/actions/create_branch.py @@ -0,0 +1,40 @@ +import time +import datetime + + +from lib.base import BaseGithubAction + +__all__ = [ + 'CreateBranchAction' +] + +class CreateBranchAction(BaseGithubAction): + def run(self, api_user, new_branch, origin_ref, repository, github_type): + + enterprise = self._is_enterprise(github_type) + + if api_user: + self.token = self._get_user_token(api_user, enterprise) + + + # First, we have to get the sha1 for the given origin ref + response = self._request("GET", f"/repos/{repository}/git/refs/{origin_ref}", + {}, + self.token, + enterprise) + + if not response or not response['object']['sha']: + raise Exception(f"Could not get ref [{origin_ref}]. Response: {response}") + + + # Then, we create the branch based on the origin ref + payload = { "ref": f"refs/heads/{new_branch}", + "sha": response['object']['sha']} + + response = self._request("POST", + f"/repos/{repository}/git/refs", + payload, + self.token, + enterprise) + + return { 'response': response } diff --git a/actions/create_branch.yaml b/actions/create_branch.yaml new file mode 100644 index 0000000..f4cdd2a --- /dev/null +++ b/actions/create_branch.yaml @@ -0,0 +1,29 @@ +--- +name: "create_branch" +runner_type: "python-script" +description: "Create a new branch for a GitHub repository" +enabled: true +entry_point: "create_branch.py" +parameters: + api_user: + type: "string" + description: "The API user" + default: "{{action_context.api_user|default(None)}}" + repository: + type: "string" + description: "The full (Organization|User)/repository path" + required: true + origin_ref: + type: "string" + description: "The current reference to branch from (e.g. heads/master, heads/main)" + default: "heads/master" + new_branch: + type: "string" + description: "The branch to be created from the given ref" + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + default: enterprise + enum: + - enterprise + - online diff --git a/actions/delete_branch.py b/actions/delete_branch.py new file mode 100644 index 0000000..c8b1d9e --- /dev/null +++ b/actions/delete_branch.py @@ -0,0 +1,24 @@ +import time +import datetime + + +from lib.base import BaseGithubAction + +__all__ = [ + 'DeleteBranchAction' +] + +class DeleteBranchAction(BaseGithubAction): + def run(self, api_user, branch, repository, github_type): + + enterprise = self._is_enterprise(github_type) + + if api_user: + self.token = self._get_user_token(api_user, enterprise) + + response = self._request("DELETE", f"/repos/{repository}/git/refs/heads/{branch}", + {}, + self.token, + enterprise) + + return { 'response': response } diff --git a/actions/delete_branch.yaml b/actions/delete_branch.yaml new file mode 100644 index 0000000..36a0258 --- /dev/null +++ b/actions/delete_branch.yaml @@ -0,0 +1,25 @@ +--- +name: "delete_branch" +runner_type: "python-script" +description: "Deletes a branch from a GitHub repository" +enabled: true +entry_point: "delete_branch.py" +parameters: + api_user: + type: "string" + description: "The API user" + default: "{{action_context.api_user|default(None)}}" + repository: + type: "string" + description: "The full (Organization|User)/repository path" + required: true + branch: + type: "string" + description: "The branch to be created from the given ref" + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + default: enterprise + enum: + - enterprise + - online diff --git a/actions/get_branch.py b/actions/get_branch.py new file mode 100644 index 0000000..4e846f0 --- /dev/null +++ b/actions/get_branch.py @@ -0,0 +1,26 @@ +import time +import datetime + + +from lib.base import BaseGithubAction + +__all__ = [ + 'GetBranchAction' +] + +class GetBranchAction(BaseGithubAction): + def run(self, api_user, branch, repository, github_type): + + enterprise = self._is_enterprise(github_type) + + if api_user: + self.token = self._get_user_token(api_user, enterprise) + + + # First, we have to get the sha1 for the given origin ref + response = self._request("GET", f"/repos/{repository}/git/ref/heads/{branch}", + {}, + self.token, + enterprise) + + return { 'response': response } diff --git a/actions/get_branch.yaml b/actions/get_branch.yaml new file mode 100644 index 0000000..e970dd1 --- /dev/null +++ b/actions/get_branch.yaml @@ -0,0 +1,25 @@ +--- +name: "get_branch" +runner_type: "python-script" +description: "Gets branch details from a GitHub repository" +enabled: true +entry_point: "get_branch.py" +parameters: + api_user: + type: "string" + description: "The API user" + default: "{{action_context.api_user|default(None)}}" + repository: + type: "string" + description: "The full (Organization|User)/repository path" + required: true + branch: + type: "string" + description: "The name of the branch to fetch details for" + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + default: enterprise + enum: + - enterprise + - online From 929a01891539fccf411450815c9394c72158a74f Mon Sep 17 00:00:00 2001 From: Guilherme Pim Date: Tue, 21 Jun 2022 18:08:41 -0300 Subject: [PATCH 2/4] documentation --- CHANGES.md | 1 + actions/create_branch.yaml | 5 ++++- actions/delete_branch.yaml | 5 ++++- actions/get_branch.yaml | 5 ++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 545a01c..0e15138 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ * Add new ``github.create_repository_from_template`` action which allows user to create a repository from template. * Bug fix on ``github.store_oauth_token.`` to api save the token correctly so that it can be read later. * Segure improvement on ``github.store_oauth_token.`` to encrypt de github token in web interface. +* Add new ``github.create_branch``, ``github.get_branch``, ``github.delete_branch`` actions which allows user to create/get/delete a branch. ## 2.1.1 diff --git a/actions/create_branch.yaml b/actions/create_branch.yaml index f4cdd2a..e3e03b0 100644 --- a/actions/create_branch.yaml +++ b/actions/create_branch.yaml @@ -1,7 +1,10 @@ --- name: "create_branch" runner_type: "python-script" -description: "Create a new branch for a GitHub repository" +description: > + Create a new branch for a GitHub repository + Example: + st2 run github.create_branch repository="reponame" origin_ref="heads/" new_branch="branch_name" api_user="token_name" enabled: true entry_point: "create_branch.py" parameters: diff --git a/actions/delete_branch.yaml b/actions/delete_branch.yaml index 36a0258..a605ef3 100644 --- a/actions/delete_branch.yaml +++ b/actions/delete_branch.yaml @@ -1,7 +1,10 @@ --- name: "delete_branch" runner_type: "python-script" -description: "Deletes a branch from a GitHub repository" +description: > + Deletes a branch from a GitHub repository + Example: + st2 run github.delete_branch repository="reponame" branch="branch_name" api_user="token_name" enabled: true entry_point: "delete_branch.py" parameters: diff --git a/actions/get_branch.yaml b/actions/get_branch.yaml index e970dd1..be6755d 100644 --- a/actions/get_branch.yaml +++ b/actions/get_branch.yaml @@ -1,7 +1,10 @@ --- name: "get_branch" runner_type: "python-script" -description: "Gets branch details from a GitHub repository" +description: > + Gets branch details from a GitHub repository + Example: + st2 run github.get_branch repository="reponame" branch="branch_name" api_user="token_name" enabled: true entry_point: "get_branch.py" parameters: From a55d1ee24bfff93e3c3335597f9a8834b1a4679f Mon Sep 17 00:00:00 2001 From: Guilherme Pim Date: Wed, 22 Jun 2022 17:21:54 -0300 Subject: [PATCH 3/4] fixes --- actions/create_branch.py | 2 +- actions/create_file.py | 3 ++- actions/create_file.yaml | 12 ++++++++++++ actions/create_pull.py | 4 +++- actions/create_pull.yaml | 12 ++++++++++++ actions/get_user.py | 4 +--- actions/lib/base.py | 15 +++++++++++++++ actions/update_file.py | 3 ++- actions/update_file.yaml | 11 +++++++++++ 9 files changed, 59 insertions(+), 7 deletions(-) diff --git a/actions/create_branch.py b/actions/create_branch.py index 35b7bda..57ebb38 100644 --- a/actions/create_branch.py +++ b/actions/create_branch.py @@ -18,7 +18,7 @@ def run(self, api_user, new_branch, origin_ref, repository, github_type): # First, we have to get the sha1 for the given origin ref - response = self._request("GET", f"/repos/{repository}/git/refs/{origin_ref}", + response = self._request("GET", f"/repos/{repository}/git/ref/{origin_ref}", {}, self.token, enterprise) diff --git a/actions/create_file.py b/actions/create_file.py index d705fcb..5567969 100644 --- a/actions/create_file.py +++ b/actions/create_file.py @@ -8,8 +8,9 @@ class CreateFileAction(BaseGithubAction): - def run(self, user, repo, path, message, content, branch=None, committer=None, author=None, + def run(self, user, repo, path, message, content, github_type, api_user, branch=None, committer=None, author=None, encoding=None): + self._change_to_user_token_if_enterprise(api_user, github_type) author, branch, committer = prep_github_params_for_file_ops(author, branch, committer) if encoding and encoding == 'base64': diff --git a/actions/create_file.yaml b/actions/create_file.yaml index bb5281c..ddadda1 100644 --- a/actions/create_file.yaml +++ b/actions/create_file.yaml @@ -45,3 +45,15 @@ parameters: type: "string" description: "If omitted this will be filled in with committer information. If passed, you must specify both a name and email. Expected format: FirstName LastName " required: false + + api_user: + type: "string" + description: "The" + default: "{{action_context.api_user|default(None)}}" + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + default: ~ + enum: + - enterprise + - online \ No newline at end of file diff --git a/actions/create_pull.py b/actions/create_pull.py index 5274d95..965c508 100644 --- a/actions/create_pull.py +++ b/actions/create_pull.py @@ -7,7 +7,9 @@ class CreatePullAction(BaseGithubAction): - def run(self, user, repo, title, body, head, base): + def run(self, user, repo, title, body, head, base, api_user, github_type): + self._change_to_user_token_if_enterprise(api_user, github_type) + user = self._client.get_user(user) repo = user.get_repo(repo) pull = repo.create_pull(title=title, body=body, head=head, base=base) diff --git a/actions/create_pull.yaml b/actions/create_pull.yaml index d1caf79..ca10588 100644 --- a/actions/create_pull.yaml +++ b/actions/create_pull.yaml @@ -32,3 +32,15 @@ parameters: type: "string" description: "The name of the branch you want the changes pulled into. This should be an existing branch on the current repository. You cannot submit a pull request to one repository that requests a merge to a base of another repository." required: true + api_user: + type: "string" + description: "The" + default: "{{action_context.api_user|default(None)}}" + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + default: ~ + enum: + - enterprise + - online + diff --git a/actions/get_user.py b/actions/get_user.py index 9b25dd0..1a50a51 100644 --- a/actions/get_user.py +++ b/actions/get_user.py @@ -8,9 +8,7 @@ class GetUserAction(BaseGithubAction): def run(self, user, token_user, github_type): - enterprise = self._is_enterprise(github_type) - if token_user: - self._change_to_user_token(token_user, enterprise) + self._change_to_user_token_if_enterprise(token_user, github_type) user = self._client.get_user(user) result = user_to_dict(user=user) diff --git a/actions/lib/base.py b/actions/lib/base.py index 22b0716..9169096 100644 --- a/actions/lib/base.py +++ b/actions/lib/base.py @@ -2,6 +2,7 @@ import requests from bs4 import BeautifulSoup import json +import logging from st2common.runners.base_action import Action @@ -18,6 +19,7 @@ class BaseGithubAction(Action): + def run(self, **kwargs): pass @@ -70,6 +72,8 @@ def _get_analytics(self, category, repo, enterprise): response = s.get(url) return response.json() + # Whether or not this execution is meant for enterprise github installation (on-premises) + # or online installations (in the cloud) def _is_enterprise(self, github_type): if github_type == "enterprise": @@ -83,6 +87,8 @@ def _is_enterprise(self, github_type): else: raise ValueError("Default GitHub Invalid!") + # Github token will come from KV using this function.. and depending on whether + # it's for enterprise or not, it will return have either of the key prefix below def _get_user_token(self, user, enterprise): """ Return a users GitHub OAuth Token, if it fails replace '-' @@ -104,7 +110,15 @@ def _get_user_token(self, user, enterprise): return token + def _change_to_user_token_if_enterprise(self, api_user, github_type): + enterprise = self._is_enterprise(github_type) + if api_user: + self._change_to_user_token(api_user, enterprise) + + # Changes the internal client used on this instance of action execution to + # the one matching the configuration for enterprise/online and user given here def _change_to_user_token(self, user, enterprise): + logging.debug("Changing github client for user [%s] and enterprise [%s]", user, enterprise) token = self._get_user_token(user, enterprise) if enterprise: @@ -114,6 +128,7 @@ def _change_to_user_token(self, user, enterprise): return True + # Sends a generic HTTP/s request to the github endpoint def _request(self, method, uri, payload, token, enterprise): headers = {'Authorization': 'token {}'.format(token)} diff --git a/actions/update_file.py b/actions/update_file.py index b36fc45..48d1b5e 100644 --- a/actions/update_file.py +++ b/actions/update_file.py @@ -8,8 +8,9 @@ class UpdateFileAction(BaseGithubAction): - def run(self, user, repo, path, message, content, sha, branch=None, committer=None, + def run(self, user, repo, path, message, content, sha, api_user, github_type, branch=None, committer=None, author=None, encoding=None): + self._change_to_user_token_if_enterprise(api_user, github_type) author, branch, committer = prep_github_params_for_file_ops(author, branch, committer) if encoding and encoding == 'base64': diff --git a/actions/update_file.yaml b/actions/update_file.yaml index bdbaa62..de31537 100644 --- a/actions/update_file.yaml +++ b/actions/update_file.yaml @@ -49,3 +49,14 @@ parameters: type: "string" description: "If omitted this will be filled in with committer information. If passed, you must specify both a name and email. Expected format: FirstName LastName " required: false + api_user: + type: "string" + description: "The" + default: "{{action_context.api_user|default(None)}}" + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + default: ~ + enum: + - enterprise + - online From a376c7a5e5f05befe4219bb253bce5a17dca479d Mon Sep 17 00:00:00 2001 From: Guilherme Pim Date: Fri, 24 Jun 2022 14:11:35 -0300 Subject: [PATCH 4/4] adding missing repository actions --- actions/add_update_repository_team.py | 29 +++++++++++++++ actions/add_update_repository_team.yaml | 48 +++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 actions/add_update_repository_team.py create mode 100644 actions/add_update_repository_team.yaml diff --git a/actions/add_update_repository_team.py b/actions/add_update_repository_team.py new file mode 100644 index 0000000..93f8e14 --- /dev/null +++ b/actions/add_update_repository_team.py @@ -0,0 +1,29 @@ +import time +import datetime + + +from lib.base import BaseGithubAction + +__all__ = [ + 'AddUpdateRepositoryTeamAction' +] + +class AddUpdateRepositoryTeamAction(BaseGithubAction): + def run(self, api_user, org, team_slug, owner, repo, github_type, permission ): + + enterprise = self._is_enterprise(github_type) + + if api_user: + self.token = self._get_user_token(api_user, enterprise) + + payload = { "permission": permission } + + response = self._request("PUT", + "/orgs/{}/teams/{}/repos/{}/{}".format(org,team_slug,owner,repo ), + payload, + self.token, + enterprise) + + results = {'response': response} + + return results diff --git a/actions/add_update_repository_team.yaml b/actions/add_update_repository_team.yaml new file mode 100644 index 0000000..fd5fef0 --- /dev/null +++ b/actions/add_update_repository_team.yaml @@ -0,0 +1,48 @@ +--- +name: add_update_repository_team +runner_type: python-script +pack: github +description: > + Add or update repository team. + Example: + st2 run github.add_update_repository_team organization="organization" owner="owner" repo="reponame" team_slug="team_id" api_user="token_name" +enabled: true +entry_point: add_update_repository_team.py +parameters: + api_user: + type: "string" + description: "The API user" + default: "{{action_context.api_user|default(None)}}" + org: + type: "string" + description: "The organization name. The name is not case sensitive." + required: true + team_slug: + type: "string" + description: "The slug of the team name." + required: true + owner: + type: "string" + description: "The account owner of the repository. The name is not case sensitive." + required: true + repo: + type: "string" + description: "The name of the repository. The name is not case sensitive." + required: true + github_type: + type: "string" + description: "The type of github installation to target, if unset will use the configured default." + enum: + - "online" + - "enterprise" + default: "enterprise" + permission: + type: "string" + description: "The permission to grant the team on this repository. In addition to the enumerated values, you can also specify a custom repository role name, if the owning organization has defined any. If no permission is specified, the team's permission attribute will be used to determine what permission to grant the team on this repository." + enum: + - "pull" + - "push" + - "admin" + - "maintain" + - "triage" + default: "push" \ No newline at end of file