dusty/tools/actions/git_clone/action.py (108 lines of code) (raw):

#!/usr/bin/python3 # coding=utf-8 # Copyright 2019 getcarrier.io # # Licensed 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. """ Clone git repository """ import os import io import shutil import dulwich # pylint: disable=E0401 from dulwich import porcelain # pylint: disable=E0401 from dulwich.contrib.paramiko_vendor import ParamikoSSHVendor # pylint: disable=E0401 import paramiko.transport # pylint: disable=E0401 from dusty.commands.git_clone import _dulwich_repo_get_default_identity # pylint: disable=E0401 from dusty.commands.git_clone import _paramiko_transport_verify_key # pylint: disable=E0401 from dusty.commands.git_clone import _paramiko_client_SSHClient_auth # pylint: disable=E0401 from dusty.models.action import ActionModel from dusty.tools import log class Action(ActionModel): """ Action: clone git repository """ def __init__(self, context, config): """ Initialize action instance """ super().__init__() self.context = context self.validate_config(config) self.config = config def run(self): """ Run action """ # Patch dulwich to work without valid UID/GID dulwich.repo.__original__get_default_identity = dulwich.repo._get_default_identity # pylint: disable=W0212 dulwich.repo._get_default_identity = _dulwich_repo_get_default_identity # pylint: disable=W0212 # Patch dulwich to use paramiko SSH client dulwich.client.get_ssh_vendor = ParamikoSSHVendor # Patch paramiko to skip key verification paramiko.transport.Transport._verify_key = _paramiko_transport_verify_key # pylint: disable=W0212 # Set USERNAME if needed try: getpass.getuser() except: # pylint: disable=W0702 os.environ["USERNAME"] = "git" # Get options source = self.config.get("source") target = self.config.get("target") branch = self.config.get("branch", "master") depth = self.config.get("depth", None) # Prepare auth auth_args = dict() if self.config.get("username", None) is not None: auth_args["username"] = self.config.get("username") if self.config.get("password", None) is not None: auth_args["password"] = self.config.get("password") if self.config.get("key", None) is not None: auth_args["key_filename"] = self.config.get("key") if self.config.get("key_data", None) is not None: key_obj = io.StringIO(self.config.get("key_data").replace("|", "\n")) pkey = paramiko.RSAKey.from_private_key(key_obj) # Patch paramiko to use our key paramiko.client.SSHClient._auth = _paramiko_client_SSHClient_auth( # pylint: disable=W0212 paramiko.client.SSHClient._auth, pkey # pylint: disable=W0212 ) # Clone repository log.info("Cloning repository %s into %s", source, target) repository = porcelain.clone( source, target, checkout=False, depth=depth, errstream=log.DebugLogStream(), **auth_args ) # Checkout branch log.info("Checking out branch %s", branch) branch = branch.encode("utf-8") repository[b"refs/heads/" + branch] = repository[b"refs/remotes/origin/" + branch] repository.refs.set_symbolic_ref(b"HEAD", b"refs/heads/" + branch) repository.reset_index(repository[b"HEAD"].tree) # Delete .git if requested if self.config.get("delete_git_dir", False): log.info("Deleting .git directory") shutil.rmtree(os.path.join(target, ".git")) @staticmethod def fill_config(data_obj): """ Make sample config """ data_obj.insert( len(data_obj), "source", "git@github.com:carrier-io/dusty.git", comment="Source repository (SSH or HTTPS URL)" ) data_obj.insert( len(data_obj), "target", "/data/code", comment="Target directory" ) data_obj.insert( len(data_obj), "branch", "master", comment="(optional) Branch to checkout. Default: master)" ) data_obj.insert( len(data_obj), "depth", 1, comment="(optional) Limit clone depth (for lightweight clone)" ) data_obj.insert( len(data_obj), "username", "some_username", comment="(optional) Username for auth" ) data_obj.insert( len(data_obj), "password", "SomePassword", comment="(optional) Password for auth" ) data_obj.insert( len(data_obj), "key", "/path/to/ssh.key", comment="(optional) Path to SSH private key for auth" ) data_obj.insert( len(data_obj), "key_data", "--- SSHKeyData ---|Goes Here|--- End of SSHKeyData|", comment="(optional) SSH private key data for auth. Replace line breaks with '|'" ) data_obj.insert( len(data_obj), "delete_git_dir", False, comment="(optional) Remove .git directory after checkout" ) @staticmethod def validate_config(config): """ Validate config """ required = ["source", "target"] not_set = [item for item in required if item not in config] if not_set: error = f"Required configuration options not set: {', '.join(not_set)}" log.error(error) raise ValueError(error) @staticmethod def get_name(): """ Module name """ return "git_clone" @staticmethod def get_description(): """ Module description or help message """ return "Clone git repository"