Skip to main content

task_in_steps

Many python-automation scripts can be seperated into steps. This library supports this seperation. You can define steps with classes. The steps are indepodent as they can implement can_skip which checks if the step can be skipped.

Example

class Config:

    def __init__(self):
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('--projectname', help='Name of the new project', required=True)
        self.parser.add_argument('--domain', help='Top-Level-Domain (without review.)', required=True)
        self.parser.add_argument('--template', help='A link to a GitHub-Repo, used as template', required=True)
        self.parser.add_argument('--username', help='LDAP Username', required=True)
        self.parser.add_argument('--password', help='LDAP Password (optional)', required=False)
        self.gerrit_port = 29418

    def load_args(self):
        args = self.parser.parse_args()
        self.project_name = args.projectname
        self.template_src = args.template
        self.gerrit_user = args.username
        self.password = args.password
        self.domain = args.domain
        self.gerrit_host = 'review.' + args.domain
        self.gerrit_url = f'http://{self.gerrit_host}'
        return self

if __name__ == "__main__":
    config = Config().load_args()

    run_steps(config, [
        CertificateOnThisMachine(),
        UploadSshKey(),
        ProjectFromTemplate(),
        CreateGerritProject(),
        ConfigureGerritGitHooks(),
        ConfigureGitPushToGerrit(),
        CreateJenkinsPipelines(),
        TriggerTemplateInit(),
        SendInitialCommit()
    ])

### The steps:

class CertificateOnThisMachine(Step):
    name = "SSH certificate on this machine"

    def __init__(self):
        home = os.path.expanduser('~')
        self.ssh_path = home + '/.ssh'

    def can_skip(self, config):
        return os.path.isfile(self.ssh_path + '/id_rsa.pub')

    def run(self, config):
        print('no ssh-certificate: generating one')
        os.system(f'ssh-keygen -t rsa -N '' -f {self.ssh_path}')
        return True


