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/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 diff --git a/actions/create_branch.py b/actions/create_branch.py new file mode 100644 index 0000000..57ebb38 --- /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/ref/{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..e3e03b0 --- /dev/null +++ b/actions/create_branch.yaml @@ -0,0 +1,32 @@ +--- +name: "create_branch" +runner_type: "python-script" +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: + 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/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/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..a605ef3 --- /dev/null +++ b/actions/delete_branch.yaml @@ -0,0 +1,28 @@ +--- +name: "delete_branch" +runner_type: "python-script" +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: + 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..be6755d --- /dev/null +++ b/actions/get_branch.yaml @@ -0,0 +1,28 @@ +--- +name: "get_branch" +runner_type: "python-script" +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: + 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 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