Exemplo n.º 1
0
def vault(valut_type, action, environment, target, kwargs):
    try:
        repo_config = AnsibleRepo()
    except (ValueError, IOError, LookupError) as e:
        print_c("ERROR! ", color="light_red", file=sys.stderr)
        print(e, file=sys.stderr)
        sys.exit(1)

    debug_level = get_debuglevel(kwargs)
    search_base = os.path.join(repo_config.base, repo_config.inventory_base,
                               environment)

    vault_cmd = "%s %s" % (repo_config.ansible_vault, action)
    vault_file = os.path.join(search_base, repo_config.vault_file)

    # Look for vault file
    if os.path.isfile(vault_file):
        vault_cmd += " --vault-password-file=%s" % vault_file
    else:
        vault_cmd += " --ask-vault-pass"

    # Build path of the target
    if valut_type == 'host':
        target_path = "host_vars"
    elif valut_type == 'group':
        target_path = "group_vars"
    target_path = os.path.join(search_base, target_path)
    target_path = os.path.join(target_path, target)
    target_path = "%s/secrets.yml" % target_path

    # Checks based on the selected action
    if action in ['encrypt', 'decrypt']:
        if not os.path.isfile(target_path):
            raise ScriptError("Can't find the target file \"%s\"" %
                              target_path)

    vault_cmd += " \"%s\"" % target_path

    # Any additional argument
    vault_cmd += " " + ' '.join(kwargs)

    if debug_level > 0:
        print("Executing: %s" % vault_cmd)

    # Execute
    stdout, stderr, rc = exec_cmd(vault_cmd.strip())
    if rc != 0:
        print_c(stderr, color="light_red")
    else:
        print("Done")
        os.chmod(target_path, 0664)
    exit(rc)
Exemplo n.º 2
0
def main():
    print_c("GIT Pre Push Checks\n".center(40), color='white')
    try:
        result = pre_push()
        if not result:
            sys.exit(1)

    except ScriptError as e:
        print_c("Exception raised", color="light_red")
        print(e.message)
        sys.exit(1)

    sys.exit(0)
Exemplo n.º 3
0
def get_debuglevel(ansible_args):
    """
    Discover and return the debug level
    """
    debug_level = 0
    debugging = [x for x in ansible_args if x.startswith('-v')]

    if len(debugging) > 0:
        debug_level = debugging[0].count('v')

    if debug_level > 0:
        print_c("Verbose mode #%d" % debug_level, "cyan")

    return debug_level
Exemplo n.º 4
0
def main():
    print_c("GIT Commit Message Checks\n".center(40), color='white')

    # Read commit message
    with open(sys.argv[1]) as f:
        text = f.read().strip()

    try:
        result = commit_msg(text)
        if not result:
            sys.exit(1)
    except ScriptError as e:
        print_c("Exception raised", color="light_red")
        print(e.message)
        sys.exit(1)

    sys.exit(0)
Exemplo n.º 5
0
def main():
    print_c("Ansible Vault Wrapper\n", color="white")

    parser = argparse.ArgumentParser()

    # This command can work differently based on its name.
    match = re.search(r'-(\w+)$', sys.argv[0])
    if match is None:
        parser.add_argument("vault_type",
                            choices=['host', 'group'],
                            help="What type of vault action to peform.")
        vault_type = None
    else:
        vault_type = match.group(1)

    # Arguments
    parser.add_argument(
        "vault_action",
        choices=['create', 'decrypt', 'edit', 'encrypt', 'rekey', 'view'],
        help="Vault action")
    parser.add_argument("environment", help="The environment to target.")
    parser.add_argument("target", help="The group or host target.")
    parser.add_argument(
        "ansible",
        nargs='*',
        help="These options will be passed directly to Ansible as they are.")

    args, others = parser.parse_known_args()

    # If the type cannot be determined using the exec name, then check for the argument
    if vault_type is None:
        vault_type = args.vault_type

    try:
        vault(vault_type, args.vault_action, args.environment, args.target,
              others)

    except ScriptError as e:
        print_c("ERROR! ", color="light_red", file=sys.stderr)
        print(e.message, file=sys.stderr)
        sys.exit(1)

    sys.exit(0)
