def validate(state, config, *vargs):
    allowed = True
    reason = "Namespace accepted"
    ignore_namespaces = config.get('ignore_namespaces',
                                   ['default', 'kube-system'])
    metadata = state.get('object', {}).get('metadata', {})
    annotations = metadata.get('annotations', {})

    name = metadata.get('name')

    if ignore_namespaces and name in ignore_namespaces:
        log("Namespace ignored:", name)
        return make_response(True)

    owner = annotations.get('getup.io/owner') or annotations.get(
        'openshift.io/requester')

    if owner is None:
        allowed = False
        reason = "Missing ownership annotation: either \"getup.io/owner\" or \"openshift.io/requester\" must be supplied."
    elif config.get('username_type') == 'email' and '@' not in owner:
        allowed = False
        reason = "Invalid annotation: owner must be an email address: %s" % owner

    log("{}: {}".format(reason, name))

    return make_response(allowed, reason)
Example #2
0
def reconcile(state, config, *args):
    metadata = state.get('object', {}).get('metadata', {})
    finalizers = metadata.get('finalizers', [])

    if not finalizers:
        return

    if 'orphan' in finalizers:
        log("Removing finalizer from Job {}/{}: 'orphan'".format(
            metadata['namespace'], metadata['name']))
        finalizers = [f for f in finalizers if f != 'orphan']

    state['object']['metadata']['finalizers'] = finalizers
    return state
Example #3
0
def load_config(controller_name, controller_type):
    config = {}
    config_file = os.environ.get(
        "CONTROLLER_CONFIG_{}".format(controller_name.upper()),
        "/config/{}-{}.yaml".format(controller_name, controller_type))

    try:
        with open(config_file, 'r') as cf:
            config = yaml.safe_load(cf)
    except FileNotFoundError as ex:
        pass
    except Exception as ex:
        log("Error loading config[%s:%s]: %s: %s" %
            (controller_name, controller_type, config_file, str(ex)))
        sys.exit(1)

    return config
def reconcile(state, config, *args):
    ignore_namespaces = config.get('ignore_namespaces',
                                   ['default', 'kube-system'])
    metadata = state.get('object', {}).get('metadata', {})
    annotations = metadata.get('annotations', {})

    name = metadata.get('name')
    uid = metadata.get('uid')

    if ignore_namespaces and name in ignore_namespaces:
        log("Namespace ignored:", name)
        return

    owner = annotations.get('getup.io/owner') or annotations.get(
        'openshift.io/requester')

    if owner is None:
        log("Namespace %s is missing ownership annotation: either \"getup.io/owner\" or \"openshift.io/requester\" must be supplied."
            % name)
        return

    if config.get('username_type') == 'email' and '@' not in owner:
        log("Invalid annotation: owner must be an email address: %s" % owner)
        return

    log('Project "%s" owner should be "%s"' % (name, owner))

    project_list_url = os.environ["GETUP_API_URL"].strip(
        '/') + '/api/v1/project/'
    project_url = project_list_url + name + '/'
    auth = (os.environ["GETUP_API_USERNAME"], os.environ["GETUP_API_PASSWORD"])

    res = requests.get(project_url,
                       auth=auth,
                       params={"status": "active"},
                       allow_redirects=True)
    project = res.json()

    if res.status_code == 200:
        # project already exists on DB.
        if project['owner'] == owner:
            log('Project "%s" already owned by "%s".' % (name, owner))
        else:
            log('WARNING: Conflict: Project "%s" owned by "%s", but namespace says it should be owned by "%s". Please fix ambiguity.'
                % (name, project['owner'], owner))

        if project['uid'] != uid:
            # namespace was recreated with same name, must finish old one and create new Billing Project
            res = requests.patch(project_url,
                                 auth=auth,
                                 allow_redirects=True,
                                 params={
                                     'sync': True,
                                     'status': 'active'
                                 },
                                 data=json.dumps({'status': 'finished'}),
                                 headers={'content-type': 'application/json'})

            if not res.ok:
                log('Project "%s" status change failed: %s %s' %
                    (name, res.status_code, res.text))
            else:
                log('Project "%s" status changed to "finished"' % name)

    elif res.status_code == 404:
        # project do not exists on DB, lets create one
        data = {'owner': owner, 'name': name, 'family': 'default'}
        log('Will create project with "%s"' % data)
        res = requests.post(project_list_url,
                            auth=auth,
                            data=data,
                            params={"sync": True})

        if not res.ok:
            log('Error creating Project:', res.text)
        else:
            log('Project "%s" created successfully' % name)
Example #5
0
def reconcile(state, config, *args):
    metadata = state.get('object', {}).get('metadata', {})
    annotations = metadata.get('annotations', {})
    labels = metadata.get('labels', {})
    name = metadata.get('name')
    uid = metadata.get('uid')
    taints = state.get('object', {}).get('spec', {}).get('taints', [])

    # copy label to annotation
    for prefix_name, value in labels.items():
        if not prefix_name.startswith('annotation.getup.io/'):
            continue
        _, name = prefix_name.split('/')
        if not name:
            continue
        log('Added annotation: {}={}'.format(name, value))
        annotations[name] = value

    # copy annotation to label
    for prefix_name, value in annotations.items():
        if not prefix_name.startswith('label.getup.io/'):
            continue
        _, name = prefix_name.split('/')
        if not name:
            continue
        log('Added label: {}={}'.format(name, value))
        labels[name] = value

    # taint from labels or annotations
    for prefix_name, value in itertools.chain(labels.items(),
                                              annotations.items()):
        if not prefix_name.startswith('taint.getup.io/'):
            continue
        _, name = prefix_name.split('/')
        if not name:
            continue
        try:
            taint = json.loads(value)
        except json.decoder.JSONDecodeError:
            tmp = value.split(':')
            taint = {
                'key':
                name,
                'value':
                tmp[0],
                'effect':
                tmp[1] if len(tmp) > 1 else os.environ.get(
                    'DEFAULT_NODE_TAINT_EFFECT', 'NoSchedule'),
            }

        found = False
        for t in taints:
            if t['effect'] == taint['effect'] and t['key'] == taint[
                    'key'] and t['value'] == taint['value']:
                found = True
                break
        if found:
            continue
        log('Added taint: {}'.format(taint))
        taints.append(taint)

    state['object']['spec']['taints'] = taints
    state['object']['metadata']['annotations'] = annotations
    state['object']['metadata']['labels'] = labels

    return state
Example #6
0
            config = yaml.safe_load(cf)
    except FileNotFoundError as ex:
        pass
    except Exception as ex:
        log("Error loading config[%s:%s]: %s: %s" %
            (controller_name, controller_type, config_file, str(ex)))
        sys.exit(1)

    return config


if __name__ == "__main__":
    try:
        controller_name, controller_type, *args = *sys.argv[1:3], *sys.argv[3:]
    except ValueError as ex:
        log("Invalid parameters: Usage: %s [controller-name] [controller-type]"
            % sys.argv[0])
        log("Invalid parameters: Got %s" % sys.argv)
        sys.exit(1)

    try:
        controller = importlib.import_module(
            'controllers.{}'.format(controller_name))
        func = getattr(controller, controller_type)

        config = load_config(controller_name, controller_type)
        state = json.load(sys.stdin)

        result = func(state, config, *args)

        if result is not None:
            print(json.dumps(result))