Skip to content

Commit

Permalink
Add github actions secrets to org
Browse files Browse the repository at this point in the history
  • Loading branch information
peresypkinamarina committed Oct 12, 2021
1 parent 83d6e07 commit 71c5d2c
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 0 deletions.
67 changes: 67 additions & 0 deletions github/Organization.py
Expand Up @@ -582,6 +582,49 @@ def create_repo(
self._requester, headers, data, completed=True
)

def create_secret(
self,
secret_name,
unencrypted_value,
visibility="all",
selected_repositories=github.GithubObject.NotSet,
):
"""
:calls: `PUT /orgs/{org}/actions/secrets/{secret_name} <https://docs.github.com/en/rest/reference/actions#create-or-update-an-organization-secret>`_
:param secret_name: string
:param unencrypted_value: string
:param visibility: string
:param selected_repositories: list of :class:`github.Repository.Repository`
:rtype: bool
"""
assert isinstance(secret_name, str), secret_name
assert isinstance(unencrypted_value, str), unencrypted_value
assert isinstance(visibility, str), visibility
if visibility == "selected":
assert isinstance(selected_repositories, list) and all(
isinstance(element, github.Repository.Repository)
for element in selected_repositories
), selected_repositories
else:
assert selected_repositories is github.GithubObject.NotSet

public_key = self.get_public_key()
payload = public_key.encrypt(unencrypted_value)
put_parameters = {
"key_id": public_key.key_id,
"encrypted_value": payload,
"visibility": visibility,
}
if selected_repositories is not github.GithubObject.NotSet:
put_parameters["selected_repository_ids"] = [
element.id for element in selected_repositories
]

status, headers, data = self._requester.requestJson(
"PUT", f"{self.url}/actions/secrets/{secret_name}", input=put_parameters
)
return status == 201

def create_team(
self,
name,
Expand Down Expand Up @@ -641,6 +684,18 @@ def delete_hook(self, id):
"DELETE", f"{self.url}/hooks/{id}"
)

def delete_secret(self, secret_name):
"""
:calls: `DELETE /orgs/{org}/actions/secrets/{secret_name} <https://docs.github.com/en/rest/reference/actions#delete-an-organization-secret>`_
:param secret_name: string
:rtype: bool
"""
assert isinstance(secret_name, str), secret_name
status, headers, data = self._requester.requestJson(
"DELETE", f"{self.url}/actions/secrets/{secret_name}"
)
return status == 204

def edit(
self,
billing_email=github.GithubObject.NotSet,
Expand Down Expand Up @@ -912,6 +967,18 @@ def convert_to_outside_collaborator(self, member):
"PUT", f"{self.url}/outside_collaborators/{member._identity}"
)

def get_public_key(self):
"""
:calls: `GET /orgs/{org}/actions/secrets/public-key <https://docs.github.com/en/rest/reference/actions#get-an-organization-public-key>`_
:rtype: :class:`github.PublicKey.PublicKey`
"""
headers, data = self._requester.requestJsonAndCheck(
"GET", f"{self.url}/actions/secrets/public-key"
)
return github.PublicKey.PublicKey(
self._requester, headers, data, completed=True
)