Exemplo n.º 6
0
def pre_push():
    repo = AnsibleRepo()

    print_c(" Terraform", color='white')
    print_c("-" * 40, color='white')

    print("Check status: ", end='')
    print_c("ALLOWED\n", color="light_green")

    return True
Exemplo n.º 7
0
def pre_commit():
    repo = AnsibleRepo()

    print_c(" Packer", color='white')
    print_c("-" * 40, color='white')

    print("Check status: ", end='')
    print_c("ALLOWED\n", color="light_green")

    return True
Exemplo n.º 8
0
def main():
    print_c("GIT Pre Push Checks\n".center(40), color='white')
    pre_commit()
Exemplo n.º 9
0
def pre_commit():
    repo = AnsibleRepo()

    print_c(" Ansible", color='white')
    print_c("-" * 40, color='white')
    print_c("Commit repository checks:")

    # Check YAML syntax (not Ansible validity, it will take too long)
    print_c("  Checking YAML syntax... ", end='')
    bad_yaml = []
    for root, _, files in os.walk(repo.base, topdown=False):
        if '.git' in root:
            continue
        for name in files:
            name = os.path.join(root, name)
            if not is_valid_yaml(name):
                bad_yaml.append(name)

    if len(bad_yaml) > 0:
        print_c("ERROR", color='light_red')
        print(
            "\nBad YAML syntax\n\n"
            "Some YAML files were found having a bad\n"
            "syntax. It's not possible to commit \n"
            "incorrect files.\n\n"
            "Bad files:"
        )
        for f in bad_yaml:
            print("  %s" % f[(len(repo.base) + 1):])
        print(
            "\nAborting the commit.\n"
        )
        return False

    print_c("OK", color='light_green')

    # Check cleartext passwords
    print_c("  Checking cleartext secrets... ", end='')
    vaults = []
    for root, _, files in os.walk(repo.base, topdown=False):
        if '.git' in root:
            continue
        for name in files:
            name = os.path.join(root, name)
            if contains_cleartext_secrets(name):
                vaults.append(name)

    if len(vaults) > 0:
        print_c("ERROR", color='light_red')
        print(
            "\nClear text secrets\n\n"
            "The following files need to be\n"
            "encrypted before it's possibile to\n"
            "commit. Storing a clear text password\n"
            "or SSH/SSL private keys in GIT is\n"
            "really unsecure!\n\n"
            "Only the value \"Passw0rd\" is allowed\n"
            "as a default password in cleartext.\n\n"
            "Sensitive files:"
        )
        for f in vaults:
            print("  %s" % f[(len(repo.base) + 1):])
        print(
            "\nAborting the commit.\n"
        )
        return False

    print_c("OK", color='light_green')
    print("Check status: ", end='')
    print_c("ALLOWED\n", color="light_green")

    return True