class UploadSshKey(Step):
    name = "Upload your SSH-Key"

    def can_skip(self, config):
        ssh_command = f'ssh -p {config.gerrit_port} {config.gerrit_user}@{config.gerrit_host} gerrit version'
        res = subprocess.run(ssh_command.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
        return res == 0

    def run(self, config):
        home = os.path.expanduser('~')
        ssh_path = home + '/.ssh'
        print()
        print('You did not upload your SSH-Key.')
        print()
        print(f'1. Go to: http://{config.gerrit_host}/settings/#SSHKeys')
        print( '2. Paste this in, click "Add Key" and run again:')
        with open(f'{ssh_path}/id_rsa.pub', 'r') as file:
            print(file.read())
        return False

class ProjectFromTemplate(Step):
    name = "Create project from template"

    def can_skip(self, config):
        return os.path.isdir(f'./{config.project_name}')

    def run(self, config):
        os.system(f'git clone {config.template_src} {config.project_name}')
        os.chdir(f'./{config.project_name}')
        os.system(f'rm .git -rf')
        os.system(f'git init')
        os.chdir('..')
        return True


class CreateGerritProject(Step):
    name = 'Create Gerrit project'

    def can_skip(self, config):
        command = f'ssh -p {config.gerrit_port} {config.gerrit_user}@{config.gerrit_host} gerrit ls-projects'
        existing_projects = subprocess.check_output(command, shell=True).decode().strip().split('\n')
        return config.project_name in existing_projects

    def run(self, config):
        os.chdir(f'./{config.project_name}')
        os.system(f'ssh -p {config.gerrit_port} {config.gerrit_user}@{config.gerrit_host} gerrit create-project {config.project_name}')
        os.chdir('..')
        return True


class ConfigureGerritGitHooks(Step):
    name = 'Installing Gerrit Git-Hooks'

    def can_skip(self, config):
        path = f'./{config.project_name}/.git/hooks/commit-msg'
        if not os.path.isfile(path):
            return False
        with open(path, 'r') as file:
            content = file.read()
            return 'Change-Id:' in content

    def run(self, config):
        os.chdir(f'./{config.project_name}')
        os.system(f'scp -p -P {config.gerrit_port} {config.gerrit_user}@{config.gerrit_host}:hooks/commit-msg ".git/hooks/"')
        os.chdir('..')
        return True


class ConfigureGitPushToGerrit(Step):
    name = 'Configure "git push" to Gerrit'

    def can_skip(self, config):
        path = f'./{config.project_name}/.git/config'
        if not os.path.isfile(path):
            return False
        with open(path, 'r') as file:
            content = file.read()
            return 'push = refs/heads/master:refs/for/master' in content

    def run(self, config):
        path = f'./{config.project_name}/.git/config'
        git_config = f'''
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = ssh://{config.gerrit_user}@{config.gerrit_host}:{config.gerrit_port}/{config.project_name}
        fetch = +refs/heads/master:refs/remotes/origin/master
        push = refs/heads/master:refs/for/master
[branch "master"]
        remote = origin
        merge = refs/heads/master
'''
        with open(path, 'w') as f:
            f.write(git_config)
        return True

class CreateJenkinsPipelines(Step):
    name = 'Creating Jenkins-Pipelines'

    def can_skip(self, config):
        job1 = config.project_name + '-code'
        job2 = config.project_name + '-deploy'

        server = self._get_server(config)
        jobs = [job['name'] for job in server.get_all_jobs()]

        return job1 in jobs and job2 in jobs


    def run(self, config):
        self._create_pipeline(config.project_name, 'code', config)
        self._create_pipeline(config.project_name, 'deploy', config)
        return True

    def _get_server(self, config):
        if hasattr(self, 'server'):
            return self.server

        url = f'http://ci.{config.domain}'

        password = config.password
        if not password:
            print(f'Enter credentials for "{url}"')
            print(f'User: {config.gerrit_user}')
            password = getpass()

        self.server = Jenkins(
            url, 
            username=config.gerrit_user, 
            password=password)

        return self.server


    def _create_pipeline(self, project_name, pipeline_type, config):
        content = pkg_resources.resource_string('generate_gerrit_jenkins_project', f'jobs/{pipeline_type}.config.xml').decode()
        content = content.replace('helloworld', project_name)

        try:
            self._get_server(config).create_job(project_name + '-' + pipeline_type, content)
        except JenkinsException as e:
            print(e)
            raise

class TriggerTemplateInit(Step):
    name = 'Trigger Template Init'

    def can_skip(self, config):
        return not os.path.isfile(f'./{config.project_name}/init_template.py')

    def run(self, config):
        stdout, stderr, has_error = exec(f'python3 init_template.py', cwd=f'./{config.project_name}')
        if not has_error:
            exec(f'rm init_template.py', cwd=f'./{config.project_name}')
        return not has_error

class SendInitialCommit(Step):
    name = 'Send initial commit to gerrit'

    def can_skip(self, config):
        stdout, stderr, has_error = exec('git log', cwd=f'./{config.project_name}')
        return not has_error # git log returns error if there are not commits. Skip=True if no error returned

    def run(self, config):
        stdout, stderr, has_error1 = exec('git add .', cwd=f'./{config.project_name}')
        print(stdout, stderr)
        stdout, stderr, has_error2 = exec('git commit -aminit', cwd=f'./{config.project_name}')
        print(stdout, stderr)
        stdout, stderr, has_error3 = exec('git push origin master', cwd=f'./{config.project_name}')
        print(stdout, stderr)
        return True

Metadata

Release files for task-in-steps 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for task-in-steps 0.0.1
File Size Uploaded
task_in_steps-0.0.1.tar.gz 10.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for task-in-steps 0.0.1
File Interpreter ABI Platform
task_in_steps-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 19.8 kB

Release files / task_in_steps-0.0.1.tar.gz

Download URL task_in_steps-0.0.1.tar.gz
Size 10.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2993ad537587383d622efc42c5d4971cf246e7181d6d3bdcabb81a6498b38c80
BLAKE2b-256 checksum
How to use checksums
043febfb9bee85ee6da6774606e3212ea30675563b956ad36005c9118245f549
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.5.0 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.5

Release files / task_in_steps-0.0.1-py3-none-any.whl

Download URL task_in_steps-0.0.1-py3-none-any.whl
Size 9.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c0df603b3e4cc53ff78dabbd081249d75b80288ef7ecbbcb94a30008f69fbb6a
BLAKE2b-256 checksum
How to use checksums
e4104981b1a3d917f5e3f2ba7f4862670c07109fd8788a4479e7dc71eb389771
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.5.0 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.5

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page