#!/usr/bin/python

#
# (c) Copyright 2018 SUSE LLC
#
# 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.
#
# Cancels all running builds with the supplied parameter values except the
# most recent one, via the API.

import argparse
import jenkins
import json
import os
import sys
import time


def get_build_params(build):
    """
    Return a dictionary of params names and their values
    """
    # This is what a parameter action looks like:
    # {'_class': 'hudson.model.ParametersAction', 'parameters': [
    #     {'_class': 'hudson.model.StringParameterValue',
    #      'value': '12',
    #      'name': 'FOO_BAR_BAZ'}]}
    actions = build.get('actions')
    if actions:
        parameters = {}
        for elem in actions:
            if elem.get('_class') == 'hudson.model.ParametersAction':
                parameters = elem.get('parameters', {})
                break

        return set([(pair['name'],
                     u"{0}".format(pair.get('value')),)
                    for pair in parameters])
    return set()


def jenkins_cancel_job(job_name, job_args=[], older_than=None,
                       dry_run=False, wait=0):
    config_files = ('/etc/jenkinsapi.conf', './jenkinsapi.conf')
    config = dict()
    job_parameters = set()

    for config_file in config_files:
        if not os.path.exists(config_file):
            continue
        with open(config_file, 'r') as f:
            config.update(json.load(f))

    if not config:
        print('Error: No config file could be loaded. '
              'Please create either of: %s' %
              ', '.join(config_files))
        sys.exit(1)

    for param in job_args:
        p_key, _, p_val = param.partition('=')
        job_parameters.add((p_key.strip(' '), p_val.strip(' ')))

    server = jenkins.Jenkins(str(config['jenkins_url']),
                             username=config['jenkins_user'],
                             password=config['jenkins_api_token'])

    job_info = server.get_job_info(job_name)
    stopped_jobs = []
    for build in job_info['builds']:
        build = server.get_build_info(job_name, build['number'])
        if not build['building']:
            continue
        if older_than and build['number'] >= older_than:
            continue
        if not job_parameters or \
           not job_parameters.issubset(get_build_params(build)):
            continue

        print('Stopping job {}/{}'.format(
            job_name, build['number']
        ))
        if not dry_run:
            server.stop_build(job_name, build['number'])
            stopped_jobs.append(build['number'])

    while wait > 0:
        for stopped_job in stopped_jobs:
            build = server.get_build_info(job_name, stopped_job)
            if build['building']:
                print('Waiting for job {}/{} to stop...'.format(
                    job_name, stopped_job
                ))
                break
        else:
            break
        time.sleep(30)
        wait -= 10


def main():

    parser = argparse.ArgumentParser(
        description='Cancel one or more running Jenkins jobs')
    parser.add_argument('job', default=os.environ.get('JOB_NAME'), nargs='?',
                        help='the Jenkins job name. If not supplied, the '
                             'JOB_NAME environment variable is used.')
    parser.add_argument('-p', '--with-param', action='append',
                        required=False, default=[],
                        help='Parameter name-regexp pair used to filter jobs'
                             '(e.g. some_param=some-regexp)')
    parser.add_argument('--older-than-build', type=int,
                        help="Only cancel builds older than the indicated "
                             "build number.")
    parser.add_argument('--wait', type=int, default=0,
                        help="Number of seconds to wait until jobs are "
                             "stopped.")
    parser.add_argument('--dry-run', default=False, action='store_true',
                        help='do a dry run')
    args = parser.parse_args()

    if not args.job:
        print("Error: Job name could not be determined from the JOB_NAME "
              "environment variable.")
        sys.exit(1)

    jenkins_cancel_job(args.job, args.with_param,
                       args.older_than_build, args.dry_run, args.wait)


if __name__ == "__main__":
    main()
