Skip to content

Commit

Permalink
Container Registry Integration into SCDF
Browse files Browse the repository at this point in the history
 - Refactor existing container registry operations into a common sub project `spring-cloud-dataflow-container-registry`
 - Add new ContainerRegistryService that provides the container registry related operations
 - Add pluggable assemblers for streams/tasks/appregistration resources
 - Resolves spring-cloud#4181
 - Resolves spring-cloud#4182
  • Loading branch information
ilayaperumalg authored and jvalkeal committed Oct 22, 2020
1 parent 5cca496 commit 8644a5c
Show file tree
Hide file tree
Showing 70 changed files with 2,128 additions and 765 deletions.
15 changes: 13 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@
<json-unit.version>2.11.1</json-unit.version>
<docker-compose-rule.version>1.4.2</docker-compose-rule.version>
<findbugs.version>3.0.2</findbugs.version>
<aws-java-sdk-ecr.version>1.11.731</aws-java-sdk-ecr.version>
<commons-text.version>1.8</commons-text.version>
<prometheus-rsocket-spring.version>1.2.1</prometheus-rsocket-spring.version>
<joda-time.version>2.10.6</joda-time.version>
<aws-java-sdk-ecr.version>1.11.731</aws-java-sdk-ecr.version>
<commons-text.version>1.8</commons-text.version>
</properties>
<modules>
<module>spring-cloud-dataflow-container-registry</module>
<module>spring-cloud-dataflow-configuration-metadata</module>
<module>spring-cloud-dataflow-core-dsl</module>
<module>spring-cloud-dataflow-core</module>
Expand Down Expand Up @@ -274,6 +275,16 @@
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-ecr</artifactId>
<version>${aws-java-sdk-ecr.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import org.springframework.cloud.dataflow.audit.service.DefaultAuditRecordService;
import org.springframework.cloud.dataflow.configuration.metadata.ApplicationConfigurationMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.BootApplicationConfigurationMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.container.ContainerImageMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.container.DefaultContainerImageMetadataResolver;
import org.springframework.cloud.dataflow.container.registry.ContainerRegistryService;
import org.springframework.cloud.dataflow.core.AppRegistration;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.core.DefaultStreamDefinitionService;
Expand Down Expand Up @@ -175,6 +177,14 @@ protected boolean isOverwrite(AppRegistration app, boolean overwrite) {
};
}

@MockBean
ContainerRegistryService containerRegistryService;

@Bean
public ContainerImageMetadataResolver containerImageMetadataResolver(ContainerRegistryService containerRegistryService) {
return new DefaultContainerImageMetadataResolver(containerRegistryService);
}

