Browse Source
Co-authored-by: Jiajie Zhong <zhongjiajie955@gmail.com> Co-authored-by: JieguangZhou <jieguang_zhou@163.com> Co-authored-by: Devosend <devosend@gmail.com> Co-authored-by: juzimao <578961953@qq.com>3.2.0-release
chenrj
2 years ago
committed by
GitHub
11 changed files with 536 additions and 7 deletions
@ -0,0 +1,35 @@
|
||||
.. Licensed to the Apache Software Foundation (ASF) under one |
||||
or more contributor license agreements. See the NOTICE file |
||||
distributed with this work for additional information |
||||
regarding copyright ownership. The ASF licenses this file |
||||
to you 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. |
||||
|
||||
GitHub |
||||
====== |
||||
|
||||
`GitHub` is a github resource plugin for pydolphinscheduler. |
||||
|
||||
When using a github resource plugin, you only need to add the `resource_plugin` parameter in the task subclass or workflow definition, |
||||
such as `resource_plugin=GitHub(prefix="https://github.com/xxx", access_token="ghpxx")`. |
||||
The token parameter is optional. You need to add it when your warehouse is a private repository. |
||||
|
||||
You can view this `document <https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token>`_ |
||||
when creating a token. |
||||
|
||||
For the specific use of resource plugins, you can see `How to use` in :doc:`resource-plugin` |
||||
|
||||
Dive Into |
||||
--------- |
||||
|
||||
.. automodule:: pydolphinscheduler.resources_plugin.github |
@ -0,0 +1,18 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you 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. |
||||
|
||||
"""Init base package.""" |
@ -0,0 +1,91 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you 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. |
||||
|
||||
"""DolphinScheduler GitFileInfo and Git object.""" |
||||
|
||||
from abc import ABCMeta, abstractmethod |
||||
from typing import Optional |
||||
|
||||
|
||||
class GitFileInfo: |
||||
"""A class that defines the details of GIT files. |
||||
|
||||
:param user: A string representing the user the git file belongs to. |
||||
:param repo_name: A string representing the repository to which the git file belongs. |
||||
:param branch: A string representing the branch to which the git file belongs. |
||||
:param file_path: A string representing the git file path. |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
user: Optional[str] = None, |
||||
repo_name: Optional[str] = None, |
||||
branch: Optional[str] = None, |
||||
file_path: Optional[str] = None, |
||||
*args, |
||||
**kwargs |
||||
): |
||||
self.user = user |
||||
self.repo_name = repo_name |
||||
self.branch = branch |
||||
self.file_path = file_path |
||||
|
||||
|
||||
class GitHubFileInfo(GitFileInfo): |
||||
"""A class that defines the details of GitHub files. |
||||
|
||||
:param user: A string representing the user the GitHub file belongs to. |
||||
:param repo_name: A string representing the repository to which the GitHub file belongs. |
||||
:param branch: A string representing the branch to which the GitHub file belongs. |
||||
:param file_path: A string representing the GitHub file path. |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
user: Optional[str] = None, |
||||
repo_name: Optional[str] = None, |
||||
branch: Optional[str] = None, |
||||
file_path: Optional[str] = None, |
||||
*args, |
||||
**kwargs |
||||
): |
||||
super().__init__( |
||||
user=user, |
||||
repo_name=repo_name, |
||||
branch=branch, |
||||
file_path=file_path, |
||||
*args, |
||||
**kwargs |
||||
) |
||||
|
||||
|
||||
# [start Git] |
||||
class Git(object, metaclass=ABCMeta): |
||||
"""An abstract class of online code warehouse based on git implementation.""" |
||||
|
||||
_git_file_info: Optional[GitFileInfo] = None |
||||
|
||||
# [start abstractmethod git_file_info] |
||||
@abstractmethod |
||||
def get_git_file_info(self, path: str): |
||||
"""Get the detailed information of GIT file according to the file URL.""" |
||||
raise NotImplementedError |
||||
|
||||
# [end abstractmethod git_file_info] |
||||
|
||||
|
||||
# [end Git] |
@ -0,0 +1,103 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you 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. |
||||
|
||||
"""DolphinScheduler github resource plugin.""" |
||||
import base64 |
||||
from typing import Optional |
||||
from urllib.parse import urljoin |
||||
|
||||
import requests |
||||
|
||||
from pydolphinscheduler.core.resource_plugin import ResourcePlugin |
||||
from pydolphinscheduler.resources_plugin.base.git import Git, GitHubFileInfo |
||||
|
||||
|
||||
class GitHub(ResourcePlugin, Git): |
||||
"""GitHub resource plugin, a plugin for task and workflow to dolphinscheduler to read resource. |
||||
|
||||
:param prefix: A string representing the prefix of GitHub. |
||||
:param access_token: A string used for identity authentication of GitHub private repository. |
||||
""" |
||||
|
||||
def __init__( |
||||
self, prefix: str, access_token: Optional[str] = None, *args, **kwargs |
||||
): |
||||
super().__init__(prefix, *args, **kwargs) |
||||
self.access_token = access_token |
||||
|
||||
def build_req_api( |
||||
self, |
||||
user: str, |
||||
repo_name: str, |
||||
file_path: str, |
||||
api: str, |
||||
): |
||||
"""Build request file content API.""" |
||||
api = api.replace("{user}", user) |
||||
api = api.replace("{repo_name}", repo_name) |
||||
api = api.replace("{file_path}", file_path) |
||||
return api |
||||
|
||||
def get_git_file_info(self, path: str): |
||||
"""Get file information from the file url, like repository name, user, branch, and file path.""" |
||||
elements = path.split("/") |
||||
index = self.get_index(path, "/", 7) |
||||
index = index + 1 |
||||
file_info = GitHubFileInfo( |
||||
user=elements[3], |
||||
repo_name=elements[4], |
||||
branch=elements[6], |
||||
file_path=path[index:], |
||||
) |
||||
self._git_file_info = file_info |
||||
|
||||
def get_req_url(self): |
||||
"""Build request URL according to file information.""" |
||||
return self.build_req_api( |
||||
user=self._git_file_info.user, |
||||
repo_name=self._git_file_info.repo_name, |
||||
file_path=self._git_file_info.file_path, |
||||
api="https://api.github.com/repos/{user}/{repo_name}/contents/{file_path}", |
||||
) |
||||
|
||||
def read_file(self, suf: str): |
||||
"""Get the content of the file. |
||||
|
||||
The address of the file is the prefix of the resource plugin plus the parameter suf. |
||||
""" |
||||
path = urljoin(self.prefix, suf) |
||||
return self.req(path) |
||||
|
||||
def req(self, path: str): |
||||
"""Send HTTP request, parse response data, and get file content.""" |
||||
headers = { |
||||
"Content-Type": "application/json; charset=utf-8", |
||||
} |
||||
if self.access_token is not None: |
||||
headers.setdefault("Authorization", "Bearer %s" % self.access_token) |
||||
self.get_git_file_info(path) |
||||
response = requests.get( |
||||
headers=headers, |
||||
url=self.get_req_url(), |
||||
params={"ref": self._git_file_info.branch}, |
||||
) |
||||
if response.status_code == requests.codes.ok: |
||||
json_response = response.json() |
||||
content = base64.b64decode(json_response["content"]) |
||||
return content.decode("utf-8") |
||||
else: |
||||
raise Exception(response.json()) |
@ -0,0 +1,195 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you 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. |
||||
|
||||
"""Test github resource plugin.""" |
||||
from unittest.mock import PropertyMock, patch |
||||
|
||||
import pytest |
||||
|
||||
from pydolphinscheduler.resources_plugin import GitHub |
||||
from pydolphinscheduler.resources_plugin.base.git import GitFileInfo |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"attr, expected", |
||||
[ |
||||
( |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"file_path": "script/install.sh", |
||||
"api": "https://api.github.com/repos/{user}/{repo_name}/contents/{file_path}", |
||||
}, |
||||
"https://api.github.com/repos/apache/dolphinscheduler/contents/script/install.sh", |
||||
), |
||||
], |
||||
) |
||||
def test_github_build_req_api(attr, expected): |
||||
"""Test the build_req_api function of the github resource plug-in.""" |
||||
github = GitHub(prefix="prefix") |
||||
assert expected == github.build_req_api(**attr) |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"attr, expected", |
||||
[ |
||||
( |
||||
"https://github.com/apache/dolphinscheduler/blob/dev/script/install.sh", |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"branch": "dev", |
||||
"file_path": "script/install.sh", |
||||
}, |
||||
), |
||||
( |
||||
"https://github.com/apache/dolphinscheduler/blob/master/pom.xml", |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"branch": "master", |
||||
"file_path": "pom.xml", |
||||
}, |
||||
), |
||||
( |
||||
"https://github.com/apache/dolphinscheduler/blob/1.3.9-release/docker/build/startup.sh", |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"branch": "1.3.9-release", |
||||
"file_path": "docker/build/startup.sh", |
||||
}, |
||||
), |
||||
], |
||||
) |
||||
def test_github_get_git_file_info(attr, expected): |
||||
"""Test the get_git_file_info function of the github resource plug-in.""" |
||||
github = GitHub(prefix="prefix") |
||||
github.get_git_file_info(attr) |
||||
assert expected == github._git_file_info.__dict__ |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"attr, expected", |
||||
[ |
||||
( |
||||
( |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"file_path": "docker/build/startup.sh", |
||||
} |
||||
), |
||||
"https://api.github.com/repos/apache/dolphinscheduler/contents/docker/build/startup.sh", |
||||
), |
||||
( |
||||
( |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"file_path": "pom.xml", |
||||
} |
||||
), |
||||
"https://api.github.com/repos/apache/dolphinscheduler/contents/pom.xml", |
||||
), |
||||
( |
||||
( |
||||
{ |
||||
"user": "apache", |
||||
"repo_name": "dolphinscheduler", |
||||
"file_path": "script/create-dolphinscheduler.sh", |
||||
} |
||||
), |
||||
"https://api.github.com/repos/apache/dolphinscheduler/contents/script/create-dolphinscheduler.sh", |
||||
), |
||||
], |
||||
) |
||||
@patch( |
||||
"pydolphinscheduler.resources_plugin.github.GitHub._git_file_info", |
||||
new_callable=PropertyMock, |
||||
) |
||||
def test_github_get_req_url(m_git_file_info, attr, expected): |
||||
"""Test the get_req_url function of the github resource plug-in.""" |
||||
github = GitHub(prefix="prefix") |
||||
m_git_file_info.return_value = GitFileInfo(**attr) |
||||
assert expected == github.get_req_url() |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"attr, expected", |
||||
[ |
||||
( |
||||
{ |
||||
"init": {"prefix": "prefix", "access_token": "access_token"}, |
||||
"file_path": "github_resource_plugin.sh", |
||||
"file_content": "github resource plugin", |
||||
}, |
||||
"github resource plugin", |
||||
), |
||||
( |
||||
{ |
||||
"init": { |
||||
"prefix": "prefix", |
||||
}, |
||||
"file_path": "github_resource_plugin.sh", |
||||
"file_content": "github resource plugin", |
||||
}, |
||||
"github resource plugin", |
||||
), |
||||
], |
||||
) |
||||
@patch("pydolphinscheduler.resources_plugin.github.GitHub.req") |
||||
def test_github_read_file(m_req, attr, expected): |
||||
"""Test the read_file function of the github resource plug-in.""" |
||||
github = GitHub(**attr.get("init")) |
||||
m_req.return_value = attr.get("file_content") |
||||
assert expected == github.read_file(attr.get("file_path")) |
||||
|
||||
|
||||
@pytest.mark.skip(reason="Lack of test environment, need stable repository") |
||||
@pytest.mark.parametrize( |
||||
"attr, expected", |
||||
[ |
||||
( |
||||
"https://github.com/apache/dolphinscheduler/blob/dev/lombok.config", |
||||
"#\n" |
||||
"# Licensed to the Apache Software Foundation (ASF) under one or more\n" |
||||
"# contributor license agreements. See the NOTICE file distributed with\n" |
||||
"# this work for additional information regarding copyright ownership.\n" |
||||
"# The ASF licenses this file to You under the Apache License, Version 2.0\n" |
||||
'# (the "License"); you may not use this file except in compliance with\n' |
||||
"# the License. You may obtain a copy of the License at\n" |
||||
"#\n" |
||||
"# http://www.apache.org/licenses/LICENSE-2.0\n" |
||||
"#\n" |
||||
"# Unless required by applicable law or agreed to in writing, software\n" |
||||
'# distributed under the License is distributed on an "AS IS" BASIS,\n' |
||||
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" |
||||
"# See the License for the specific language governing permissions and\n" |
||||
"# limitations under the License.\n" |
||||
"#\n" |
||||
"\n" |
||||
"lombok.addLombokGeneratedAnnotation = true\n", |
||||
), |
||||
], |
||||
) |
||||
def test_github_req(attr, expected): |
||||
"""Test the req function of the github resource plug-in.""" |
||||
github = GitHub( |
||||
prefix="prefix", |
||||
) |
||||
assert expected == github.req(attr) |
@ -0,0 +1,75 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you 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. |
||||
|
||||
"""Test abstract class resource_plugin.""" |
||||
|
||||
import pytest |
||||
|
||||
from pydolphinscheduler.exceptions import PyResPluginException |
||||
from pydolphinscheduler.resources_plugin import GitHub |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"attr, expected", |
||||
[ |
||||
( |
||||
{ |
||||
"s": "https://api.github.com/repos/apache/dolphinscheduler/contents/script/install.sh", |
||||
"x": "/", |
||||
"n": 2, |
||||
}, |
||||
7, |
||||
), |
||||
( |
||||
{ |
||||
"s": "https://api.github.com", |
||||
"x": ":", |
||||
"n": 1, |
||||
}, |
||||
5, |
||||
), |
||||
], |
||||
) |
||||
def test_github_get_index(attr, expected): |
||||
"""Test the get_index function of the abstract class resource_plugin.""" |
||||
github = GitHub(prefix="prefix") |
||||
assert expected == github.get_index(**attr) |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"attr", |
||||
[ |
||||
{ |
||||
"s": "https://api.github.com", |
||||
"x": "/", |
||||
"n": 3, |
||||
}, |
||||
{ |
||||
"s": "https://api.github.com/", |
||||
"x": "/", |
||||
"n": 4, |
||||
}, |
||||
], |
||||
) |
||||
def test_github_get_index_exception(attr): |
||||
"""Test exception to get_index function of abstract class resource_plugin.""" |
||||
with pytest.raises( |
||||
PyResPluginException, |
||||
match="Incomplete path.", |
||||
): |
||||
github = GitHub(prefix="prefix") |
||||
github.get_index(**attr) |
Loading…
Reference in new issue