Exemplo n.º 10
0
def commit_msg(commit_msg):
    #
    # Message content
    #

    # Check only the first line
    commit_msg = commit_msg.split('\n', 1)[0]

    print_c(" Repository", color='white')
    print_c("-" * 40, color='white')

    print_c("Commit message checks:")
    print_c("  Check message reference... ", end='')

    # Message
    message = build_regex("[^\.]{{8,}}")

    # Tickets: "EP01351699: Ticket completed."
    ticket_token = build_regex("[A-Z]{{1,6}}[0-9]{{4,12}}",
                               pattern_name="ticket")
    ticket = build_regex("{ticket_token}:\s+{message}\.",
                         pattern_name="ticket_title",
                         ticket_token=ticket_token,
                         message=message)

    # JIRA Cards: "JIRATAG-10: This is a commit. JIRATAG-10: This is another commit."
    card_token = build_regex("[A-Z]+-[0-9]+", pattern_name="card")
    card = build_regex("{card_token}:\s+{message}\.",
                       pattern_name="card_name",
                       card_token=card_token,
                       message=message)

    # Notes: "NOTES: This is a note on the commit."
    note_token = build_regex("NOTES?")
    note = build_regex("{note_token}:\s+{message}\.",
                       pattern_name="note_content",
                       note_token=note_token,
                       message=message)

    # Test: "TEST: This is a test."
    test_token = build_regex("TESTS?")
    test = build_regex("{test_token}:\s+{message}\.",
                       pattern_name="test_content",
                       test_token=test_token,
                       message=message)

    # Caveats: "CAVEAT: When reading note that something is not perfect."
    caveat_token = build_regex("CAV(|EATS?)")
    caveat = build_regex("{caveat_token}:\s+{message}\.",
                         pattern_name="caveat_content",
                         caveat_token=caveat_token,
                         message=message)

    # Releases: "v1.2.3: Next release ready."
    release_version = build_regex("[vV]?[0-9]+(\.[0-9]+){{1,2}}",
                                  pattern_name="release_version")
    release = build_regex("{release_ver}:\s+{message}\.",
                          pattern_name="release",
                          release_ver=release_version,
                          message=message)

    # Final regex
    message_regex = build_regex(
        "^({release}|{ticket}|{card}|{test})(|\s+{note})(|\s+{caveat})$",
        release=release,
        ticket=ticket,
        card=card,
        note=note,
        test=test,
        caveat=caveat)
    message_info = re.search(message_regex, commit_msg)
    message_info = {} if message_info is None else message_info.groupdict()

    # Check if this is a merge commit
    is_merge = re.match(r'^Merge.+branch.+into.+$', commit_msg,
                        re.IGNORECASE) is not None

    # Check the text of the commit
    if not message_info and not is_merge:
        print_c("ERROR", color="light_red")
        print("\nBad commit message\n\n"
              "The commit message doesn't comply to\n"
              "the required standard.\n\n"
              "Commit messages must be short and clear\n"
              "descriptions of the changes on the last\n"
              "commit and they must have a reference\n"
              "to a card or a ticket. Messages can\n"
              "contain notes, caveats, test comments.\n\n"
              "Merge commits are also possible using\n"
              "the default text given by GIT.\n\n"
              "Example of valid messages are:\n"
              "  TEST: This is a test.\n"
              "  CARD-10: This is a commit.\n"
              "  AA12345678: Ticket completed.\n"
              "  CAVEAT: Something is not perfect.\n"
              "  NOTES: This is a note on the commit.\n\n"
              "Change the commit message and try again.\n")
        return False
    print_c("OK", color='light_green')

    #
    # GIT Branch
    #
    if not is_merge:
        print_c("  Check branch name... ", end='')

        git_branch = re.search(r'\s*\*\s+(.*)', exec_git('branch'),
                               re.MULTILINE).group(1).strip()

        # Branch type
        branch_type = build_regex("(feature|bugfix|hotfix|ticket|release)",
                                  pattern_name="type")

        # Final regex
        branch_regex = build_regex(
            "{branch_type}/({card_token}|{release_version}|{ticket_token})",
            branch_type=branch_type,
            card_token=card_token,
            release_version=release_version,
            ticket_token=ticket_token)

        branch_info = re.search(branch_regex, git_branch)
        branch_info = {} if branch_info is None else branch_info.groupdict()

        if not branch_info:
            print_c("ERROR", color="light_red")
            print("\nBad branch name\n\n"
                  "The current branch doesn't comply with\n"
                  "the required standard.\n\n"
                  "The name of the branch must reflect\n"
                  "the type of branch and it must contain\n"
                  "a reference to an issue tracker like\n"
                  "JIRA, OTRS or similar.\n\n"
                  "Rename the current branch.\n")
            return False

        # Check that the commit reflect the branch name
        branch_type = branch_info.get('type', '')
        branch_match = message_match = None

        if branch_type in ["feature", "bugfix", "hotfix"]:
            branch_match = branch_info['card']
            message_match = message_info['card']

        elif branch_type == "ticket":
            branch_match = branch_info['ticket']
            message_match = message_info['ticket']

        elif branch_type == "release":
            branch_match = branch_info['release_version']
            message_match = message_info['release_version']

        if branch_match is None or branch_match != message_match:
            print_c("ERROR", color="light_red")
            print("\nBad branch/message reference\n\n"
                  "The reference on the message must\n"
                  "match the branch name.\n\n"
                  "To have consistent commit messages, it\n"
                  "is necessary that the reference used\n"
                  "in the commit message is the same as\n"
                  "the name used in the branch.\n\n"
                  "Change the commit message or use a\n"
                  "different branch name.\n")
            return False
        print_c("OK", color='light_green')

    print_c("Check status: ", end='')
    print_c("ALLOWED\n", color="light_green")

    return True
