Skip to content

Commit

Permalink
feat(artifact/oracle): Added oracle artifact. (#2990)
Browse files Browse the repository at this point in the history
  • Loading branch information
guoyongzhang authored and tomaslin committed Sep 26, 2018
1 parent 89769ef commit 8f106c7
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 0 deletions.
60 changes: 60 additions & 0 deletions clouddriver-artifacts/clouddriver-artifacts.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@

class DownloadTask extends DefaultTask {
@Input
String sourceUrl

@OutputFile
File target

@TaskAction
void download() {
ant.get(src: sourceUrl, dest: target)
}
}

final File sdkDownloadLocation = project.file('build/sdkdownload')
final File sdkLocation = project.file('build/oci-java-sdk')

// Oracle BMCS SDK isn't published to any maven repo (yet!), so we manually download, unpack and add to compile/runtime deps
// https://github.com/oracle/oci-java-sdk/issues/25
task fetchSdk(type: DownloadTask) {
sourceUrl = 'https://github.com/oracle/oci-java-sdk/releases/download/v1.2.44/oci-java-sdk.zip'
target = sdkDownloadLocation
}

task unpackSdk(type: Sync) {
dependsOn('fetchSdk')
from zipTree(tasks.fetchSdk.target)
into sdkLocation
include "**/*.jar"
exclude "**/*-sources.jar"
exclude "**/*-javadoc.jar"
exclude "apidocs/**"
exclude "examples/**"

// Scary but works. I think clouddriver deps in general need cleaning at some point
// Even without the oracle bmc sdk 3rd party deps there's still multiple javax.inject and commons-X JARs
exclude "**/*jackson*.jar"
exclude "**/*jersey*.jar"
exclude "**/hk2*.jar"
exclude "**/*guava*.jar"
exclude "**/commons*.jar"
exclude "**/aopalliance*.jar"
exclude "**/javassist*.jar"
exclude "**/slf*.jar"
exclude "**/osgi*.jar"
exclude "**/validation*.jar"
exclude "**/jsr305*.jar"
exclude "**/json-smart*.jar"
exclude "**/oci-java-sdk-full-shaded-*.jar"
}

task cleanSdk(type: Delete) {
delete sdkLocation, sdkDownloadLocation
}

tasks.clean.dependsOn('cleanSdk')
tasks.compileJava.dependsOn('unpackSdk')

dependencies {
compile project(":clouddriver-core")

Expand All @@ -9,4 +67,6 @@ dependencies {
compile spinnaker.dependency("googleStorage")
compile spinnaker.dependency("awsS3")
compile "org.apache.commons:commons-compress:1.14"

compile fileTree(sdkLocation)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2017, 2018, Oracle America, Inc.
*
* The contents of this file are subject to the Apache License Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* If a copy of the Apache License Version 2.0 was not distributed with this file,
* You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html
*/

package com.netflix.spinnaker.clouddriver.artifacts.oracle;

import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactAccount;
import lombok.Data;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = true)
@Data
public class OracleArtifactAccount extends ArtifactAccount {
private String name;

private String namespace;
private String region;
private String userId;
private String fingerprint;
private String sshPrivateKeyFilePath;
private String tenancyId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2017, 2018, Oracle America, Inc.
*
* The contents of this file are subject to the Apache License Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* If a copy of the Apache License Version 2.0 was not distributed with this file,
* You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html
*/

package com.netflix.spinnaker.clouddriver.artifacts.oracle;

import com.google.common.base.Supplier;
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
import com.oracle.bmc.auth.SimplePrivateKeySupplier;
import com.oracle.bmc.http.signing.DefaultRequestSigner;
import com.oracle.bmc.http.signing.RequestSigner;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.client.urlconnection.URLConnectionClientHandler;

import javax.ws.rs.core.MediaType;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OracleArtifactClient {

private Client client;

public OracleArtifactClient(String userId, String sshPrivateKeyFilePath, String fingerprint, String tenancyId) {
Supplier<InputStream> privateKeySupplier = new SimplePrivateKeySupplier(sshPrivateKeyFilePath);
AuthenticationDetailsProvider provider = SimpleAuthenticationDetailsProvider.builder()
.userId(userId)
.fingerprint(fingerprint)
.privateKeySupplier(privateKeySupplier)
.tenantId(tenancyId)
.build();

RequestSigner requestSigner = DefaultRequestSigner.createRequestSigner(provider);

ClientConfig clientConfig = new DefaultClientConfig();
client = new Client(new URLConnectionClientHandler(), clientConfig);
client.addFilter(new RequestSigningFilter(requestSigner));
}

InputStream readObject(URI uri) {
WebResource wr = client.resource(uri);
wr.accept(MediaType.APPLICATION_OCTET_STREAM_TYPE);
return wr.get(InputStream.class);
}

private class RequestSigningFilter extends ClientFilter {
private final RequestSigner signer;

private RequestSigningFilter(RequestSigner requestSigner) {
this.signer = requestSigner;
}

@Override
public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
Map<String, List<String>> stringHeaders = new HashMap<>();
for (String key : cr.getHeaders().keySet()) {
List<String> vals = new ArrayList<>();
for (Object val : cr.getHeaders().get(key)) {
vals.add((String) val);
}
stringHeaders.put(key, vals);
}

Map<String, String> signedHeaders = signer.signRequest(cr.getURI(), cr.getMethod(), stringHeaders, cr.getEntity());
for (String key : signedHeaders.keySet()) {
cr.getHeaders().putSingle(key, signedHeaders.get(key));
}

return getNext().handle(cr);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2017, 2018, Oracle America, Inc.
*
* The contents of this file are subject to the Apache License Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* If a copy of the Apache License Version 2.0 was not distributed with this file,
* You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html
*/

package com.netflix.spinnaker.clouddriver.artifacts.oracle;

import com.netflix.spinnaker.clouddriver.artifacts.ArtifactCredentialsRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Configuration
@ConditionalOnProperty("artifacts.oracle.enabled")
@EnableScheduling
@Slf4j
public class OracleArtifactConfiguration {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
@ConfigurationProperties("artifacts.oracle")
OracleArtifactProviderProperties oracleArtifactProviderProperties() {
return new OracleArtifactProviderProperties();
}

@Autowired
OracleArtifactProviderProperties oracleArtifactProviderProperties;

@Autowired
ArtifactCredentialsRepository artifactCredentialsRepository;

@Bean
List<? extends OracleArtifactCredentials> oracleArtifactCredentials(String clouddriverUserAgentApplicationName) {
return oracleArtifactProviderProperties.getAccounts()
.stream()
.map(a -> {
try {
OracleArtifactCredentials c = new OracleArtifactCredentials(clouddriverUserAgentApplicationName, a);
artifactCredentialsRepository.save(c);
return c;
} catch (IOException | GeneralSecurityException e) {
log.warn("Failure instantiating oracle artifact account {}: ", a, e);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2017, 2018, Oracle America, Inc.
*
* The contents of this file are subject to the Apache License Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* If a copy of the Apache License Version 2.0 was not distributed with this file,
* You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html
*/

package com.netflix.spinnaker.clouddriver.artifacts.oracle;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactCredentials;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.sun.jersey.api.client.UniformInterfaceException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Data
public class OracleArtifactCredentials implements ArtifactCredentials {
private static String ARTIFACT_REFERENCE_PREFIX = "oci://";

private static final String ARTIFACT_URI = "https://objectstorage.{arg0}.oraclecloud.com/n/{arg1}/b/{arg2}/o/{arg3}";

private final List<String> types = Arrays.asList("oracle/object");

private final String name;

private final String namespace;
private final String region;
private final String userId;
private final String fingerprint;
private final String sshPrivateKeyFilePath;
private final String tenancyId;

@JsonIgnore
private final OracleArtifactClient client;

public OracleArtifactCredentials(String applicationName, OracleArtifactAccount account)
throws IOException, GeneralSecurityException {
this.name = account.getName();
this.namespace = account.getNamespace();
this.region = account.getRegion();
this.userId = account.getUserId();
this.fingerprint = account.getFingerprint();
this.sshPrivateKeyFilePath = account.getSshPrivateKeyFilePath();
this.tenancyId = account.getTenancyId();

this.client = new OracleArtifactClient(userId, sshPrivateKeyFilePath, fingerprint, tenancyId);
}

public InputStream download(Artifact artifact) throws IOException {
String reference = artifact.getReference();
if (reference.startsWith(ARTIFACT_REFERENCE_PREFIX)) {
reference = reference.substring(ARTIFACT_REFERENCE_PREFIX.length());
}

int slash = reference.indexOf("/");
if (slash <= 0) {
throw new IllegalArgumentException("Oracle references must be of the format oci://<bucket>/<file-path>, got: " + artifact);
}

String bucketName = reference.substring(0, slash);
String path = reference.substring(slash + 1);

URI uri = UriBuilder.fromPath(ARTIFACT_URI).build(region, namespace, bucketName, path);

try {
return client.readObject(uri);
} catch (UniformInterfaceException e) {
if (e.getResponse().getStatus() == 404) {
throw new IOException("Object not found (key: " + path + ")");
}
throw e;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2017, 2018, Oracle America, Inc.
*
* The contents of this file are subject to the Apache License Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* If a copy of the Apache License Version 2.0 was not distributed with this file,
* You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html
*/

package com.netflix.spinnaker.clouddriver.artifacts.oracle;

import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactProvider;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.ArrayList;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
public class OracleArtifactProviderProperties extends ArtifactProvider<OracleArtifactAccount> {
private boolean enabled;
private List<OracleArtifactAccount> accounts = new ArrayList<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.netflix.spinnaker.clouddriver.artifacts.gcs.GcsArtifactConfiguration;
import com.netflix.spinnaker.clouddriver.artifacts.github.GitHubArtifactConfiguration;
import com.netflix.spinnaker.clouddriver.artifacts.http.HttpArtifactConfiguration;
import com.netflix.spinnaker.clouddriver.artifacts.oracle.OracleArtifactConfiguration;
import com.netflix.spinnaker.clouddriver.artifacts.s3.S3ArtifactConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
Expand All @@ -40,6 +41,7 @@
@Import({
EmbeddedArtifactConfiguration.class,
GcsArtifactConfiguration.class,
OracleArtifactConfiguration.class,
GitHubArtifactConfiguration.class,
HttpArtifactConfiguration.class,
S3ArtifactConfiguration.class
Expand Down

0 comments on commit 8f106c7

Please sign in to comment.