package com.sequenceiq.cloudbreak.cloud.gcp.util;
import static org.apache.commons.lang3.StringUtils.isAnyEmpty;
import static org.apache.commons.lang3.StringUtils.isNoneEmpty;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.SecurityUtils;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.ComputeScopes;
import com.google.api.services.compute.model.Operation;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.StorageScopes;
import com.sequenceiq.cloudbreak.cloud.context.CloudContext;
import com.sequenceiq.cloudbreak.cloud.gcp.GcpResourceException;
import com.sequenceiq.cloudbreak.cloud.model.AvailabilityZone;
import com.sequenceiq.cloudbreak.cloud.model.CloudCredential;
import com.sequenceiq.cloudbreak.cloud.model.Group;
import com.sequenceiq.cloudbreak.cloud.model.Network;
import com.sequenceiq.cloudbreak.cloud.model.Region;
public final class GcpStackUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(GcpStackUtil.class);
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final List<String> SCOPES = Arrays.asList(ComputeScopes.COMPUTE, StorageScopes.DEVSTORAGE_FULL_CONTROL);
private static final String GCP_IMAGE_TYPE_PREFIX = "https://www.googleapis.com/compute/v1/projects/%s/global/images/";
private static final String EMPTY_BUCKET = "";
private static final int FINISHED = 100;
private static final int PRIVATE_ID_PART = 2;
private static final String SERVICE_ACCOUNT = "serviceAccountId";
private static final String PRIVATE_KEY = "serviceAccountPrivateKey";
private static final String PROJECT_ID = "projectId";
private static final String NETWORK_ID = "networkId";
private static final String SUBNET_ID = "subnetId";
private static final String NO_PUBLIC_IP = "noPublicIp";
private static final String NO_FIREWALL_RULES = "noFirewallRules";
private GcpStackUtil() {
}
public static Compute buildCompute(CloudCredential gcpCredential) {
try {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential credential = buildCredential(gcpCredential, httpTransport);
return new Compute.Builder(
httpTransport, JSON_FACTORY, null).setApplicationName(gcpCredential.getName())
.setHttpRequestInitializer(credential)
.build();
} catch (Exception e) {
LOGGER.error("Error occurred while building Google Compute access.", e);
}
return null;
}
public static GoogleCredential buildCredential(CloudCredential gcpCredential, HttpTransport httpTransport) throws IOException, GeneralSecurityException {
PrivateKey pk = SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),
new ByteArrayInputStream(Base64.decodeBase64(getServiceAccountPrivateKey(gcpCredential))), "notasecret", "privatekey", "notasecret");
return new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(getServiceAccountId(gcpCredential))
.setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKey(pk)
.build();
}
public static String getServiceAccountPrivateKey(CloudCredential credential) {
return credential.getParameter(PRIVATE_KEY, String.class);
}
public static String getServiceAccountId(CloudCredential credential) {
return credential.getParameter(SERVICE_ACCOUNT, String.class);
}
public static String getProjectId(CloudCredential credential) {
return credential.getParameter(PROJECT_ID, String.class).toLowerCase().replaceAll("[^A-Za-z0-9 ]", "-");
}
public static boolean analyzeOperation(Operation operation) throws Exception {
String errorMessage = checkForErrors(operation);
if (errorMessage != null) {
throw new Exception(errorMessage);
} else {
Integer progress = operation.getProgress();
return progress == FINISHED;
}
}
public static String checkForErrors(Operation operation) {
if (operation == null) {
LOGGER.error("Operation is null!");
return null;
}
String msg = null;
if (operation.getError() != null) {
StringBuilder error = new StringBuilder();
if (operation.getError().getErrors() != null) {
for (Operation.Error.Errors errors : operation.getError().getErrors()) {
error.append(String.format("code: %s -> message: %s %s", errors.getCode(), errors.getMessage(), System.lineSeparator()));
}
msg = error.toString();
} else {
LOGGER.debug("No errors found, Error: {}", operation.getError());
}
}
if (operation.getHttpErrorStatusCode() != null) {
msg += String.format(" HTTP error message: %s, HTTP error status code: %s", operation.getHttpErrorMessage(), operation.getHttpErrorStatusCode());
}
return msg;
}
public static Compute.GlobalOperations.Get globalOperations(Compute compute, String projectId, String operationName) throws IOException {
return compute.globalOperations().get(projectId, operationName);
}
public static Compute.ZoneOperations.Get zoneOperations(Compute compute, String projectId, String operationName, AvailabilityZone region)
throws IOException {
return compute.zoneOperations().get(projectId, region.value(), operationName);
}
public static Compute.RegionOperations.Get regionOperations(Compute compute, String projectId, String operationName, Region region) throws IOException {
return compute.regionOperations().get(projectId, region.value(), operationName);
}
public static Storage buildStorage(CloudCredential gcpCredential, String name) {
try {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential credential = buildCredential(gcpCredential, httpTransport);
return new Storage.Builder(
httpTransport, JSON_FACTORY, null).setApplicationName(name)
.setHttpRequestInitializer(credential)
.build();
} catch (Exception e) {
LOGGER.error("Error occurred while building Google Storage access.", e);
}
return null;
}
public static String getBucket(String image) {
if (!StringUtils.isEmpty(image) && createParts(image).length > 1) {
String[] parts = createParts(image);
return StringUtils.join(ArrayUtils.remove(parts, parts.length - 1), "/");
} else {
LOGGER.warn("No bucket found in source image path.");
return EMPTY_BUCKET;
}
}
public static String getTarName(String image) {
if (!StringUtils.isEmpty(image)) {
String[] parts = createParts(image);
return parts[parts.length - 1];
} else {
throw new GcpResourceException("Source image path environment variable is not well formed");
}
}
public static String getImageName(String image) {
return getTarName(image).replaceAll("(\\.tar|\\.zip|\\.gz|\\.gzip)", "").replaceAll("\\.", "-");
}
public static String getAmbariImage(String projectId, String image) {
return String.format(GCP_IMAGE_TYPE_PREFIX + getImageName(image), projectId);
}
public static Long getPrivateId(String resourceName) {
try {
return Long.valueOf(resourceName.split("-")[PRIVATE_ID_PART]);
} catch (NumberFormatException nfe) {
LOGGER.warn("Cannot determine the private id of GCP instance, name: " + resourceName);
return null;
} catch (Exception e) {
LOGGER.warn("Cannot determine the private id of GCP instance, name: " + resourceName, e);
return null;
}
}
public static boolean isExistingNetwork(Network network) {
return isNoneEmpty(getCustomNetworkId(network));
}
public static boolean newSubnetInExistingNetwork(Network network) {
return isExistingNetwork(network) && isNoneEmpty(network.getSubnet().getCidr());
}
public static boolean newNetworkAndSubnet(Network network) {
return !isExistingNetwork(network);
}
public static boolean legacyNetwork(Network network) {
return isAnyEmpty(network.getSubnet().getCidr()) && isAnyEmpty(getSubnetId(network));
}
public static boolean isExistingSubnet(Network network) {
return isNoneEmpty(getSubnetId(network));
}
public static String getCustomNetworkId(Network network) {
return network.getStringParameter(NETWORK_ID);
}
public static String getSubnetId(Network network) {
return network.getStringParameter(SUBNET_ID);
}
public static Boolean noPublicIp(Network network) {
Boolean noPublicIp = network.getParameter(NO_PUBLIC_IP, Boolean.class);
if (noPublicIp == null) {
return Boolean.FALSE;
}
return noPublicIp;
}
public static Boolean noFirewallRules(Network network) {
Boolean noFirewallRules = network.getParameter(NO_FIREWALL_RULES, Boolean.class);
if (noFirewallRules == null) {
return Boolean.FALSE;
}
return noFirewallRules;
}
public static String getClusterTag(CloudContext cloudContext) {
return cloudContext.getName() + cloudContext.getId();
}
public static String getGroupClusterTag(CloudContext cloudContext, Group group) {
return group.getName().toLowerCase().replaceAll("[^A-Za-z0-9 ]", "") + cloudContext.getId();
}
private static String[] createParts(String splittable) {
return splittable.split("/");
}
}