Exemplo n.º 11
0
def pre_push():
    print_c(" Repository", color='white')
    print_c("-" * 40, color='white')
    print_c("Pre push repository checks:")

    # If pushing only tags, we skip all other checks
    print_c("  Pushing tags... ", end='')
    ppid_cmd = psutil.Process(os.getppid()).cmdline()
    if len(ppid_cmd) > 2 and ppid_cmd[2] == '--tags':
        print_c("Tags", color="light_yellow")
        print_c("Check status: ", end='')
        print_c("ALLOWED\n", color="light_green")
        print_c("Skipping other checks because of tags.\n")
        sys.exit(1)
    print_c("NO", color='light_green')

    # Check we're not committing into protected branches
    print_c("  Target branch... ", end='')
    git_branch = re.search(r'\s*\*\s+(.*)', exec_git('branch'),
                           re.MULTILINE).group(1).strip()
    if git_branch in ['master', 'development', 'devel']:
        print_c("ERROR", color="light_red")
        print("\nPush forbidden on protected branches\n\n"
              "Protected branches cannot be changed\n"
              "directly, this is bad practice that can\n"
              "lead to big problems. These branches\n"
              "can only be pulled from the remote\n"
              "repository.\n\n"
              "Aborting the push.\n")
        return False
    print_c("OK", color='light_green')

    print_c("Check status: ", end='')
    print_c("ALLOWED\n", color="light_green")

    # We make calls to sub-checks for each component so that there can be
    # specific checks for Ansible, Terraform and Packer
    try:
        repository = RepoInfo()

        # Ansible GIT hook
        if repository.ansible is not None:
            if not ansible.pre_push():
                sys.exit(1)

        # Packer GIT hook
        if repository.packer is not None:
            if not packer.pre_push():
                sys.exit(1)

        # Terraform GIT hook
        if repository.terraform is not None:
            if not terraform.pre_push():
                sys.exit(1)

    except ScriptError as e:
        print_c("Exception raised", color="light_red")
        print(e.message)
        sys.exit(1)

    return True
Exemplo n.º 12
0
def pre_push():
    repo = AnsibleRepo()

    print_c(" Ansible", color='white')
    print_c("-" * 40, color='white')
    print_c("Pre push repository checks:")

    for i, p in enumerate(repo.playbooks()):
        p_name = p[(len(repo.repo_base) + 1):]
        print_c("    #%d: Checking %s... " % (i + 1, p_name), end='')
        #
        # FIXME: This is old code and the "all" target is not meaningful anymore
        # without specifying also the environment.
        #
        #deploy = DeployConfig(repo, p, 'all', [])
        #stdout, stderr, rc = exec_cmd(deploy.deploy_full + " --syntax-check")
        #if rc != 0:
        #    print_c("ERROR", color='light_red')
        #
        #    print_c(
        #        "\nPUSH ERROR - Found syntax error while checking playbook \"%s\"" % p,
        #        color="light_red",
        #        file=sys.stderr
        #    )
        #    print(stderr)
        #    return False
        print_c("OK", color='light_green')

    print_c("Check status: ", end='')
    print_c("ALLOWED\n", color="light_green")

    return True
