All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.hyscale.controller.commands.deploy.HyscaleDeployServiceCommand Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2019 Pramati Prism, Inc.
 *
 * 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.
 */
package io.hyscale.controller.commands.deploy;

import java.io.File;
import java.util.*;
import java.util.concurrent.Callable;
import com.google.gson.*;
import io.hyscale.commons.component.ComponentInvoker;
import io.hyscale.commons.config.SetupConfig;
import io.hyscale.commons.constants.ToolConstants;
import io.hyscale.commons.constants.ValidationConstants;
import io.hyscale.commons.exception.HyscaleException;
import io.hyscale.commons.io.StructuredOutputHandler;
import io.hyscale.commons.utils.GsonProviderUtil;
import io.hyscale.commons.validator.Validator;
import io.hyscale.controller.builder.K8sAuthConfigBuilder;
import io.hyscale.controller.constants.WorkflowConstants;
import io.hyscale.controller.invoker.DockerfileGeneratorComponentInvoker;
import io.hyscale.controller.model.*;
import io.hyscale.controller.profile.ServiceSpecProcessor;
import io.hyscale.controller.util.CommandUtil;
import io.hyscale.controller.util.ServiceSpecUtil;
import io.hyscale.controller.validator.impl.*;

import io.hyscale.deployer.core.model.ServiceStatus;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import io.hyscale.commons.logger.WorkflowLogger;
import io.hyscale.commons.models.Manifest;
import io.hyscale.controller.activity.ControllerActivity;
import io.hyscale.controller.model.WorkflowContextBuilder;
import io.hyscale.controller.commands.args.FileConverter;
import io.hyscale.controller.commands.input.ProfileArg;
import io.hyscale.controller.invoker.DeployComponentInvoker;
import io.hyscale.controller.invoker.ImageBuildComponentInvoker;
import io.hyscale.controller.invoker.ManifestGeneratorComponentInvoker;
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.validation.constraints.Pattern;