@Bean
public ApplicationConfigurationMetadataResolver metadataResolver() {
return new BootApplicationConfigurationMetadataResolver(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import org.springframework.cloud.dataflow.audit.service.DefaultAuditRecordService;
import org.springframework.cloud.dataflow.configuration.metadata.ApplicationConfigurationMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.BootApplicationConfigurationMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.container.ContainerImageMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.container.DefaultContainerImageMetadataResolver;
import org.springframework.cloud.dataflow.container.registry.ContainerRegistryService;
import org.springframework.cloud.dataflow.core.AppRegistration;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.core.DefaultStreamDefinitionService;
Expand Down Expand Up @@ -131,10 +133,15 @@ protected boolean isOverwrite(AppRegistration app, boolean overwrite) {
}

@MockBean
DefaultContainerImageMetadataResolver containerImageMetadataResolver;
ContainerRegistryService containerRegistryService;

@Bean
public ApplicationConfigurationMetadataResolver metadataResolver() {
public ContainerImageMetadataResolver containerImageMetadataResolver(ContainerRegistryService containerRegistryService) {
return new DefaultContainerImageMetadataResolver(containerRegistryService);
}

@Bean
public ApplicationConfigurationMetadataResolver metadataResolver(ContainerImageMetadataResolver containerImageMetadataResolver) {
return new BootApplicationConfigurationMetadataResolver(
CompletionTestsMocks.class.getClassLoader(), containerImageMetadataResolver);
}
Expand Down
10 changes: 2 additions & 8 deletions spring-cloud-dataflow-configuration-metadata/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,14 @@
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dataflow-container-registry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-ecr</artifactId>
<version>${aws-java-sdk-ecr.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-resource-docker</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,12 @@

package org.springframework.cloud.dataflow.configuration.metadata;

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.dataflow.configuration.metadata.container.ContainerImageMetadataProperties;
import org.springframework.cloud.dataflow.configuration.metadata.container.ContainerImageMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.container.ContainerImageParser;
import org.springframework.cloud.dataflow.configuration.metadata.container.DefaultContainerImageMetadataResolver;
import org.springframework.cloud.dataflow.configuration.metadata.container.RegistryConfiguration;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.AnonymousRegistryAuthorizer;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.AwsEcrAuthorizer;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.BasicAuthRegistryAuthorizer;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.DockerConfigJsonSecretToRegistryConfigurationConverter;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.DockerOAuth2RegistryAuthorizer;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.DropAuthorizationHeaderOnSignedS3RequestRedirectStrategy;
import org.springframework.cloud.dataflow.configuration.metadata.container.authorization.RegistryAuthorizer;
import org.springframework.cloud.dataflow.container.registry.ContainerRegistryService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

/**
* Automatically exposes an {@link ApplicationConfigurationMetadataResolver} if none is already registered.
Expand All @@ -70,172 +30,19 @@
* @author Christian Tzolov
*/
@Configuration
@EnableConfigurationProperties({ ContainerImageMetadataProperties.class })
public class ApplicationConfigurationMetadataResolverAutoConfiguration {

private static final Logger logger = LoggerFactory.getLogger(ApplicationConfigurationMetadataResolverAutoConfiguration.class);

@Bean
public RegistryAuthorizer dockerOAuth2RegistryAuthorizer(
@Qualifier("containerRestTemplate") RestTemplate containerRestTemplate,
@Qualifier("noSslVerificationContainerRestTemplate") RestTemplate noSslVerificationContainerRestTemplate) {
return new DockerOAuth2RegistryAuthorizer(containerRestTemplate, noSslVerificationContainerRestTemplate);
}

@Bean
public RegistryAuthorizer anonymousRegistryAuthorizer() {
return new AnonymousRegistryAuthorizer();
}

@Bean
public RegistryAuthorizer basicAuthRegistryAuthorizer() {
return new BasicAuthRegistryAuthorizer();
}

@Bean
public RegistryAuthorizer awsRegistryAuthorizer() {
return new AwsEcrAuthorizer();
}

@Bean
public ContainerImageParser containerImageParser(ContainerImageMetadataProperties properties) {
return new ContainerImageParser(properties.getDefaultRegistryHost(),
properties.getDefaultRepositoryTag(), properties.getOfficialRepositoryNamespace());
}

@Bean
@ConditionalOnMissingBean(ContainerImageMetadataResolver.class)
public DefaultContainerImageMetadataResolver containerImageMetadataResolver(
@Qualifier("containerRestTemplate") RestTemplate containerRestTemplate,
@Qualifier("noSslVerificationContainerRestTemplate") RestTemplate trustAnySslRestTemplate,
ContainerImageParser imageNameParser,
Map<String, RegistryConfiguration> registryConfigurationMap,
List<RegistryAuthorizer> registryAuthorizers) {
return new DefaultContainerImageMetadataResolver(containerRestTemplate, trustAnySslRestTemplate, imageNameParser,
registryConfigurationMap, registryAuthorizers);
}

@Bean
@ConditionalOnMissingBean(ApplicationConfigurationMetadataResolver.class)
public ApplicationConfigurationMetadataResolver metadataResolver(
DefaultContainerImageMetadataResolver containerImageMetadataResolver) {
ContainerImageMetadataResolver containerImageMetadataResolver) {
return new BootApplicationConfigurationMetadataResolver(containerImageMetadataResolver);
}

@Bean
public Map<String, RegistryConfiguration> registryConfigurationMap(ContainerImageMetadataProperties properties,
@Value("${.dockerconfigjson:#{null}}") String dockerConfigJsonSecret,
DockerConfigJsonSecretToRegistryConfigurationConverter secretToRegistryConfigurationConverter) {

// Retrieve registry configurations, explicitly declared via properties.
Map<String, RegistryConfiguration> registryConfigurationMap =
properties.getRegistryConfigurations().entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().getRegistryHost(), Map.Entry::getValue));

// For dockeroauth2 configuration that doesn't have the Docker OAuth2 Access Token entry point set,
// use the secretToRegistryConfigurationConverter.getDockerTokenServiceUri() tor retrieve the entry point.
registryConfigurationMap.values().stream()
.filter(rc -> rc.getAuthorizationType() == RegistryConfiguration.AuthorizationType.dockeroauth2)
.filter(rc -> !rc.getExtra().containsKey(DockerOAuth2RegistryAuthorizer.DOCKER_REGISTRY_AUTH_URI_KEY))
.forEach(rc -> secretToRegistryConfigurationConverter.getDockerTokenServiceUri(rc.getRegistryHost())
.ifPresent(tokenServiceUri -> rc.getExtra().put(
DockerOAuth2RegistryAuthorizer.DOCKER_REGISTRY_AUTH_URI_KEY,
tokenServiceUri)));

if (!StringUtils.isEmpty(dockerConfigJsonSecret)) {
// Retrieve registry configurations from mounted kubernetes Secret.
Map<String, RegistryConfiguration> secretsRegistryConfigurationMap
= secretToRegistryConfigurationConverter.convert(dockerConfigJsonSecret);

if (!CollectionUtils.isEmpty(secretsRegistryConfigurationMap)) {
// Merge the Secret and the Property based registry configurations.
// The properties values when set has precedence over the Secret retrieved one. Later allow to override
// some of the Secret properties or set the disableSslVerification for secret based configs.
registryConfigurationMap = Stream.concat(
secretsRegistryConfigurationMap.entrySet().stream(),
registryConfigurationMap.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(secretConf, propConf) -> {
RegistryConfiguration rc = new RegistryConfiguration();
rc.setRegistryHost(secretConf.getRegistryHost());
rc.setUser(StringUtils.hasText(propConf.getUser()) ? propConf.getUser() : secretConf.getUser());
rc.setSecret(StringUtils.hasText(propConf.getSecret()) ? propConf.getSecret() : secretConf.getSecret());
rc.setAuthorizationType(propConf.getAuthorizationType() != null ? propConf.getAuthorizationType() : secretConf.getAuthorizationType());
rc.setManifestMediaType(StringUtils.hasText(propConf.getManifestMediaType()) ? propConf.getManifestMediaType() : secretConf.getManifestMediaType());
rc.setDisableSslVerification(propConf.isDisableSslVerification());
rc.getExtra().putAll(secretConf.getExtra());
rc.getExtra().putAll(propConf.getExtra());
return rc;
}
));
}
}

logger.info("Final Registry Configurations: " + registryConfigurationMap);

return registryConfigurationMap;
}

@Bean
public DockerConfigJsonSecretToRegistryConfigurationConverter secretToRegistryConfigurationConverter(
@Qualifier("noSslVerificationContainerRestTemplate") RestTemplate trustAnySslRestTemplate) {
return new DockerConfigJsonSecretToRegistryConfigurationConverter(trustAnySslRestTemplate);
}

@Bean
@ConditionalOnMissingBean(name = "containerRestTemplate")
public RestTemplate containerRestTemplate(RestTemplateBuilder builder) {
// Create a RestTemplate that uses custom request factory
return this.initRestTemplate(builder, HttpClients.custom());
}

@Bean
@ConditionalOnMissingBean(name = "noSslVerificationContainerRestTemplate")
public RestTemplate noSslVerificationContainerRestTemplate(RestTemplateBuilder builder)
throws NoSuchAlgorithmException, KeyManagementException {

// Trust manager that blindly trusts all SSL certificates.
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}

public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
// Install trust manager to SSL Context.
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

// Create a RestTemplate that uses custom request factory
return initRestTemplate(builder,
HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE));
}

private RestTemplate initRestTemplate(RestTemplateBuilder restTemplateBuilder, HttpClientBuilder clientBuilder) {
StringHttpMessageConverter octetToStringMessageConverter = new StringHttpMessageConverter();
List<MediaType> mediaTypeList = new ArrayList(octetToStringMessageConverter.getSupportedMediaTypes());
mediaTypeList.add(MediaType.APPLICATION_OCTET_STREAM);
octetToStringMessageConverter.setSupportedMediaTypes(mediaTypeList);

clientBuilder.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build());

HttpComponentsClientHttpRequestFactory customRequestFactory =
new HttpComponentsClientHttpRequestFactory(
clientBuilder.setRedirectStrategy(new DropAuthorizationHeaderOnSignedS3RequestRedirectStrategy()).build());

return restTemplateBuilder
.additionalMessageConverters(octetToStringMessageConverter)
.requestFactory(() -> customRequestFactory)
.build();
@ConditionalOnMissingBean(ContainerImageMetadataResolver.class)
public ContainerImageMetadataResolver containerImageMetadataResolver(
ContainerRegistryService containerRegistryService) {
return new DefaultContainerImageMetadataResolver(containerRegistryService);
}
}
Loading

0 comments on commit 8644a5c

Please sign in to comment.