def get_repo(self, name):
"""
:calls: `GET /repos/{owner}/{repo} <http://docs.github.com/en/rest/reference/repos>`_
Expand Down
9 changes: 9 additions & 0 deletions github/Organization.pyi
Expand Up @@ -66,6 +66,13 @@ class Organization(CompletableGithubObject):
allow_merge_commit: Union[bool, _NotSetType] = ...,
allow_rebase_merge: Union[bool, _NotSetType] = ...,
) -> Repository: ...
def create_secret(
self,
secret_name: str,
unencrypted_value: str,
visibility: str = ...,
selected_repositories: Union[List[Repository], _NotSetType] = ...,
) -> bool: ...
def create_team(
self,
name: str,
Expand All @@ -79,6 +86,7 @@ class Organization(CompletableGithubObject):
def delete_hook(self, id: int) -> None: ...
@property
def default_repository_permission(self) -> str: ...
def delete_secret(self, secret_name: str) -> bool: ...
@property
def description(self) -> str: ...
@property
Expand Down Expand Up @@ -133,6 +141,7 @@ class Organization(CompletableGithubObject):
def get_projects(
self, state: Union[_NotSetType, str] = ...
) -> PaginatedList[Project]: ...
def get_public_key(self) -> PublicKey: ...
def get_public_members(self) -> PaginatedList[NamedUser]: ...
def get_repo(self, name: str) -> Repository: ...
def get_repos(
Expand Down
19 changes: 19 additions & 0 deletions tests/Organization.py
Expand Up @@ -33,6 +33,7 @@
################################################################################

import datetime
from unittest import mock

import github

Expand Down Expand Up @@ -356,6 +357,24 @@ def testCreateFork(self):
self.assertFalse(repo.has_wiki)
self.assertFalse(repo.has_pages)

@mock.patch("github.PublicKey.encrypt")
def testCreateSecret(self, encrypt):
# encrypt returns a non-deterministic value, we need to mock it so the replay data matches
encrypt.return_value = "M+5Fm/BqTfB90h3nC7F3BoZuu3nXs+/KtpXwxm9gG211tbRo0F5UiN0OIfYT83CKcx9oKES9Va4E96/b"
self.assertTrue(self.org.create_secret("secret-name", "secret-value", "all"))

@mock.patch("github.PublicKey.encrypt")
def testCreateSecretSelected(self, encrypt):
# encrypt returns a non-deterministic value, we need to mock it so the replay data matches
repos = [self.org.get_repo("TestPyGithub"), self.org.get_repo("FatherBeaver")]
encrypt.return_value = "M+5Fm/BqTfB90h3nC7F3BoZuu3nXs+/KtpXwxm9gG211tbRo0F5UiN0OIfYT83CKcx9oKES9Va4E96/b"
self.assertTrue(
self.org.create_secret("secret-name", "secret-value", "selected", repos)
)

def testDeleteSecret(self):
self.assertTrue(self.org.delete_secret("secret-name"))

def testInviteUserWithNeither(self):
with self.assertRaises(AssertionError) as raisedexp:
self.org.invite_user()
Expand Down
21 changes: 21 additions & 0 deletions tests/ReplayData/Organization.testCreateSecret.txt
@@ -0,0 +1,21 @@
https
GET
api.github.com
None
/orgs/BeaverSoftware/actions/secrets/public-key
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('status', '200 OK'), ('x-ratelimit-remaining', '4978'), ('content-length', '487'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"1dd282b50e691f8f162ef9355dad8771"'), ('date', 'Thu, 10 May 2012 19:03:19 GMT'), ('content-type', 'application/json; charset=utf-8')]
{"key": "u5e1Z25+z8pmgVVt5Pd8k0z/sKpVL1MXYtRAecE4vm8=", "key_id": "568250167242549743"}

https
PUT
api.github.com
None
/orgs/BeaverSoftware/actions/secrets/secret-name
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
{"encrypted_value": "M+5Fm/BqTfB90h3nC7F3BoZuu3nXs+/KtpXwxm9gG211tbRo0F5UiN0OIfYT83CKcx9oKES9Va4E96/b", "key_id": "568250167242549743", "visibility": "all"}
201
[('Date', 'Fri, 17 Apr 2020 00:12:33 GMT'), ('Server', 'GitHub.com'), ('Content-Length', '2'), ('Content-Type', 'application/json; charset=utf-8'), ('Status', '201 Created'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4984'), ('X-RateLimit-Reset', '1587085388'), ('X-OAuth-Scopes', 'read:org, repo, user'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Vary', 'Accept-Encoding, Accept, X-Requested-With'), ('X-GitHub-Request-Id', 'C290:52DA:50234:B404B:5E98F470')]
{}
43 changes: 43 additions & 0 deletions tests/ReplayData/Organization.testCreateSecretSelected.txt
@@ -0,0 +1,43 @@
https
GET
api.github.com
None
/repos/BeaverSoftware/TestPyGithub
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('status', '200 OK'), ('x-ratelimit-remaining', '4992'), ('content-length', '1431'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"4ecd2c151a469cfa6cd45e6beff1269b"'), ('date', 'Fri, 01 Jun 2012 19:40:56 GMT'), ('content-type', 'application/json; charset=utf-8')]
{"clone_url":"https://github.com/BeaverSoftware/TestPyGithub.git","has_downloads":true,"watchers":1,"git_url":"git://github.com/BeaverSoftware/TestPyGithub.git","updated_at":"2012-04-25T06:51:38Z","permissions":{"pull":true,"admin":true,"push":true},"homepage":"http://vincent-jacques.net/PyGithub","url":"https://api.github.com/repos/BeaverSoftware/TestPyGithub","has_wiki":true,"has_pages":false,"has_issues":false,"fork":false,"forks":0,"mirror_url":null,"size":112,"private":false,"open_issues":0,"svn_url":"https://github.com/BeaverSoftware/TestPyGithub","owner":{"url":"https://api.github.com/users/BeaverSoftware","gravatar_id":"d563e337cac2fdc644e2aaaad1e23266","login":"BeaverSoftware","id":1424031,"avatar_url":"https://secure.gravatar.com/avatar/d563e337cac2fdc644e2aaaad1e23266?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-orgs.png"},"name":"TestPyGithub","language":null,"description":"Guinea-pig for PyGithub testing","ssh_url":"git@github.com:BeaverSoftware/TestPyGithub.git","pushed_at":"2012-03-03T08:57:40Z","created_at":"2012-03-03T07:53:19Z","id":3609352,"html_url":"https://github.com/BeaverSoftware/TestPyGithub","full_name":"BeaverSoftware/TestPyGithub"}

https
GET
api.github.com
None
/repos/BeaverSoftware/FatherBeaver
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('status', '200 OK'), ('x-ratelimit-remaining', '4992'), ('content-length', '1431'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"4ecd2c151a469cfa6cd45e6beff1269b"'), ('date', 'Fri, 01 Jun 2012 19:40:56 GMT'), ('content-type', 'application/json; charset=utf-8')]
{"clone_url":"https://github.com/BeaverSoftware/FatherBeaver.git","has_downloads":true,"watchers":2,"git_url":"git://github.com/BeaverSoftware/FatherBeaver.git","updated_at":"2012-02-16T21:51:15Z","permissions":{"pull":true,"admin":true,"push":true},"homepage":"","url":"https://api.github.com/repos/BeaverSoftware/FatherBeaver","has_wiki":true,"has_pages":true,"has_issues":true,"fork":false,"forks":1,"mirror_url":null,"size":0,"private":false,"open_issues":0,"svn_url":"https://github.com/BeaverSoftware/FatherBeaver","owner":{"url":"https://api.github.com/users/BeaverSoftware","gravatar_id":"d563e337cac2fdc644e2aaaad1e23266","login":"BeaverSoftware","id":1424031,"avatar_url":"https://secure.gravatar.com/avatar/d563e337cac2fdc644e2aaaad1e23266?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-orgs.png"},"name":"FatherBeaver","language":null,"description":"","ssh_url":"git@github.com:BeaverSoftware/FatherBeaver.git","pushed_at":null,"created_at":"2012-02-09T19:32:21Z","id":3400397,"html_url":"https://github.com/BeaverSoftware/FatherBeaver","full_name":"BeaverSoftware/FatherBeaver"}

https
GET
api.github.com
None
/orgs/BeaverSoftware/actions/secrets/public-key
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('status', '200 OK'), ('x-ratelimit-remaining', '4978'), ('content-length', '487'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"1dd282b50e691f8f162ef9355dad8771"'), ('date', 'Thu, 10 May 2012 19:03:19 GMT'), ('content-type', 'application/json; charset=utf-8')]
{"key": "u5e1Z25+z8pmgVVt5Pd8k0z/sKpVL1MXYtRAecE4vm8=", "key_id": "568250167242549743"}

https
PUT
api.github.com
None
/orgs/BeaverSoftware/actions/secrets/secret-name
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
{"encrypted_value": "M+5Fm/BqTfB90h3nC7F3BoZuu3nXs+/KtpXwxm9gG211tbRo0F5UiN0OIfYT83CKcx9oKES9Va4E96/b", "key_id": "568250167242549743", "visibility": "selected", "selected_repository_ids": [3609352, 3400397]}
201
[('Date', 'Fri, 17 Apr 2020 00:12:33 GMT'), ('Server', 'GitHub.com'), ('Content-Length', '2'), ('Content-Type', 'application/json; charset=utf-8'), ('Status', '201 Created'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4984'), ('X-RateLimit-Reset', '1587085388'), ('X-OAuth-Scopes', 'read:org, repo, user'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Vary', 'Accept-Encoding, Accept, X-Requested-With'), ('X-GitHub-Request-Id', 'C290:52DA:50234:B404B:5E98F470')]
{}
10 changes: 10 additions & 0 deletions tests/ReplayData/Organization.testDeleteSecret.txt
@@ -0,0 +1,10 @@
https
DELETE
api.github.com
None
/orgs/BeaverSoftware/actions/secrets/secret-name
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
204
[('Date', 'Fri, 17 Apr 2020 00:12:33 GMT'), ('Server', 'GitHub.com'), ('Content-Length', '2'), ('Content-Type', 'application/json; charset=utf-8'), ('Status', '201 Created'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4984'), ('X-RateLimit-Reset', '1587085388'), ('X-OAuth-Scopes', 'read:org, repo, user'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Vary', 'Accept-Encoding, Accept, X-Requested-With'), ('X-GitHub-Request-Id', 'C290:52DA:50234:B404B:5E98F470')]
{}

0 comments on commit 71c5d2c

Please sign in to comment.