/**
 * This class executes 'hyscale deploy service' command
 * It is a sub-command of the 'hyscale deploy' command
 *
 Options:
 *  namespace - name of the namespace in which the service to be deployed
 *  appName - name of the app to logically group your services
 *  serviceSpecs - list of service specs that are to be deployed
 *  profiles - list of profiles for services
 *  profile - profile name to look for. Profile file should be present for all services in service spec (profiles and profile are mutually exclusive)
 *  verbose - prints the verbose output of the deployment
 * 

* Eg 1: hyscale deploy service -f svca.hspec -f svcb.hspec -p dev-svca.hprof -n dev -a sample * Eg 2: hyscale deploy service -f svca.hspec -f svcb.hspec -P dev -n dev -a sample *

* Responsible for deploying a service with the given 'hspec' to * the configured kubernetes cluster. * performs a validation of input before starting deployment. * Performs functions ranging from image building to manifest generation to deployment. * Creates a WorkflowContext to communicate across all deployment stages. * @see HyscaleDeployCommand * Every command/sub-command has to implement the {@link Callable} so that * whenever the command is executed the {@link #call()} * method will be invoked */ @CommandLine.Command(name = "service", aliases = {"services"}, description = "Deploys the service to kubernetes cluster", exitCodeOnInvalidInput = 223, exitCodeOnExecutionException = 123) @Component public class HyscaleDeployServiceCommand implements Callable { private static final Logger logger = LoggerFactory.getLogger(HyscaleDeployServiceCommand.class); @CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Displays the help information of the specified command") private boolean helpRequested = false; @Pattern(regexp = ValidationConstants.NAMESPACE_REGEX, message = ValidationConstants.INVALID_NAMESPACE_MSG) @CommandLine.Option(names = {"-n", "--namespace", "-ns"}, required = true, description = "Namespace of the service ") private String namespace; @Pattern(regexp = ValidationConstants.APP_NAME_REGEX, message = ValidationConstants.INVALID_APP_NAME_MSG) @CommandLine.Option(names = {"-a", "--app"}, required = true, description = "Application name") private String appName; @CommandLine.Option(names = {"-v", "--verbose", "-verbose"}, required = false, description = "Verbose output") private boolean verbose = false; @CommandLine.Option(names = {"-f", "--files"}, required = true, description = "Service specs files.", split = ",", converter = FileConverter.class) private List serviceSpecsFiles; @Pattern(regexp = ValidationConstants.STRUCTURED_OUTPUT_FORMAT_REGEX, message = ValidationConstants.INVALID_OUTPUT_FORMAT_MSG) @CommandLine.Option(names = {"-o", "--output"},paramLabel = "json" ,required = false, description = "Output in json format.") private String structuredOutput; @ArgGroup(exclusive = true, heading = "Profile options", order = 10) private ProfileArg profileArg; @Autowired private InputSpecPostValidator inputSpecPostValidator; @Autowired private ImageBuildComponentInvoker imageBuildComponentInvoker; @Autowired private ManifestGeneratorComponentInvoker manifestGeneratorComponentInvoker; @Autowired private DockerfileGeneratorComponentInvoker dockerfileGeneratorComponentInvoker; @Autowired private DeployComponentInvoker deployComponentInvoker; @Autowired private ServiceSpecInputValidator serviceSpecInputValidator; @Autowired private ServiceSpecProcessor serviceSpecProcessor; @Autowired private K8sAuthConfigBuilder authConfigBuilder; @Autowired private DockerDaemonValidator dockerValidator; @Autowired private RegistryValidator registryValidator; @Autowired private ManifestValidator manifestValidator; @Autowired private ClusterValidator clusterValidator; @Autowired private VolumeValidator volumeValidator; @Autowired private StructuredOutputHandler outputHandler; @Autowired private LoadBalancerValidator loadBalancerValidator; private PortsValidator portsValidator; @Autowired private NetworkPoliciesValidator networkPoliciesValidator; private List> postValidators; private JsonArray jsonArr; private JsonParser jsonParser; @PostConstruct public void init() { this.postValidators = new LinkedList<>(); this.postValidators.add(dockerValidator); this.postValidators.add(registryValidator); this.postValidators.add(manifestValidator); this.postValidators.add(clusterValidator); this.postValidators.add(volumeValidator); this.postValidators.add(loadBalancerValidator); this.postValidators.add(portsValidator); this.postValidators.add(networkPoliciesValidator); this.jsonArr = new JsonArray(); this.jsonParser = new JsonParser(); } @Override public Integer call() throws Exception { if (!CommandUtil.isInputValid(this)) { return ToolConstants.INVALID_INPUT_ERROR_CODE; } if(!StringUtils.isEmpty(structuredOutput)){ WorkflowLogger.setDisabled(true); verbose=false; } // Validate Service specs with schema if (!serviceSpecInputValidator.validate(serviceSpecsFiles)) { if(WorkflowLogger.isDisabled()){ outputHandler.generateErrorMessage(WorkflowConstants.DEPLOYMENT_ERROR); } return ToolConstants.INVALID_INPUT_ERROR_CODE; } Map serviceVsSpecFile = new HashMap<>(); for (File serviceSpec : serviceSpecsFiles) { serviceVsSpecFile.put(ServiceSpecUtil.getServiceName(serviceSpec), serviceSpec); } // Process servicespecs to form EffectiveServiceSpec from ProfileArg & ServiceSpecFiles List effectiveServiceSpecs; try { effectiveServiceSpecs = serviceSpecProcessor.process(profileArg, serviceSpecsFiles); } catch (HyscaleException e) { if (WorkflowLogger.isDisabled()) { StructuredOutputHandler.prepareOutput(WorkflowConstants.DEPLOYMENT_ERROR, e.getMessage()); } throw e; } // Construct WorkflowContext List contextList = new ArrayList<>(); for (EffectiveServiceSpec each : effectiveServiceSpecs) { if (each != null && each.getServiceSpec() != null && each.getServiceMetadata() != null) { WorkflowContextBuilder builder = new WorkflowContextBuilder(appName); try { builder.withProfile(each.getServiceMetadata().getEnvName()); builder.withAuthConfig(authConfigBuilder.getAuthConfig()); builder.withService(each.getServiceSpec()); builder.withNamespace(namespace); contextList.add(builder.get()); } catch (HyscaleException e) { logger.error("Error while preparing workflow context ", e); if (WorkflowLogger.isDisabled()) { StructuredOutputHandler.prepareOutput(WorkflowConstants.DEPLOYMENT_ERROR, e.getMessage()); } throw e; } } } postValidators.forEach(each -> inputSpecPostValidator.addValidator(each)); if (!inputSpecPostValidator.validate(contextList)) { WorkflowLogger.logPersistedActivities(); if(WorkflowLogger.isDisabled()){ outputHandler.generateErrorMessage(WorkflowConstants.DEPLOYMENT_ERROR); } return ToolConstants.INVALID_INPUT_ERROR_CODE; } boolean isCommandFailed = false; for (WorkflowContext workflowContext : contextList) { String serviceName = workflowContext.getServiceName(); WorkflowLogger.header(ControllerActivity.SERVICE_NAME, serviceName); workflowContext.addAttribute(WorkflowConstants.DEPLOY_START_TIME, System.currentTimeMillis()); SetupConfig.clearAbsolutePath(); SetupConfig.setAbsolutePath(serviceVsSpecFile.get(serviceName).getAbsoluteFile().getParent()); workflowContext.addAttribute(WorkflowConstants.VERBOSE, verbose); // clean up service dir before dockerfileGen workflowContext.addAttribute(WorkflowConstants.CLEAN_UP_SERVICE_DIR, true); executeInvoker(dockerfileGeneratorComponentInvoker, workflowContext); executeInvoker(imageBuildComponentInvoker, workflowContext); executeInvoker(manifestGeneratorComponentInvoker, workflowContext); if (!workflowContext.isFailed()) { List manifestList = (List) workflowContext.getAttribute(WorkflowConstants.OUTPUT); workflowContext.addAttribute(WorkflowConstants.GENERATED_MANIFESTS, manifestList); WorkflowLogger.header(ControllerActivity.STARTING_DEPLOYMENT); executeInvoker(deployComponentInvoker, workflowContext); } logWorkflowInfo(workflowContext); isCommandFailed = isCommandFailed ? isCommandFailed : workflowContext.isFailed(); if (WorkflowLogger.isDisabled() && !isCommandFailed) { ServiceStatus serviceStatus = new ServiceStatus(); serviceStatus.setName(workflowContext.getServiceName()); if (workflowContext.getAttribute(WorkflowConstants.SERVICE_IP) != null) { serviceStatus.setMessage(workflowContext.getAttribute(WorkflowConstants.SERVICE_IP).toString()); } if (workflowContext.getAttribute(WorkflowConstants.SERVICE_URL) != null) { serviceStatus.setMessage(workflowContext.getAttribute(WorkflowConstants.SERVICE_URL).toString()); } JsonObject json = (JsonObject) jsonParser.parse(GsonProviderUtil.getPrettyGsonBuilder().toJson(serviceStatus)); jsonArr.add(json); } } if(WorkflowLogger.isDisabled()) { StructuredOutputHandler.prepareOutput(WorkflowConstants.SERVICE_STATUS,jsonArr); } return isCommandFailed ? ToolConstants.HYSCALE_ERROR_CODE : 0; } private boolean executeInvoker(ComponentInvoker invoker, WorkflowContext context) { if (context.isFailed()) { return false; } try { invoker.execute(context); } catch (HyscaleException e) { logger.error("Error while executing component invoker: {}, for app: {}, service: {}", invoker.getClass(), appName, context.getServiceName(), e); context.setFailed(true); if (WorkflowLogger.isDisabled()) { ServiceStatus serviceStatus = new ServiceStatus(); serviceStatus.setName(context.getServiceName()); serviceStatus.setExitCode(e.getCode()); if (context.getAttribute(WorkflowConstants.TROUBLESHOOT_MESSAGE) != null) { serviceStatus.setMessage(context.getAttribute(WorkflowConstants.TROUBLESHOOT_MESSAGE).toString()); } else if(context.getAttribute(WorkflowConstants.ERROR_MESSAGE) != null){ serviceStatus.setMessage(context.getAttribute(WorkflowConstants.ERROR_MESSAGE).toString()); } else { serviceStatus.setMessage(e.getMessage()); } // Prepare K8s Error and set to serviceStatus. JsonObject json = (JsonObject) jsonParser.parse(GsonProviderUtil.getPrettyGsonBuilder().toJson(serviceStatus)); jsonArr.add(json); } } return !context.isFailed(); } private void logWorkflowInfo(WorkflowContext workflowContext) { WorkflowLogger.header(ControllerActivity.INFORMATION); WorkflowLogger.logPersistedActivities(); long startTime = (long) workflowContext.getAttribute(WorkflowConstants.DEPLOY_START_TIME); CommandUtil.logMetaInfo(String.valueOf((System.currentTimeMillis() - startTime) / 1000) + "s", ControllerActivity.TOTAL_TIME); CommandUtil.logMetaInfo(SetupConfig.getMountPathOf((String) workflowContext.getAttribute(WorkflowConstants.DOCKERFILE_INPUT)), ControllerActivity.DOCKERFILE_PATH); CommandUtil.logMetaInfo(SetupConfig.getMountPathOf((String) workflowContext.getAttribute(WorkflowConstants.BUILD_LOGS)), ControllerActivity.BUILD_LOGS); CommandUtil.logMetaInfo(SetupConfig.getMountPathOf((String) workflowContext.getAttribute(WorkflowConstants.PUSH_LOGS)), ControllerActivity.PUSH_LOGS); CommandUtil.logMetaInfo(SetupConfig.getMountPathOf((String) workflowContext.getAttribute(WorkflowConstants.MANIFESTS_PATH)), ControllerActivity.MANIFESTS_GENERATION_PATH); CommandUtil.logMetaInfo(SetupConfig.getMountPathOf((String) workflowContext.getAttribute(WorkflowConstants.DEPLOY_LOGS)), ControllerActivity.DEPLOY_LOGS_AT); WorkflowLogger.footer(); CommandUtil.logMetaInfo((String) workflowContext.getAttribute(WorkflowConstants.SERVICE_IP), ControllerActivity.SERVICE_IP); if (workflowContext.getAttribute(WorkflowConstants.SERVICE_URL) != null) { CommandUtil.logMetaInfo((String) workflowContext.getAttribute(WorkflowConstants.SERVICE_URL), ControllerActivity.SERVICE_URL); } } @PreDestroy public void clear() { SetupConfig.clearAbsolutePath(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy