/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.docker.machine; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.exception.InvalidRecipeException; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.exception.SnapshotException; import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceProvider; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.DockerConnectorProvider; import org.eclipse.che.plugin.docker.client.DockerRegistryAuthResolver; import org.eclipse.che.plugin.docker.client.params.RemoveImageParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Named; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Collections; import java.util.Set; import static org.eclipse.che.plugin.docker.client.DockerRegistryAuthResolver.DEFAULT_REGISTRY_SYNONYMS; /** * Docker implementation of {@link InstanceProvider} * * @author andrew00x * @author Alexander Garagatyi * @author Roman Iuvshyn * @author Mykola Morhun * * @deprecated use {@link MachineProviderImpl} instead */ @Deprecated @Singleton public class DockerInstanceProvider implements InstanceProvider { private static final Logger LOG = LoggerFactory.getLogger(DockerInstanceProvider.class); /** * dockerfile type support with recipe being a content of Dockerfile */ public static final String DOCKER_FILE_TYPE = "dockerfile"; /** * image type support with recipe script being the name of the repository + image name */ public static final String DOCKER_IMAGE_TYPE = "image"; /** * Prefix of image repository, used to identify that the image is a machine saved to snapshot. */ public static final String MACHINE_SNAPSHOT_PREFIX = "machine_snapshot_"; public static final String DOCKER_HUB_BASE_URI = "index.docker.io"; private final DockerConnector docker; private final DockerRegistryAuthResolver authResolver; private final boolean snapshotUseRegistry; @Inject public DockerInstanceProvider(DockerConnectorProvider dockerProvider, DockerRegistryAuthResolver authResolver, @Named("che.docker.registry_for_snapshots") boolean snapshotUseRegistry) throws IOException { this.docker = dockerProvider.get(); this.authResolver = authResolver; this.snapshotUseRegistry = snapshotUseRegistry; } @Override public String getType() { return "docker"; } @Override public Set<String> getRecipeTypes() { return Collections.emptySet(); } /** * Creates instance from scratch or by reusing a previously one by using specified {@link MachineSource} * data in {@link MachineConfig}. * * @param machine * machine description * @param creationLogsOutput * output for instance creation logs * @return newly created {@link Instance} * @throws UnsupportedRecipeException * if specified {@code recipe} is not supported * @throws InvalidRecipeException * if {@code recipe} is invalid * @throws NotFoundException * if instance described by {@link MachineSource} doesn't exists * @throws MachineException * if other error occurs */ @Override public Instance createInstance(Machine machine, LineConsumer creationLogsOutput) throws NotFoundException, MachineException { throw new UnsupportedOperationException("This machine provider is deprecated."); } /** * Removes snapshot of the instance in implementation specific way. * * @param machineSource * contains implementation specific key of the snapshot of the instance that should be removed * @throws SnapshotException * if exception occurs on instance snapshot removal */ @Override public void removeInstanceSnapshot(final MachineSource machineSource) throws SnapshotException { // use registry API directly because docker doesn't have such API yet // https://github.com/docker/docker-registry/issues/45 final DockerMachineSource dockerMachineSource; try { dockerMachineSource = new DockerMachineSource(machineSource); } catch (MachineException e) { throw new SnapshotException(e); } if (!snapshotUseRegistry) { try { docker.removeImage(RemoveImageParams.create(dockerMachineSource.getLocation(false))); } catch (IOException ignore) { } return; } final String repository = dockerMachineSource.getRepository(); if (repository == null) { LOG.error("Failed to remove instance snapshot: invalid machine source: {}", dockerMachineSource); throw new SnapshotException("Snapshot removing failed. Snapshot attributes are not valid"); } if (DEFAULT_REGISTRY_SYNONYMS.contains(dockerMachineSource.getRegistry())) { removeSnapshotFromDockerHub(repository); } else { removeSnapshotFromRegistry(dockerMachineSource); } } /** * Removes image from unsecured docker registry. * This method removes only manifests from registry, but no blobs. * To free disk space it is required to use garbage collection, * see <a href="https://docs.docker.com/registry/garbage-collection/#how-garbage-collection-works">here</a> * * @param dockerMachineSource * contains information about snapshot that should be removed * @throws SnapshotException * when an error occurs while deleting snapshot */ private void removeSnapshotFromRegistry(final DockerMachineSource dockerMachineSource) throws SnapshotException { try { URL url = UriBuilder.fromUri("http://" + dockerMachineSource.getRegistry()) // TODO make possible to use https here .path("/v2/{repository}/manifests/{digest}") .build(dockerMachineSource.getRepository(), dockerMachineSource.getDigest()) .toURL(); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); try { conn.setConnectTimeout(30 * 1000); conn.setRequestMethod("DELETE"); // TODO add auth header for secured registry final int responseCode = conn.getResponseCode(); if ((responseCode / 100) != 2) { InputStream in = conn.getErrorStream(); if (in == null) { in = conn.getInputStream(); } LOG.error("An error occurred while deleting snapshot with url: {}\nError stream: {}", url, IoUtil.readAndCloseQuietly(in)); throw new SnapshotException("Internal server error occurs. Can't remove snapshot"); } } finally { conn.disconnect(); } } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); } } /** * Removes snapshot repository from docker hub. * * @param repository * snapshot repository name * @throws SnapshotException * when an error occurs while deleting snapshot */ private void removeSnapshotFromDockerHub(String repository) throws SnapshotException { try { URL url = UriBuilder.fromUri("https://" + DOCKER_HUB_BASE_URI) // we use v1 here because docker hub doesn't provide open v2 REST API for repository deletion .path("/v1/repositories/{repository}/") .build(repository) .toURL(); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); try { conn.setConnectTimeout(30 * 1000); conn.setRequestMethod("DELETE"); conn.setRequestProperty("Authorization", authResolver.getBasicAuthHeaderValue(DOCKER_HUB_BASE_URI, null)); final int responseCode = conn.getResponseCode(); if (responseCode != 202 && responseCode != 404) { // if snapshot is already deleted then just skip it InputStream in = conn.getErrorStream(); if (in == null) { in = conn.getInputStream(); } LOG.error("An error occurred while deleting snapshot with url: {}\nError stream: {}", url, IoUtil.readAndCloseQuietly(in)); throw new SnapshotException("Internal server error occurs. Can't remove snapshot"); } } finally { conn.disconnect(); } } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); } } }