package io.cattle.platform.systemstack.catalog.impl; import static io.cattle.platform.core.model.tables.StackTable.*; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.core.addon.CatalogTemplate; import io.cattle.platform.core.constants.ServiceConstants; import io.cattle.platform.core.dao.GenericResourceDao; import io.cattle.platform.core.model.ProjectTemplate; import io.cattle.platform.core.model.Stack; import io.cattle.platform.json.JsonMapper; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.process.ObjectProcessManager; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.systemstack.catalog.CatalogService; import io.cattle.platform.systemstack.model.Template; import io.cattle.platform.systemstack.model.TemplateCollection; import io.cattle.platform.util.type.CollectionUtils; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.fluent.Request; import org.apache.http.client.fluent.Response; import org.yaml.snakeyaml.Yaml; import com.netflix.config.DynamicBooleanProperty; import com.netflix.config.DynamicStringProperty; public class CatalogServiceImpl implements CatalogService { private static DynamicStringProperty CATALOG_VERSION_URL = ArchaiusUtil.getString("system.stack.catalog.versions.url"); private static DynamicStringProperty CATALOG_RESOURCE_URL = ArchaiusUtil.getString("system.stack.catalog.url"); private static DynamicStringProperty CATALOG_RESOURCE_VERSION = ArchaiusUtil.getString("rancher.server.version"); private static final DynamicBooleanProperty LAUNCH_CATALOG = ArchaiusUtil.getBoolean("catalog.execute"); @Inject JsonMapper jsonMapper; @Inject GenericResourceDao resourceDao; @Inject ObjectManager objectManager; @Inject ObjectProcessManager processManager; boolean firstCall = true; @Override public Map<String, CatalogTemplate> resolvedExternalIds(List<CatalogTemplate> templates) throws IOException { Map<String, CatalogTemplate> result = new HashMap<>(); for (CatalogTemplate template : templates) { String externalId = resolveExternalId(template); if (StringUtils.isNotBlank(externalId)) { result.put(externalId, template); } } return result; } private String resolveExternalId(CatalogTemplate template) throws IOException { if (StringUtils.isNotBlank(template.getTemplateId()) || StringUtils.isNotBlank(template.getTemplateVersionId())) { return resolveUsingCatalog(template); } return DigestUtils.md5Hex(template.getDockerCompose() + template.getRancherCompose()); } protected void appendVersionCheck(StringBuilder catalogTemplateUrl) { String minVersion = CATALOG_RESOURCE_VERSION.get(); if (StringUtils.isNotBlank(minVersion)) { catalogTemplateUrl.append("?rancherVersion=").append(minVersion); } } private String resolveUsingCatalog(CatalogTemplate catalogTemplate) throws IOException { if (!LAUNCH_CATALOG.get()) { return null; } if (StringUtils.isNotBlank(catalogTemplate.getTemplateVersionId())) { return String.format("catalog://%s", catalogTemplate.getTemplateVersionId()); } //get the latest version from the catalog template Template template = getTemplateById(catalogTemplate.getTemplateId()); if (template == null || template.getVersionLinks() == null) { return null; } String versionUrl = null; String defaultVersionURL = template.getVersionLinks().get(template.getDefaultVersion()); if (StringUtils.isNotBlank(defaultVersionURL)) { versionUrl = defaultVersionURL; } else { long maxVersion = 0; for (String url : template.getVersionLinks().values()) { long currentMaxVersion = Long.valueOf(url.substring(url.lastIndexOf(":") + 1, url.length())); if (currentMaxVersion >= maxVersion) { maxVersion = currentMaxVersion; versionUrl = url; } } } template = getTemplateAtURL(versionUrl); return template == null ? null : String.format("catalog://%s", template.getId()); } protected Template getTemplateById(String id) throws IOException { StringBuilder catalogTemplateUrl = new StringBuilder(CATALOG_RESOURCE_URL.get()); catalogTemplateUrl.append(id); appendVersionCheck(catalogTemplateUrl); return getTemplateAtURL(catalogTemplateUrl.toString()); } protected Template getTemplateVersionById(String id) throws IOException { if (StringUtils.isBlank(id)) { return null; } id = StringUtils.removeStart(id, "catalog://"); StringBuilder catalogTemplateVersionUrl = new StringBuilder(CATALOG_VERSION_URL.get()); catalogTemplateVersionUrl.append(id); appendVersionCheck(catalogTemplateVersionUrl); return getTemplateAtURL(catalogTemplateVersionUrl.toString()); } protected Template getTemplateAtURL(String url) throws IOException { if (url == null) { return null; } return Request.Get(url).execute().handleResponse(new ResponseHandler<Template>() { @Override public Template handleResponse(HttpResponse response) throws ClientProtocolException, IOException { if (response.getStatusLine().getStatusCode() != 200) { return null; } return jsonMapper.readValue(response.getEntity().getContent(), Template.class); } }); } @Override public String getDefaultExternalId(Stack stack) throws IOException { Template template = getDefaultTemplateVersion(stack); return template == null ? null : "catalog://" + template.getId(); } protected Template getDefaultTemplateVersion(Stack stack) throws IOException { Template template = getTemplateVersionById(stack.getExternalId()); if (template == null || template.getLinks() == null && !template.getLinks().containsKey("template")) { return null; } template = getTemplateAtURL(template.getLinks().get("template")); if (template == null || StringUtils.isBlank(template.getDefaultTemplateVersionId())) { return null; } return getTemplateVersionById(template.getDefaultTemplateVersionId()); } @Override public Stack upgrade(Stack stack) throws IOException { Template template = getDefaultTemplateVersion(stack); if (template == null) { return null; } processManager.scheduleProcessInstance(ServiceConstants.PROCESS_STACK_UPGRADE, stack, CollectionUtils.asMap( ServiceConstants.STACK_FIELD_DOCKER_COMPOSE, template.getDockerCompose(), ServiceConstants.STACK_FIELD_RANCHER_COMPOSE, template.getRancherCompose(), ServiceConstants.STACK_FIELD_ENVIRONMENT, DataAccessor.fieldMap(stack, ServiceConstants.STACK_FIELD_ENVIRONMENT), ServiceConstants.STACK_FIELD_EXTERNAL_ID, "catalog://" + template.getId())); return stack; } @Override public Stack deploy(Long accountId, CatalogTemplate catalogTemplate) throws IOException { String externalId = resolveExternalId(catalogTemplate); if (externalId == null) { return null; } String dockerCompose = catalogTemplate.getDockerCompose(); String rancherCompose = catalogTemplate.getRancherCompose(); Template template = null; if (externalId.startsWith("catalog://")) { template = getTemplateAtURL(CATALOG_RESOURCE_URL.get() + StringUtils.removeStart(externalId, "catalog://")); if (template != null) { dockerCompose = template.getDockerCompose(); rancherCompose = template.getRancherCompose(); } } Map<Object, Object> data = CollectionUtils.asMap( (Object)STACK.EXTERNAL_ID, externalId, STACK.ACCOUNT_ID, accountId, STACK.NAME, catalogTemplate.getName(), STACK.DESCRIPTION, catalogTemplate.getDescription(), STACK.SYSTEM, true, ServiceConstants.STACK_FIELD_DOCKER_COMPOSE, dockerCompose, ServiceConstants.STACK_FIELD_RANCHER_COMPOSE, rancherCompose, ServiceConstants.STACK_FIELD_ENVIRONMENT, catalogTemplate.getAnswers(), ServiceConstants.STACK_FIELD_BINDING, catalogTemplate.getBinding()); return resourceDao.createAndSchedule(Stack.class, objectManager.convertToPropertiesFor(Stack.class, data)); } protected TemplateCollection getTemplates(String url) throws IOException { return Request.Get(url).execute().handleResponse(new ResponseHandler<TemplateCollection>() { @Override public TemplateCollection handleResponse(HttpResponse response) throws ClientProtocolException, IOException { if (response.getStatusLine().getStatusCode() != 200) { return null; } return jsonMapper.readValue(response.getEntity().getContent(), TemplateCollection.class); } }); } protected void refresh() throws IOException { if (firstCall) { String url = CATALOG_RESOURCE_URL.get(); if (url.endsWith("/")) { url = url.substring(0, url.length()-1); } Response resp = Request.Post(String.format("%s?refresh&action=refresh", url)).execute(); int status = resp.returnResponse().getStatusLine().getStatusCode(); if (status >= 400) { throw new IOException("Failed to reload got [" + status + "]"); } firstCall = false; } } @Override public Map<String, Map<Object, Object>> getTemplates(List<ProjectTemplate> installed) throws IOException { refresh(); Map<String, Map<Object, Object>> result = new HashMap<>(); StringBuilder catalogTemplateUrl = new StringBuilder(CATALOG_RESOURCE_URL.get()); appendVersionCheck(catalogTemplateUrl); if (catalogTemplateUrl.toString().contains("?")) { catalogTemplateUrl.append("&"); } else { catalogTemplateUrl.append("?"); } catalogTemplateUrl.append("templateBase_eq=project"); TemplateCollection collection = getTemplates(catalogTemplateUrl.toString()); Yaml yaml = new Yaml(); if (collection.getData() != null) { for (Template template : collection.getData()) { if (template.getVersionLinks() == null) { continue; } String url = template.getVersionLinks().get(template.getDefaultVersion()); if (url == null) { continue; } template = getTemplateAtURL(url); if (template == null || template.getFiles() == null) { continue; } String file = template.getFiles().get("project.yml"); if (file == null) { continue; } @SuppressWarnings("unchecked") Map<Object, Object> project = yaml.loadAs(file, Map.class); result.put("catalog://" + template.getId(), project); } } return result; } @Override public Map<String, String> latestInfraTemplates() throws IOException { refresh(); Map<String, String> result = new HashMap<>(); StringBuilder catalogTemplateUrl = new StringBuilder(CATALOG_RESOURCE_URL.get()); appendVersionCheck(catalogTemplateUrl); if (catalogTemplateUrl.indexOf("?") == -1) { catalogTemplateUrl.append("?"); } else { catalogTemplateUrl.append("&"); } catalogTemplateUrl.append("templateBase_eq=infra"); TemplateCollection collection = getTemplates(catalogTemplateUrl.toString()); for (Template template : collection.getData()) { if (!"library".equals(template.getCatalogId()) || StringUtils.isBlank(template.getDefaultVersion())) { continue; } if (StringUtils.isNotBlank(template.getDefaultTemplateVersionId())) { result.put(template.getId(), "catalog://" + template.getDefaultTemplateVersionId()); } } return result; } @Override public String getTemplateIdFromExternalId(String externalId) { if (StringUtils.isBlank(externalId)) { return null; } String templateId = StringUtils.removeStart(externalId, "catalog://"); String[] parts = StringUtils.split(templateId, ":", 3); if (parts.length < 3) { return null; } return String.format("%s:%s", parts[0], parts[1]); } }