Exemplo n.º 13
0
def deploy(args, ansible_args):
    # Getting information about the repository and the deployment
    try:
        repo = AnsibleRepo()
        deploy = DeployConfig(repo, args.playbook, args.target, args.filter, ansible_args)
    except (ValueError, IOError, LookupError) as e:
        print_c("ERROR! ", color="light_red", file=sys.stderr)
        print(e, file=sys.stderr)
        sys.exit(1)

    debug_level = get_debuglevel(ansible_args)

    # Warn the user that vault is being used
    if len(repo.vaulted()) > 0 and debug_level > 0:
        print_c("Found encrypted files, vault password needed.", color="yellow")
        if deploy.vault_file:
            print_c("Vault password found in: \"%s\"." % deploy.vault_file, color="green")

    env = {}

    # Warn the user we're in check mode
    if '--check' in ansible_args:
        print_c("Running in check mode. No changes and no signature.", color="light_green")
        env['ANSIBLE_DEPLOY_SKIP_PLAYBOOK'] = args.playbook

    # If requested, disable the signature for this playbook
    if args.skip_signature:
        print_c("No signature will be written.", color="white")
        env['ANSIBLE_DEPLOY_SKIP_PLAYBOOK'] = args.playbook

    # Deploy! Yay!
    ans_pid = os.fork()
    if ans_pid == 0:
        # The deployment is done on a forked process so it can share the same variables, its
        #  output can be visualized and the return code caught.
        print_c("Execution started...", color="white")
        if debug_level > 0:
            command = shlex.split(deploy.deploy_full)
            print_c("  Executing: %s" % command[0], color="cyan")
            print_c('\n'.join(["    %s" % x for x in command[1:]]), color="cyan")
            if repo.run_as is not None and repo.run_as != getpass.getuser():
                print_c("  Running Ansible as user \"%s\"." % repo.run_as, color="cyan")

        # Run Ansible on a separate process
        switch_cmd(
            deploy.deploy_full,
            run_as=repo.run_as,
            cwd=repo.base,
            env=env
        )
    else:
        # Wait for the above process until it has finished
        _, status = os.waitpid(ans_pid, 0)
        if status > 0:
            sys.exit(1)
Exemplo n.º 14
0
def main():
    print_c("Ansible Deployment Wrapper v2.2.1\n", color="white")

    parser = argparse.ArgumentParser(
        description="Wrapper for ansible-playbook and deployment management.",
        epilog='\n'.join([
            "Notes:",
            "  All relative paths refers to the root of the GIT repository where all the",
            "  code must reside.",
            "",
            "Other features:",
            "",
            "  The script can use a password file to decrypt vaulted files. The file must",
            "  reside in the repository root, be named vault.txt and contain only the vault",
            "  password. The vault file should be present inside .gitignore and it is",
            "  recommended to use secure permission '0400'.",
            "",
            "  The script can use Ansible dynamic inventories. If it finds the script, it",
            "  will invoke Ansible with it instead of an inventory file and the chosen",
            "  environment will become a filter on groups.",
            "",
            "  See ansible.cfg, section \"repository\" for more information.",
            "",
        ]),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        '--skip-signature',
        action='store_true',
        help="If present, it will skip the writing and updating of the deployment signature."
    )
    parser.add_argument(
        'playbook',
        help="Name of the Ansible Playbook found anywhere in the playbooks directory."
    )
    parser.add_argument(
        'target',
        help="Target of the run. It can be a usual Ansible inventory or an executable file as dynamic inventory; if a "
             "relative path then it will be searched in various default locations.",
    )
    parser.add_argument(
        'filter',
        help="An Ansible pattern that will be used to further filter the target hosts, it's equivalent to the Ansible "
             "-l option.",
        nargs="?"
    )
    parser.add_argument(
        'ansible',
        nargs='*',
        help="These options will be passed, as they are, directly to Ansible."
    )
    args, others = parser.parse_known_args()

    try:
        deploy(args, others)
        sys.exit(0)

    except ScriptError as e:
        print_c("ERROR! ", color="light_red", file=sys.stderr)
        print(e.message, file=sys.stderr)
        sys.exit(1)