org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesLabelConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesLabelConverter.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2017 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.openshift.client.kubernetes;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Converter of labels defined in {@link ContainerConfig} for matching to Kubernetes
 * annotation requirements
 */
public final class KubernetesLabelConverter {
    private static final Logger LOG = LoggerFactory.getLogger(KubernetesLabelConverter.class);
    /** Prefix used for che server labels */
    private static final String CHE_SERVER_LABEL_PREFIX = "che:server";
    /** Padding to use when converting server label to DNS name */
    private static final String CHE_SERVER_LABEL_PADDING = "0%s0";
    /** Regex to use when matching converted labels -- should match {@link CHE_SERVER_LABEL_PADDING} */
    private static final Pattern CHE_SERVER_LABEL_KEY = Pattern.compile("^0(.*)0$");
    private static final String KUBERNETES_ANNOTATION_REGEX = "([A-Za-z0-9][-A-Za-z0-9_\\.]*)?[A-Za-z0-9]";

    private KubernetesLabelConverter() {
    }

    /**
     * @return prefix that is used for Che server labels
     */
    public static String getCheServerLabelPrefix() {
        return CHE_SERVER_LABEL_PREFIX;
    }

    /**
     * Converts a map of labels to match Kubernetes annotation requirements. Annotations are limited
     * to alphanumeric characters, {@code '.'}, {@code '_'} and {@code '-'}, and must start and end
     * with an alphanumeric character, i.e. they must match the regex
     * {@code ([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]}
     *
     * <p>Note that entry keys should begin with {@link KubernetesLabelConverter#CHE_SERVER_LABEL_PREFIX} and
     * entries should not contain {@code '.'} or {@code '_'} before conversion;
     * otherwise label will not be converted and included in output.
     *
     * <p>This implementation is relatively fragile -- changes to how Che generates labels may cause
     * this method to stop working. In general, it will only be possible to convert labels that are
     * alphanumeric plus up to 3 special characters (by converting the special characters to {@code '_'},
     * {@code '-'}, and {@code '.'} as necessary).
     *
     * @param labels Map of labels to convert
     * @return Map of labels converted to DNS Names
     */
    public static Map<String, String> labelsToNames(Map<String, String> labels) {
        Map<String, String> names = new HashMap<>();
        for (Map.Entry<String, String> label : labels.entrySet()) {

            if (!hasConversionProblems(label)) {

                String key = label.getKey();
                String value = label.getValue();

                // Convert keys: e.g. "che:server:4401/tcp:ref" ->
                // "che.server.4401-tcp.ref"
                key = convertLabelKey(key);
                // Convert values: e.g. "/api" -> ".api" -- note values may
                // include '-' e.g. "tomcat-debug"
                value = convertLabelValue(value);

                // Add padding since DNS names must start and end with
                // alphanumeric characters
                key = addPadding(key);
                value = addPadding(value);

                if (matchesKubernetesLabelRegex(key) && matchesKubernetesLabelRegex(value)) {
                    names.put(key, value);
                } else {
                    LOG.error(
                            "Could not convert label {} into Kubernetes annotation: labels must be alphanumeric with ':' and '/'",
                            label.toString());
                }
            }
        }
        return names;
    }

    /**
     * Undoes the label conversion done by {@link KubernetesLabelConverter#labelsToNames(Map)}
     *
     * @param labels Map of DNS names
     * @return Map of unconverted labels
     */
    public static Map<String, String> namesToLabels(Map<String, String> names) {
        Map<String, String> labels = new HashMap<>();
        for (Map.Entry<String, String> entry : names.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();

            // Remove padding
            Matcher keyMatcher = CHE_SERVER_LABEL_KEY.matcher(key);
            Matcher valueMatcher = CHE_SERVER_LABEL_KEY.matcher(value);
            if (!keyMatcher.matches() || !valueMatcher.matches()) {
                continue;
            }
            key = keyMatcher.group(1);
            value = valueMatcher.group(1);

            // Convert key: e.g. "che.server.4401_tcp.ref" -> "che:server:4401/tcp:ref"
            key = key.replaceAll("\\.", ":").replaceAll("_", "/");
            // Convert value: e.g. Convert values: e.g. "_api" -> "/api"
            value = value.replaceAll("_", "/");

            labels.put(key, value);
        }
        return labels;
    }

    /**
     * Checks if there are any potential problems coupled with label conversion
     * @param label
     * @return true if label has no conversion issues, false otherwise
     */
    private static boolean hasConversionProblems(final Map.Entry<String, String> label) {
        boolean hasProblems = false;
        String key = label.getKey();
        String value = label.getValue();

        if (StringUtils.isBlank(value)) {
            LOG.error("The label {} is blank", label.toString());
            hasProblems = true;
        } else if (key.contains(".") || key.contains("_") || value.contains("_")) {
            LOG.error("Cannot convert label {} to DNS Name: '-' and '.' are used as escape characters",
                    label.toString());
            hasProblems = true;
        } else if (!key.startsWith(CHE_SERVER_LABEL_PREFIX)) {
            LOG.warn("Expected CreateContainerParams label key {} to start with {}", key, CHE_SERVER_LABEL_PREFIX);
        }
        return hasProblems;
    }

    /**
     * Convert keys: e.g. "che:server:4401/tcp:ref" -> "che.server.4401-tcp.ref"
     */
    private static String convertLabelKey(final String key) {
        return key.replaceAll(":", ".").replaceAll("/", "_");
    }

    /**
     * Convert values: e.g. "/api" -> ".api" Note: values may include '-' e.g.
     * "tomcat-debug"
     */
    private static String convertLabelValue(final String value) {
        return value.replaceAll("/", "_");
    }

    /**
     * Adds padding since DNS names must start and end with alphanumeric
     * characters
     */
    private static String addPadding(final String label) {
        return String.format(CHE_SERVER_LABEL_PADDING, label);
    }

    private static boolean matchesKubernetesLabelRegex(final String label) {
        return label.matches(KUBERNETES_ANNOTATION_REGEX);
    }
}