/* * Copyright 2013-2017 the original author or authors. * * 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 org.cloudfoundry.util; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.resourcematch.ListMatchingResourcesRequest; import org.cloudfoundry.client.v2.resourcematch.ListMatchingResourcesResponse; import org.cloudfoundry.client.v2.resourcematch.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; /** * Utilities for matching resources */ public final class ResourceMatchingUtils { private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.resource-matching"); private ResourceMatchingUtils() { } public static Mono<List<ArtifactMetadata>> getMatchedResources(CloudFoundryClient cloudFoundryClient, Path application) { return (Files.isDirectory(application) ? getArtifactMetadataFromDirectory(application) : getArtifactMetadataFromZip(application)) .collectMap(ArtifactMetadata::getHash) .flatMapMany(artifactMetadatas -> requestListMatchingResources(cloudFoundryClient, artifactMetadatas.values()) .flatMapIterable(ListMatchingResourcesResponse::getResources) .map(resource -> artifactMetadatas.get(resource.getHash()))) .collectList() .doOnNext(matched -> LOGGER.debug("{} resources matched totaling {}", matched.size(), SizeUtils.asIbi(matched.stream() .mapToInt(ArtifactMetadata::getSize) .sum()))) .subscribeOn(Schedulers.elastic()); } private static Flux<ArtifactMetadata> getArtifactMetadataFromDirectory(Path application) { return Flux .defer(() -> { try { return Flux.fromStream(Files.walk(application)); } catch (IOException e) { throw Exceptions.propagate(e); } }) .filter(path -> !Files.isDirectory(path)) .map(path -> new ArtifactMetadata(FileUtils.hash(path), FileUtils.getRelativePathName(application, path), FileUtils.permissions(path), FileUtils.size(path))); } private static Flux<ArtifactMetadata> getArtifactMetadataFromZip(Path application) { List<ArtifactMetadata> artifactMetadatas = new ArrayList<>(); try (ZipFile zipFile = new ZipFile(application.toFile())) { Enumeration<ZipArchiveEntry> entries = zipFile.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); if (!entry.isDirectory()) { try (InputStream in = zipFile.getInputStream(entry)) { String hash = FileUtils.hash(in); String path = entry.getName(); String permissions = FileUtils.permissions(entry.getUnixMode()); int size = (int) entry.getSize(); artifactMetadatas.add(new ArtifactMetadata(hash, path, permissions, size)); } } } } catch (IOException e) { throw Exceptions.propagate(e); } return Flux.fromIterable(artifactMetadatas); } private static Mono<ListMatchingResourcesResponse> requestListMatchingResources(CloudFoundryClient cloudFoundryClient, Collection<ArtifactMetadata> artifactMetadatas) { ListMatchingResourcesRequest request = artifactMetadatas.stream() .reduce(ListMatchingResourcesRequest.builder(), (builder, artifactMetadata) -> builder.resource(Resource.builder() .hash(artifactMetadata.getHash()) .mode(artifactMetadata.getPermissions()) .size(artifactMetadata.getSize()) .build()), (a, b) -> a.addAllResources(b.build().getResources())) .build(); return cloudFoundryClient.resourceMatch() .list(request); } /** * Metadata information about a given artifact */ public static final class ArtifactMetadata { private final String hash; private final String path; private final String permissions; private final int size; /** * Creates a new instance * * @param hash the SHA-1 hash of the artifact * @param path the relative path of the artifact * @param permissions the UNIX permissions of the artifact * @param size the size of the artifact */ public ArtifactMetadata(String hash, String path, String permissions, int size) { this.hash = hash; this.path = path; this.permissions = permissions; this.size = size; } /** * Returns the SHA-1 hash of the artifact * * @return the SHA-1 hash of the artifact */ public String getHash() { return this.hash; } /** * Returns the path of the artifact * * @return the path of the artifact */ public String getPath() { return this.path; } /** * Returns the permissions of the artifact * * @return the permissions of the artifact */ public String getPermissions() { return this.permissions; } /** * Returns the size of the artifact * * @return the size of the artifact */ public int getSize() { return this.size; } } }