diff --git a/clouddriver-appengine/clouddriver-appengine.gradle b/clouddriver-appengine/clouddriver-appengine.gradle index 993614097ea..ac71db2fc54 100644 --- a/clouddriver-appengine/clouddriver-appengine.gradle +++ b/clouddriver-appengine/clouddriver-appengine.gradle @@ -20,10 +20,8 @@ dependencies { implementation "io.spinnaker.kork:kork-cloud-config-server" implementation "io.spinnaker.kork:kork-moniker" implementation "io.spinnaker.kork:kork-retrofit" - implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" + implementation "io.spinnaker.kork:kork-web" implementation "com.netflix.spectator:spectator-api" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" implementation "commons-io:commons-io" implementation "org.apache.commons:commons-compress:1.21" implementation "org.apache.groovy:groovy" diff --git a/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/artifacts/config/StorageConfigurationProperties.java b/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/artifacts/config/StorageConfigurationProperties.java index fe8c8322309..ff16acec782 100644 --- a/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/artifacts/config/StorageConfigurationProperties.java +++ b/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/artifacts/config/StorageConfigurationProperties.java @@ -22,8 +22,6 @@ import java.util.NoSuchElementException; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; -import retrofit.client.Response; -import retrofit.mime.TypedByteArray; @Data @ConfigurationProperties("artifacts.gcs") @@ -33,10 +31,6 @@ public class StorageConfigurationProperties { public static class ManagedAccount { String name; String jsonPath; - - public static String responseToString(Response response) { - return new String(((TypedByteArray) response.getBody()).getBytes()); - } } ManagedAccount getAccount(String name) { diff --git a/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineConfigurationProperties.java b/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineConfigurationProperties.java index 899e97b84dd..54e961e9760 100644 --- a/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineConfigurationProperties.java +++ b/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineConfigurationProperties.java @@ -18,23 +18,21 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.jakewharton.retrofit.Ok3Client; import com.netflix.spinnaker.clouddriver.appengine.AppengineJobExecutor; import com.netflix.spinnaker.clouddriver.googlecommon.config.GoogleCommonManagedAccount; -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler; +import com.netflix.spinnaker.config.DefaultServiceEndpoint; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import java.io.File; import java.util.ArrayList; import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; -import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; import org.springframework.util.StringUtils; -import retrofit.RestAdapter; -import retrofit.client.Response; -import retrofit.converter.JacksonConverter; -import retrofit.http.GET; -import retrofit.http.Headers; -import retrofit.mime.TypedByteArray; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Headers; @Data public class AppengineConfigurationProperties { @@ -63,7 +61,10 @@ public static class ManagedAccount extends GoogleCommonManagedAccount { private List omitVersions; private Long cachingIntervalSeconds; - public void initialize(AppengineJobExecutor jobExecutor, String gcloudPath) { + public void initialize( + AppengineJobExecutor jobExecutor, + String gcloudPath, + ServiceClientProvider serviceClientProvider) { if (!StringUtils.isEmpty(getJsonPath())) { jobExecutor.runCommand( List.of(gcloudPath, "auth", "activate-service-account", "--key-file", getJsonPath())); @@ -83,14 +84,15 @@ public void initialize(AppengineJobExecutor jobExecutor, String gcloudPath) { throw new RuntimeException("Could not find read JSON configuration file.", e); } } else { - MetadataService metadataService = createMetadataService(); + MetadataService metadataService = createMetadataService(serviceClientProvider); try { if (StringUtils.isEmpty(getProject())) { - setProject(responseToString(metadataService.getProject())); + setProject(Retrofit2SyncCall.execute(metadataService.getProject()).string()); } this.computedServiceAccountEmail = - responseToString(metadataService.getApplicationDefaultServiceAccountEmail()); + Retrofit2SyncCall.execute(metadataService.getApplicationDefaultServiceAccountEmail()) + .string(); } catch (Exception e) { throw new RuntimeException( "Could not find application default credentials for App Engine.", e); @@ -98,30 +100,19 @@ public void initialize(AppengineJobExecutor jobExecutor, String gcloudPath) { } } - static MetadataService createMetadataService() { - OkHttpClient okHttpClient = new OkHttpClient.Builder().retryOnConnectionFailure(true).build(); - RestAdapter restAdapter = - new RestAdapter.Builder() - .setEndpoint(metadataUrl) - .setConverter(new JacksonConverter()) - .setClient(new Ok3Client(okHttpClient)) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build(); - return restAdapter.create(MetadataService.class); + static MetadataService createMetadataService(ServiceClientProvider serviceClientProvider) { + return serviceClientProvider.getService( + MetadataService.class, new DefaultServiceEndpoint("metadata", metadataUrl)); } interface MetadataService { @Headers("Metadata-Flavor: Google") @GET("/project/project-id") - Response getProject(); + Call getProject(); @Headers("Metadata-Flavor: Google") @GET("/instance/service-accounts/default/email") - Response getApplicationDefaultServiceAccountEmail(); - } - - static String responseToString(Response response) { - return new String(((TypedByteArray) response.getBody()).getBytes()); + Call getApplicationDefaultServiceAccountEmail(); } public enum GcloudReleaseTrack { diff --git a/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineCredentialsConfiguration.java b/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineCredentialsConfiguration.java index d3ccaf99632..3041d8d9744 100644 --- a/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineCredentialsConfiguration.java +++ b/clouddriver-appengine/src/main/java/com/netflix/spinnaker/clouddriver/appengine/config/AppengineCredentialsConfiguration.java @@ -27,6 +27,7 @@ import com.netflix.spinnaker.credentials.MapBackedCredentialsRepository; import com.netflix.spinnaker.credentials.definition.AbstractCredentialsLoader; import com.netflix.spinnaker.credentials.poller.Poller; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import com.netflix.spinnaker.kork.configserver.ConfigFileService; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -58,7 +59,8 @@ public CredentialsRepository appengineCredenti AppengineConfigurationProperties configurationProperties, AppengineJobExecutor jobExecutor, ConfigFileService configFileService, - String clouddriverUserAgentApplicationName) { + String clouddriverUserAgentApplicationName, + ServiceClientProvider serviceClientProvider) { return new CredentialsTypeBaseConfiguration( applicationContext, CredentialsTypeProperties @@ -74,7 +76,7 @@ public CredentialsRepository appengineCredenti if (StringUtils.isEmpty(gcloudPath)) { gcloudPath = "gcloud"; } - a.initialize(jobExecutor, gcloudPath); + a.initialize(jobExecutor, gcloudPath, serviceClientProvider); String jsonKey = configFileService.getContents(a.getJsonPath()); return new AppengineNamedAccountCredentials.Builder() diff --git a/clouddriver-appengine/src/test/java/com/netflix/spinnaker/clouddriver/appengine/config/AppEngineAccountCredentialsRepoTest.java b/clouddriver-appengine/src/test/java/com/netflix/spinnaker/clouddriver/appengine/config/AppEngineAccountCredentialsRepoTest.java index fd9ebede2fc..afab6d53650 100644 --- a/clouddriver-appengine/src/test/java/com/netflix/spinnaker/clouddriver/appengine/config/AppEngineAccountCredentialsRepoTest.java +++ b/clouddriver-appengine/src/test/java/com/netflix/spinnaker/clouddriver/appengine/config/AppEngineAccountCredentialsRepoTest.java @@ -10,6 +10,7 @@ import com.netflix.spinnaker.clouddriver.names.NamerRegistry; import com.netflix.spinnaker.credentials.CredentialsLifecycleHandler; import com.netflix.spinnaker.credentials.CredentialsRepository; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import com.netflix.spinnaker.kork.configserver.ConfigFileService; import org.junit.jupiter.api.Test; import org.springframework.boot.context.annotation.UserConfigurations; @@ -75,5 +76,10 @@ Registry getRegistry() { String getClouddriverUserAgentApplicationName() { return "clouddriverUserAgentApplicationName"; } + + @Bean + ServiceClientProvider getServiceClientProvider() { + return mock(ServiceClientProvider.class); + } } } diff --git a/clouddriver-artifacts/clouddriver-artifacts.gradle b/clouddriver-artifacts/clouddriver-artifacts.gradle index d9855482aa2..1e4f1dbce1a 100644 --- a/clouddriver-artifacts/clouddriver-artifacts.gradle +++ b/clouddriver-artifacts/clouddriver-artifacts.gradle @@ -32,6 +32,7 @@ dependencies { implementation "io.spinnaker.kork:kork-credentials" implementation "io.spinnaker.kork:kork-annotations" implementation "io.spinnaker.kork:kork-exceptions" + implementation "io.spinnaker.kork:kork-retrofit" implementation "io.spinnaker.kork:kork-security" implementation "com.netflix.spectator:spectator-api" implementation("com.netflix.spectator:spectator-ext-aws") { diff --git a/clouddriver-artifacts/src/main/java/com/netflix/spinnaker/clouddriver/artifacts/front50/Front50ArtifactCredentials.java b/clouddriver-artifacts/src/main/java/com/netflix/spinnaker/clouddriver/artifacts/front50/Front50ArtifactCredentials.java index 718af5c4905..816bff14cfa 100644 --- a/clouddriver-artifacts/src/main/java/com/netflix/spinnaker/clouddriver/artifacts/front50/Front50ArtifactCredentials.java +++ b/clouddriver-artifacts/src/main/java/com/netflix/spinnaker/clouddriver/artifacts/front50/Front50ArtifactCredentials.java @@ -24,6 +24,7 @@ import com.netflix.spinnaker.clouddriver.core.services.Front50Service; import com.netflix.spinnaker.kork.annotations.NonnullByDefault; import com.netflix.spinnaker.kork.artifacts.model.Artifact; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import com.netflix.spinnaker.security.AuthenticatedRequest; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -73,19 +74,23 @@ public InputStream download(Artifact artifact) throws IOException { pipelineTemplate = AuthenticatedRequest.allowAnonymous( () -> - front50Service.getV2PipelineTemplate( - result.pipelineTemplateId, "", result.version)); + Retrofit2SyncCall.execute( + front50Service.getV2PipelineTemplate( + result.pipelineTemplateId, "", result.version))); } else if (artifactId.contains(":")) { SplitResult result = splitReferenceOnToken(artifactId, ":"); pipelineTemplate = AuthenticatedRequest.allowAnonymous( () -> - front50Service.getV2PipelineTemplate( - result.pipelineTemplateId, result.version, "")); + Retrofit2SyncCall.execute( + front50Service.getV2PipelineTemplate( + result.pipelineTemplateId, result.version, ""))); } else { pipelineTemplate = AuthenticatedRequest.allowAnonymous( - () -> front50Service.getV2PipelineTemplate(artifactId, "", "")); + () -> + Retrofit2SyncCall.execute( + front50Service.getV2PipelineTemplate(artifactId, "", ""))); } return new ByteArrayInputStream(objectMapper.writeValueAsBytes(pipelineTemplate)); @@ -93,7 +98,9 @@ public InputStream download(Artifact artifact) throws IOException { @Override public List getArtifactNames() { - return front50Service.listV2PipelineTemplates(Collections.singletonList("global")).stream() + return Retrofit2SyncCall.execute( + front50Service.listV2PipelineTemplates(Collections.singletonList("global"))) + .stream() .map(t -> (String) t.get("id")) .distinct() .collect(Collectors.toList()); diff --git a/clouddriver-aws/clouddriver-aws.gradle b/clouddriver-aws/clouddriver-aws.gradle index e3c9306d498..c8238f5f9fb 100644 --- a/clouddriver-aws/clouddriver-aws.gradle +++ b/clouddriver-aws/clouddriver-aws.gradle @@ -40,8 +40,8 @@ dependencies { implementation "io.spinnaker.kork:kork-credentials" implementation "io.spinnaker.kork:kork-moniker" implementation "io.spinnaker.kork:kork-retrofit" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" + implementation "io.spinnaker.kork:kork-web" + implementation "com.squareup.retrofit2:converter-jackson" implementation "io.reactivex:rxjava" implementation "org.apache.httpcomponents:httpclient" implementation "org.apache.httpcomponents:httpcore" @@ -63,6 +63,7 @@ dependencies { testImplementation "org.spockframework:spock-spring" testImplementation "org.springframework.boot:spring-boot-starter-test" testImplementation "org.springframework:spring-test" + testImplementation "com.squareup.retrofit2:retrofit-mock" integrationImplementation project(":clouddriver-web") integrationImplementation "org.springframework:spring-test" diff --git a/clouddriver-aws/src/integration/java/com/netflix/spinnaker/clouddriver/aws/test/ModifyServerGroupLaunchTemplateSpec.java b/clouddriver-aws/src/integration/java/com/netflix/spinnaker/clouddriver/aws/test/ModifyServerGroupLaunchTemplateSpec.java index c4a7238e137..be0c3a95656 100644 --- a/clouddriver-aws/src/integration/java/com/netflix/spinnaker/clouddriver/aws/test/ModifyServerGroupLaunchTemplateSpec.java +++ b/clouddriver-aws/src/integration/java/com/netflix/spinnaker/clouddriver/aws/test/ModifyServerGroupLaunchTemplateSpec.java @@ -81,6 +81,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ActiveProfiles; +import retrofit2.mock.Calls; /** * Test class for general test cases related to CreateServerGroup operation. Note: launch template @@ -178,7 +179,7 @@ public void setup() { Map applicationMap = new HashMap(); applicationMap.put("application", "myAwsApp"); applicationMap.put("legacyUdf", null); - when(mockFront50Service.getApplication(ASG_NAME)).thenReturn(applicationMap); + when(mockFront50Service.getApplication(ASG_NAME)).thenReturn(Calls.response(applicationMap)); // mock EC2 responses when(mockRegionScopedProvider.getAmazonEC2()).thenReturn(mockEc2); diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProvider.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProvider.groovy index c97dd0de408..37084f197a1 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProvider.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProvider.groovy @@ -22,6 +22,7 @@ import com.netflix.spinnaker.clouddriver.core.services.Front50Service import com.netflix.spinnaker.kork.annotations.VisibleForTesting import com.netflix.spinnaker.kork.core.RetrySupport import com.netflix.spinnaker.kork.exceptions.SpinnakerException +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException @@ -52,7 +53,7 @@ class LocalFileUserDataProvider implements UserDataProvider { boolean isLegacyUdf(String account, String applicationName) { Closure result = { try { - Map application = front50Service.getApplication(applicationName) + Map application = Retrofit2SyncCall.execute(front50Service.getApplication(applicationName)) if (application.legacyUdf == null) { return localFileUserDataProperties.defaultLegacyUdf } diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApi.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApi.groovy index 9bc3e57e153..a595cb0ad4b 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApi.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApi.groovy @@ -23,12 +23,13 @@ import com.netflix.spinnaker.clouddriver.aws.model.edda.EddaRule import com.netflix.spinnaker.clouddriver.aws.model.edda.LoadBalancerInstanceState import com.netflix.spinnaker.clouddriver.aws.model.edda.TargetGroupAttributes import com.netflix.spinnaker.clouddriver.aws.model.edda.TargetGroupHealth -import retrofit.http.GET -import retrofit.http.Path +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path interface EddaApi { @GET('/REST/v2/view/loadBalancerInstances;_expand') - List loadBalancerInstances() + Call> loadBalancerInstances() @GET('/REST/v2/view/targetGroupHealth;_expand') List targetGroupHealth() diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApiFactory.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApiFactory.groovy index 3335dc3925d..eb82c313a1b 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApiFactory.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaApiFactory.groovy @@ -16,27 +16,21 @@ package com.netflix.spinnaker.clouddriver.aws.edda -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler -import retrofit.RestAdapter -import retrofit.converter.Converter +import com.netflix.spinnaker.config.DefaultServiceEndpoint +import com.netflix.spinnaker.kork.client.ServiceClientProvider import java.util.regex.Pattern class EddaApiFactory { - private Converter eddaConverter + private ServiceClientProvider serviceClientProvider - EddaApiFactory(Converter eddaConverter) { - this.eddaConverter = eddaConverter + EddaApiFactory(ServiceClientProvider serviceClientProvider) { + this.serviceClientProvider = serviceClientProvider } public EddaApi createApi(String endpointTemplate, String region) { if (endpointTemplate) { - return new RestAdapter.Builder() - .setConverter(eddaConverter) - .setEndpoint(endpointTemplate.replaceAll(Pattern.quote('{{region}}'), region)) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build() - .create(EddaApi) + return serviceClientProvider.getService(EddaApi, new DefaultServiceEndpoint("eddaapi", endpointTemplate.replaceAll(Pattern.quote('{{region}}'), region))) } return null } diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaConfiguration.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaConfiguration.groovy index 2b08bc715cf..0fdebc0ac9e 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaConfiguration.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/edda/EddaConfiguration.groovy @@ -16,26 +16,16 @@ package com.netflix.spinnaker.clouddriver.aws.edda -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.netflix.awsobjectmapper.AmazonObjectMapper -import com.netflix.awsobjectmapper.AmazonObjectMapperConfigurer +import com.netflix.spinnaker.kork.client.ServiceClientProvider import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import retrofit.converter.Converter -import retrofit.converter.JacksonConverter @Configuration class EddaConfiguration { - @Bean - Converter eddaConverter() { - new JacksonConverter(AmazonObjectMapperConfigurer.createConfigured() - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) - } @Bean - EddaApiFactory eddaApiFactory(Converter eddaConverter) { - new EddaApiFactory(eddaConverter) + EddaApiFactory eddaApiFactory(ServiceClientProvider serviceClientProvider) { + new EddaApiFactory(serviceClientProvider) } } diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonApplicationLoadBalancerCachingAgent.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonApplicationLoadBalancerCachingAgent.groovy index 23c9231ad79..24b749e39d6 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonApplicationLoadBalancerCachingAgent.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonApplicationLoadBalancerCachingAgent.groovy @@ -42,6 +42,7 @@ import com.netflix.spinnaker.clouddriver.aws.security.EddaTimeoutConfig import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials import com.netflix.spinnaker.clouddriver.cache.OnDemandAgent import com.netflix.spinnaker.clouddriver.core.provider.agent.HealthProvidingCachingAgent +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import static com.netflix.spinnaker.cats.agent.AgentDataType.Authority.AUTHORITATIVE import static com.netflix.spinnaker.cats.agent.AgentDataType.Authority.INFORMATIVE @@ -202,8 +203,8 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc Map> targetGroupArnToHealths Map> targetGroupArnToAttributes if (useEdda) { - List targetGroupAttributesList = eddaApi.targetGroupAttributes() - List targetGroupHealthList = eddaApi.targetGroupHealth() + List targetGroupAttributesList = Retrofit2SyncCall.execute(eddaApi.targetGroupAttributes()) + List targetGroupHealthList = Retrofit2SyncCall.execute(eddaApi.targetGroupHealth()) targetGroupArnToAttributes = targetGroupAttributesList.collectEntries { [(it.targetGroupArn): it.attributes] } targetGroupArnToHealths = targetGroupHealthList.collectEntries { [(it.targetGroupArn): it.health] } } else { @@ -232,7 +233,7 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc boolean useEdda) { Map> loadBalancerArnToAttributes if (useEdda) { - loadBalancerArnToAttributes = eddaApi.applicationLoadBalancerAttributes().collectEntries { + loadBalancerArnToAttributes = Retrofit2SyncCall.execute(eddaApi.applicationLoadBalancerAttributes()).collectEntries { [(it.loadBalancerArn): it.attributes] } } else { @@ -255,7 +256,7 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc if (useEdda) { loadBalancerArnToListeners.putAll( - eddaApi.allListeners().flatten().groupBy { Listener listener -> + Retrofit2SyncCall.execute(eddaApi.allListeners()).flatten().groupBy { Listener listener -> listener.loadBalancerArn } ) @@ -264,7 +265,7 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc [(it.listenerArn): it] } - eddaApi.allRules().flatten().each { EddaRule eddaRule -> + Retrofit2SyncCall.execute(eddaApi.allRules()).flatten().each { EddaRule eddaRule -> def listener = listenerByListenerArn.get(eddaRule.listenerArn) if (listener) { listenerToRules[listener].addAll(eddaRule.rules) diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonLoadBalancerCachingAgent.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonLoadBalancerCachingAgent.groovy index 49a26f7dfa7..c537c125fc2 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonLoadBalancerCachingAgent.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/AmazonLoadBalancerCachingAgent.groovy @@ -37,6 +37,7 @@ import com.netflix.spinnaker.clouddriver.aws.edda.EddaApi import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials import com.netflix.spinnaker.clouddriver.cache.OnDemandAgent +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.INSTANCES import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.LOAD_BALANCERS @@ -216,7 +217,7 @@ class AmazonLoadBalancerCachingAgent extends AbstractAmazonLoadBalancerCachingAg boolean useEdda) { Map loadBalancerNameToAttributes if (useEdda) { - loadBalancerNameToAttributes = eddaApi.classicLoadBalancerAttributes().collectEntries { + loadBalancerNameToAttributes = Retrofit2SyncCall.execute(eddaApi.classicLoadBalancerAttributes()).collectEntries { [(it.name): it.attributes] } } else { diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/EddaLoadBalancerCachingAgent.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/EddaLoadBalancerCachingAgent.groovy index f9529fc5252..ff53c0b7ddf 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/EddaLoadBalancerCachingAgent.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/provider/agent/EddaLoadBalancerCachingAgent.groovy @@ -33,6 +33,7 @@ import com.netflix.spinnaker.clouddriver.aws.data.Keys import com.netflix.spinnaker.clouddriver.aws.model.edda.InstanceLoadBalancers import com.netflix.spinnaker.clouddriver.aws.model.edda.LoadBalancerInstanceState import com.netflix.spinnaker.clouddriver.aws.provider.AwsProvider +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.HEALTH import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.INSTANCES @@ -78,7 +79,7 @@ class EddaLoadBalancerCachingAgent implements CachingAgent, HealthProvidingCachi @Override CacheResult loadData(ProviderCache providerCache) { log.info("Describing items in ${agentType}") - List balancerInstances = eddaApi.loadBalancerInstances() + List balancerInstances = Retrofit2SyncCall.execute(eddaApi.loadBalancerInstances()) List ilbs = InstanceLoadBalancers.fromLoadBalancerInstanceState(balancerInstances) Collection lbHealths = new ArrayList(ilbs.size()) diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/services/RegionScopedProviderFactory.groovy b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/services/RegionScopedProviderFactory.groovy index 7f00565d68d..0e039f019f9 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/services/RegionScopedProviderFactory.groovy +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/services/RegionScopedProviderFactory.groovy @@ -34,6 +34,7 @@ import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials import com.netflix.spinnaker.clouddriver.eureka.api.Eureka import com.netflix.spinnaker.clouddriver.eureka.deploy.ops.EurekaUtil import com.netflix.spinnaker.clouddriver.model.ClusterProvider +import com.netflix.spinnaker.kork.client.ServiceClientProvider import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -58,6 +59,9 @@ class RegionScopedProviderFactory { @Autowired(required = false) Collection amazonResourceTaggers + @Autowired + ServiceClientProvider serviceClientProvider + RegionScopedProvider forRegion(NetflixAmazonCredentials amazonCredentials, String region) { new RegionScopedProvider(amazonCredentials, region) } @@ -134,7 +138,7 @@ class RegionScopedProviderFactory { if (!amazonCredentials.discoveryEnabled) { throw new IllegalStateException('discovery not enabled') } - EurekaUtil.getWritableEureka(amazonCredentials.discovery, region) + EurekaUtil.getWritableEureka(amazonCredentials.discovery, region, serviceClientProvider) } AsgBuilder getAsgBuilderForLaunchConfiguration() { diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DisableAsgAtomicOperationUnitSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DisableAsgAtomicOperationUnitSpec.groovy index b21f54f6ccf..5b4d34d680e 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DisableAsgAtomicOperationUnitSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DisableAsgAtomicOperationUnitSpec.groovy @@ -30,8 +30,11 @@ import com.netflix.spinnaker.clouddriver.aws.model.AutoScalingProcessType import com.netflix.spinnaker.clouddriver.data.task.DefaultTaskStatus import com.netflix.spinnaker.clouddriver.data.task.TaskState import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException -import retrofit.RetrofitError -import retrofit.client.Response +import okhttp3.MediaType +import okhttp3.ResponseBody +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls import spock.lang.Unroll class DisableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnitSpecSupport { @@ -90,12 +93,12 @@ class DisableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnit throw new LoadBalancerNotFoundException("Does not exist") } 1 * eureka.getInstanceInfo('i1') >> - [ + Calls.response([ instance: [ app: "asg1" ] - ] - 1 * eureka.updateInstanceStatus('asg1', 'i1', 'OUT_OF_SERVICE') >> new Response('http://foo', 200, 'OK', [], null) + ]) + 1 * eureka.updateInstanceStatus('asg1', 'i1', 'OUT_OF_SERVICE') >> Calls.response(null) 2 * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 0 * task.fail() } @@ -116,12 +119,12 @@ class DisableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnit 2 * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 1 * asgService.getAutoScalingGroup(_) >> asg 1 * eureka.getInstanceInfo('i1') >> - [ + Calls.response([ instance: [ app: "asg1" ] - ] - 1 * eureka.updateInstanceStatus('asg1', 'i1', 'OUT_OF_SERVICE') >> new Response('http://foo', 200, 'OK', [], null) + ]) + 1 * eureka.updateInstanceStatus('asg1', 'i1', 'OUT_OF_SERVICE') >> Calls.response(null) } def 'should not fail because of discovery errors on disable'() { @@ -133,9 +136,7 @@ class DisableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnit describeInstanceResult.getReservations() >> [new Reservation().withInstances(instance)] eureka.updateInstanceStatus('asg1', 'i1', 'OUT_OF_SERVICE') >> { - throw new SpinnakerHttpException(new RetrofitError("error", "url", - new Response("url", 503, "service unavailable", [], null), - null, null, null, null)) + throw makeSpinnakerHttpException(503) } when: @@ -146,11 +147,11 @@ class DisableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnit _ * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) _ * asgService.getAutoScalingGroup(_) >> asg _ * eureka.getInstanceInfo('i1') >> - [ + Calls.response([ instance: [ app: "asg1" ] - ] + ]) 0 * task.fail() } @@ -250,4 +251,21 @@ class DisableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnit 99 || 0 } + private static SpinnakerHttpException makeSpinnakerHttpException(int status) { + String url = "https://some-url"; + retrofit2.Response retrofit2Response = + retrofit2.Response.error( + status, + ResponseBody.create( + MediaType.parse("application/json"), "{ \"message\": \"arbitrary message\" }")); + + Retrofit retrofit = + new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + return new SpinnakerHttpException(retrofit2Response, retrofit); + } + } diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/EnableAsgAtomicOperationUnitSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/EnableAsgAtomicOperationUnitSpec.groovy index 2be187ac1c0..69dfcf5bae2 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/EnableAsgAtomicOperationUnitSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/EnableAsgAtomicOperationUnitSpec.groovy @@ -26,7 +26,7 @@ import com.netflix.spinnaker.clouddriver.aws.model.AutoScalingProcessType import com.netflix.spinnaker.clouddriver.data.task.DefaultTaskStatus import com.netflix.spinnaker.clouddriver.data.task.TaskState import com.netflix.spinnaker.clouddriver.eureka.deploy.ops.AbstractEurekaSupport -import retrofit.client.Response +import retrofit2.mock.Calls class EnableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnitSpecSupport { @@ -82,13 +82,13 @@ class EnableAsgAtomicOperationUnitSpec extends EnableDisableAtomicOperationUnitS 2 * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 1 * asgService.getAutoScalingGroup(_) >> asg 1 * eureka.getInstanceInfo('i1') >> - [ + Calls.response([ instance: [ app: "asg1", status: "OUT_OF_SERVICE" ] - ] - 1 * eureka.resetInstanceStatus("asg1", "i1", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> new Response('http://foo', 200, '', [], null) + ]) + 1 * eureka.resetInstanceStatus("asg1", "i1", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> Calls.response(null) } def 'should skip discovery if not enabled for account'() { diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/discovery/DiscoverySupportUnitSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/discovery/DiscoverySupportUnitSpec.groovy index 3972659057b..64d5c1c695d 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/discovery/DiscoverySupportUnitSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/discovery/DiscoverySupportUnitSpec.groovy @@ -38,8 +38,12 @@ import com.netflix.spinnaker.clouddriver.eureka.deploy.ops.EurekaSupportConfigur import com.netflix.spinnaker.clouddriver.model.ClusterProvider import com.netflix.spinnaker.clouddriver.model.ServerGroup import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException -import retrofit.RetrofitError -import retrofit.client.Response +import okhttp3.MediaType +import okhttp3.Response +import okhttp3.ResponseBody +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls import spock.lang.Specification import spock.lang.Subject import spock.lang.Unroll @@ -105,6 +109,7 @@ class DiscoverySupportUnitSpec extends Specification { ) then: + discoverySupport.eurekaSupportConfigurationProperties.retryMax * eureka.getInstanceInfo(_) >> {return Calls.response(null)} thrown(AbstractEurekaSupport.RetryableException) discoverySupport.eurekaSupportConfigurationProperties.retryMax * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 0 * eureka.updateInstanceStatus(*_) @@ -155,6 +160,7 @@ class DiscoverySupportUnitSpec extends Specification { ) then: + discoverySupport.eurekaSupportConfigurationProperties.retryMax * eureka.getInstanceInfo(_) >> {return Calls.response(null)} discoverySupport.eurekaSupportConfigurationProperties.retryMax * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 0 * eureka.updateInstanceStatus(*_) 1 * task.updateStatus(_, "Could not find application name in Discovery or AWS, short-circuiting (asg: myapp-test-v000, region: us-east-1)") @@ -176,16 +182,16 @@ class DiscoverySupportUnitSpec extends Specification { then: (instanceIds.size() + 1) * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 1 * eureka.getInstanceInfo(_) >> - [ + Calls.response([ instance: [ app: appName, status: "OUT_OF_SERVICE" ] - ] + ]) 0 * task.fail() instanceIds.each { - 1 * eureka.resetInstanceStatus(appName, it, AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> response(200) + 1 * eureka.resetInstanceStatus(appName, it, AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> Calls.response(null) } where: @@ -210,10 +216,10 @@ class DiscoverySupportUnitSpec extends Specification { then: task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 1 * task.fail() - 1 * eureka.getInstanceInfo(_) >> [ instance: [ app: appName, status: "OUT_OF_SERVICE" ] ] - 1 * eureka.resetInstanceStatus(appName, "bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw new SpinnakerHttpException(httpError(400))} - 1 * eureka.resetInstanceStatus(appName, "good", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {response(200)} - 1 * eureka.resetInstanceStatus(appName, "also-bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw new SpinnakerHttpException(httpError(400))} + 1 * eureka.getInstanceInfo(_) >> Calls.response([ instance: [ app: appName, status: "OUT_OF_SERVICE" ] ]) + 1 * eureka.resetInstanceStatus(appName, "bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw makeSpinnakerHttpException(400)} + 1 * eureka.resetInstanceStatus(appName, "good", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> Calls.response(null) + 1 * eureka.resetInstanceStatus(appName, "also-bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw makeSpinnakerHttpException(400)} 1 * task.updateStatus("PHASE", { it.startsWith("Looking up discovery") }) 3 * task.updateStatus("PHASE", { it.startsWith("Attempting to mark") }) 2 * task.updateStatus('PHASE', { it.startsWith("Failed updating status") }) @@ -243,10 +249,10 @@ class DiscoverySupportUnitSpec extends Specification { then: task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) - 1 * eureka.getInstanceInfo(_) >> [ instance: [ app: appName, status: "OUT_OF_SERVICE" ] ] - 1 * eureka.resetInstanceStatus(appName, "bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw new SpinnakerHttpException(httpError(500))} - 1 * eureka.resetInstanceStatus(appName, "good", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> response(200) - 1 * eureka.resetInstanceStatus(appName, "also-bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw new SpinnakerHttpException(httpError(500))} + 1 * eureka.getInstanceInfo(_) >> Calls.response([ instance: [ app: appName, status: "OUT_OF_SERVICE" ] ]) + 1 * eureka.resetInstanceStatus(appName, "bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw makeSpinnakerHttpException(500)} + 1 * eureka.resetInstanceStatus(appName, "good", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> Calls.response(null) + 1 * eureka.resetInstanceStatus(appName, "also-bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw makeSpinnakerHttpException(500)} 1 * task.updateStatus("PHASE", { it.startsWith("Looking up discovery") }) 3 * task.updateStatus("PHASE", { it.startsWith("Attempting to mark") }) 0 * task.updateStatus("PHASE", { it.startsWith("Failed marking instances 'UP'")}) @@ -279,10 +285,10 @@ class DiscoverySupportUnitSpec extends Specification { then: task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) - 1 * eureka.getInstanceInfo(_) >> [ instance: [ app: appName, status: "OUT_OF_SERVICE" ] ] - 1 * eureka.resetInstanceStatus(appName, "bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw new SpinnakerHttpException(httpError(500))} - 1 * eureka.resetInstanceStatus(appName, "good", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> response(200) - 1 * eureka.resetInstanceStatus(appName, "also-bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw new SpinnakerHttpException(httpError(500))} + 1 * eureka.getInstanceInfo(_) >> Calls.response([ instance: [ app: appName, status: "OUT_OF_SERVICE" ] ]) + 1 * eureka.resetInstanceStatus(appName, "bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw makeSpinnakerHttpException(500)} + 1 * eureka.resetInstanceStatus(appName, "good", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >>Calls.response(null) + 1 * eureka.resetInstanceStatus(appName, "also-bad", AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> {throw makeSpinnakerHttpException(500)} 1 * task.updateStatus("PHASE", { it.startsWith("Looking up discovery") }) 3 * task.updateStatus("PHASE", { it.startsWith("Attempting to mark") }) 1 * task.updateStatus("PHASE", { it.startsWith("Failed marking instances 'UP'")}) @@ -314,7 +320,7 @@ class DiscoverySupportUnitSpec extends Specification { then: "should only retry a maximum of DISCOVERY_RETRY_MAX times on NOT_FOUND" discoverySupport.eurekaSupportConfigurationProperties.retryMax * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) discoverySupport.eurekaSupportConfigurationProperties.retryMax * eureka.getInstanceInfo(_) >> { - throw new SpinnakerHttpException(httpError(errorCode)) + throw makeSpinnakerHttpException(errorCode) } 0 * task.fail() thrown(SpinnakerHttpException) @@ -328,37 +334,6 @@ class DiscoverySupportUnitSpec extends Specification { errorCode << [404, 406, 503] } - void "should retry on non 200 response from discovery"() { - given: - def task = Mock(Task) - def description = new EnableDisableInstanceDiscoveryDescription( - region: 'us-west-1', - credentials: TestCredential.named('test', [discovery: discoveryUrl]) - ) - - when: - discoverySupport.updateDiscoveryStatusForInstances(description, task, "PHASE", discoveryStatus, instanceIds) - - then: "should only retry a maximum of DISCOVERY_RETRY_MAX times on NOT_FOUND" - 1 * eureka.getInstanceInfo('i-123') >> - [ - instance: [ - app: appName, - status: "OUT_OF_SERVICE" - ] - ] - 3 * eureka.resetInstanceStatus(appName, 'i-123', AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >>> [response(302), response(201), response(200)] - 4 * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) - 0 * task.fail() - - where: - discoveryUrl = "http://us-west-1.discovery.netflix.net" - region = "us-west-1" - discoveryStatus = AbstractEurekaSupport.DiscoveryStatus.UP - appName = "kato" - instanceIds = ["i-123"] - } - @Unroll void "should NOT fails if strict=#strict for #status operation if instance is not found"() { given: @@ -373,13 +348,13 @@ class DiscoverySupportUnitSpec extends Specification { then: "task should not be failed" 1 * eureka.getInstanceInfo('i-123') >> - [ + Calls.response([ instance: [ app: appName ] - ] - eureka.updateInstanceStatus(appName, 'i-123', status.value) >> { throw new SpinnakerHttpException(httpError(404)) } - eureka.resetInstanceStatus(appName, 'i-123', AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> { throw new SpinnakerHttpException(httpError(404)) } + ]) + eureka.updateInstanceStatus(appName, 'i-123', status.value) >> { throw makeSpinnakerHttpException(404) } + eureka.resetInstanceStatus(appName, 'i-123', AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> { throw makeSpinnakerHttpException(404) } task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 0 * task.fail() @@ -406,19 +381,19 @@ class DiscoverySupportUnitSpec extends Specification { then: "should retry on NOT_FOUND" (instanceIds.size() + 1) * task.getStatus() >> new DefaultTaskStatus(TaskState.STARTED) 1 * eureka.getInstanceInfo(_) >> - [ + Calls.response([ instance: [ app: appName, status: "OUT_OF_SERVICE" ] - ] + ]) 1 * task.fail() instanceIds.eachWithIndex { it, idx -> 1 * eureka.resetInstanceStatus(appName, it, AbstractEurekaSupport.DiscoveryStatus.OUT_OF_SERVICE.value) >> { if (!result[idx]) { throw new RuntimeException("blammo") } - return response(200) + return Calls.response(null) } } @@ -529,12 +504,21 @@ class DiscoverySupportUnitSpec extends Specification { ) } - private static RetrofitError httpError(int code) { - RetrofitError.httpError('http://foo', response(code), null, Map) - } - - private static Response response(int code) { - new Response('http://foo', code, 'WAT', [], null) + private static SpinnakerHttpException makeSpinnakerHttpException(int status) { + String url = "https://some-url"; + retrofit2.Response retrofit2Response = + retrofit2.Response.error( + status, + ResponseBody.create( + MediaType.parse("application/json"), "{ \"message\": \"arbitrary message\" }")); + + Retrofit retrofit = + new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + return new SpinnakerHttpException(retrofit2Response, retrofit); } private static AmazonServiceException amazonError(int code) { diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProviderSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProviderSpec.groovy index 2b4355ede93..e4efe7b5858 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProviderSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/userdata/LocalFileUserDataProviderSpec.groovy @@ -21,9 +21,12 @@ import com.netflix.spinnaker.clouddriver.aws.deploy.asg.LaunchConfigurationBuild import com.netflix.spinnaker.clouddriver.aws.userdata.UserDataInput import com.netflix.spinnaker.clouddriver.core.services.Front50Service import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException +import okhttp3.MediaType +import okhttp3.ResponseBody import org.springframework.http.HttpStatus -import retrofit.RetrofitError -import retrofit.client.Response +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory import spock.lang.Specification class LocalFileUserDataProviderSpec extends Specification { @@ -85,13 +88,10 @@ class LocalFileUserDataProviderSpec extends Specification { void "return defaultLegacyUdf if front50.getApplication throws SpinnakerHttpException with NOT_FOUND status"() { given: - RetrofitError notFoundRetrofitError = RetrofitError.httpError("url", - new Response("url", HttpStatus.NOT_FOUND.value(), "Application Not Found", [], null), - null, null) LocalFileUserDataProvider localFileUserDataProvider = new LocalFileUserDataProvider() localFileUserDataProvider.localFileUserDataProperties = new LocalFileUserDataProperties() localFileUserDataProvider.front50Service = Mock(Front50Service) - localFileUserDataProvider.front50Service.getApplication(_) >> {throw new SpinnakerHttpException(notFoundRetrofitError)} + localFileUserDataProvider.front50Service.getApplication(_) >> {throw makeSpinnakerHttpException(HttpStatus.NOT_FOUND.value())} when: def useLegacyUdf = localFileUserDataProvider.isLegacyUdf("test_account", "unknown_application") @@ -103,10 +103,7 @@ class LocalFileUserDataProviderSpec extends Specification { void "isLegacyUdf includes the exception from front50 when failing to read the legacyUdf preference"() { given: // anything other than a 404/not found works here. On 404, isLegacyUdf falls back to a default. - RetrofitError arbitraryRetrofitError = RetrofitError.httpError("url", - new Response("url", HttpStatus.INTERNAL_SERVER_ERROR.value(), "some error", [], null), - null, null) - SpinnakerHttpException spinnakerHttpException = new SpinnakerHttpException(arbitraryRetrofitError) + SpinnakerHttpException spinnakerHttpException = makeSpinnakerHttpException(HttpStatus.INTERNAL_SERVER_ERROR.value()) // To speed up the test by avoiding a bunch of retries, set retryable to // false. spinnakerHttpException.setRetryable(false) @@ -164,4 +161,21 @@ class LocalFileUserDataProviderSpec extends Specification { "export LAUNCH_CONFIG=${LAUNCH_CONFIG_NAME}", ].join('\n') } + + private static SpinnakerHttpException makeSpinnakerHttpException(int status) { + String url = "https://some-url"; + Response retrofit2Response = + Response.error( + status, + ResponseBody.create( + MediaType.parse("application/json"), "{ \"message\": \"arbitrary message\" }")); + + Retrofit retrofit = + new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + return new SpinnakerHttpException(retrofit2Response, retrofit); + } } diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/lifecycle/InstanceTerminationLifecycleWorkerSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/lifecycle/InstanceTerminationLifecycleWorkerSpec.groovy index 1c68d2c40dd..fc1f540755c 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/lifecycle/InstanceTerminationLifecycleWorkerSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/lifecycle/InstanceTerminationLifecycleWorkerSpec.groovy @@ -30,7 +30,8 @@ import com.netflix.spinnaker.clouddriver.eureka.api.Eureka import com.netflix.spinnaker.clouddriver.eureka.deploy.ops.AbstractEurekaSupport.DiscoveryStatus import com.netflix.spinnaker.credentials.CredentialsRepository import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException -import retrofit.RetrofitError +import okhttp3.Request +import retrofit2.mock.Calls import spock.lang.Shared import spock.lang.Specification import spock.lang.Subject @@ -134,7 +135,7 @@ class InstanceTerminationLifecycleWorkerSpec extends Specification { 1 * credentialsRepository.getAll() >> [mgmtCredentials, testCredentials] 1 * awsEurekaSupportProvider.get() >> awsEurekaSupport 1 * awsEurekaSupport.getEureka(_, 'us-west-2') >> eureka - 1 * eureka.updateInstanceStatus('clouddriver', 'i-1234', DiscoveryStatus.OUT_OF_SERVICE.value) + 1 * eureka.updateInstanceStatus('clouddriver', 'i-1234', DiscoveryStatus.OUT_OF_SERVICE.value) >> Calls.response(null) } def 'should process both sns and sqs messages'() { @@ -211,7 +212,7 @@ class InstanceTerminationLifecycleWorkerSpec extends Specification { then: 1 * eureka.updateInstanceStatus(_, _, _) >> { - throw new SpinnakerNetworkException(RetrofitError.networkError("http://some-url", new IOException("cannot connect"))) } + throw new SpinnakerNetworkException(new IOException("timeout"), new Request.Builder().url("http://some-url").build()) } 1 * eureka.updateInstanceStatus(_, _, _) 0 * eureka.updateInstanceStatus(_, _, _) } diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/security/AmazonCredentialsLifecycleHandlerSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/security/AmazonCredentialsLifecycleHandlerSpec.groovy index 52a20a14fd9..cb2850c5824 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/security/AmazonCredentialsLifecycleHandlerSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/security/AmazonCredentialsLifecycleHandlerSpec.groovy @@ -35,6 +35,7 @@ import com.netflix.spinnaker.clouddriver.aws.provider.agent.ImageCachingAgent import com.netflix.spinnaker.clouddriver.aws.provider.agent.ReservationReportCachingAgent import com.netflix.spinnaker.config.AwsConfiguration import com.netflix.spinnaker.credentials.CredentialsRepository +import com.netflix.spinnaker.kork.client.ServiceClientProvider import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService import spock.lang.Specification @@ -48,7 +49,8 @@ class AmazonCredentialsLifecycleHandlerSpec extends Specification { Optional> agentProviders = Optional.empty() def amazonCloudProvider = new AmazonCloudProvider() def registry = new DefaultRegistry() - def eddaApiFactory = new EddaApiFactory() + def serviceClientProvider = Mock(ServiceClientProvider) + def eddaApiFactory = new EddaApiFactory(serviceClientProvider) def dynamicConfigService = Mock(DynamicConfigService) { isEnabled("aws.features.cloud-formation", false) >> false isEnabled("aws.features.launch-templates", false) >> false diff --git a/clouddriver-cloudrun/clouddriver-cloudrun.gradle b/clouddriver-cloudrun/clouddriver-cloudrun.gradle index 1ed13a19428..c8e593faafc 100644 --- a/clouddriver-cloudrun/clouddriver-cloudrun.gradle +++ b/clouddriver-cloudrun/clouddriver-cloudrun.gradle @@ -19,7 +19,6 @@ dependencies { implementation "io.spinnaker.kork:kork-cloud-config-server" implementation "io.spinnaker.kork:kork-moniker" implementation "com.netflix.spectator:spectator-api" - implementation "com.squareup.retrofit:retrofit" implementation "commons-io:commons-io" implementation "org.apache.commons:commons-compress:1.20" implementation "org.apache.groovy:groovy" diff --git a/clouddriver-consul/clouddriver-consul.gradle b/clouddriver-consul/clouddriver-consul.gradle index f16bc69accd..54a812afdaa 100644 --- a/clouddriver-consul/clouddriver-consul.gradle +++ b/clouddriver-consul/clouddriver-consul.gradle @@ -1,14 +1,12 @@ dependencies { implementation project(":clouddriver-core") - implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" implementation "org.apache.groovy:groovy" implementation "org.apache.groovy:groovy-json" implementation "org.springframework.boot:spring-boot-starter-web" implementation "io.spinnaker.kork:kork-retrofit" implementation "io.spinnaker.kork:kork-exceptions" + implementation "io.spinnaker.kork:kork-web" testImplementation "cglib:cglib-nodep" testImplementation "org.objenesis:objenesis" diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/Consul.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/Consul.groovy index 83ad486cad4..2fb75f4363f 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/Consul.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/Consul.groovy @@ -16,33 +16,23 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1 -import com.jakewharton.retrofit.Ok3Client import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig import com.netflix.spinnaker.clouddriver.consul.config.ConsulProperties -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler -import okhttp3.OkHttpClient -import retrofit.RestAdapter -import retrofit.converter.JacksonConverter +import com.netflix.spinnaker.config.DefaultServiceEndpoint +import com.netflix.spinnaker.kork.client.ServiceClientProvider class Consul { T api String endpoint Long timeout - Consul(ConsulConfig config, Class type) { - this(config.agentEndpoint, config.agentPort, ConsulProperties.DEFAULT_TIMEOUT_MILLIS, type) + Consul(ConsulConfig config, Class type, ServiceClientProvider serviceClientProvider) { + this(config.agentEndpoint, config.agentPort, ConsulProperties.DEFAULT_TIMEOUT_MILLIS, type, serviceClientProvider) } - Consul(String endpoint, Integer port, Long timeout, Class type) { + Consul(String endpoint, Integer port, Long timeout, Class type, ServiceClientProvider serviceClientProvider) { this.endpoint = "http://${endpoint}:${port}" this.timeout = timeout - this.api = new RestAdapter.Builder() - .setEndpoint(this.endpoint) - .setClient(new Ok3Client(new OkHttpClient())) - .setConverter(new JacksonConverter()) - .setLogLevel(RestAdapter.LogLevel.NONE) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build() - .create(type) + this.api = serviceClientProvider.getService(type, new DefaultServiceEndpoint(type.name, endpoint)) } } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulAgent.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulAgent.groovy index 7e9dafca61d..627fe8daa57 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulAgent.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulAgent.groovy @@ -19,9 +19,10 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1 import com.netflix.spinnaker.clouddriver.consul.api.v1.services.AgentApi import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig import com.netflix.spinnaker.clouddriver.consul.config.ConsulProperties +import com.netflix.spinnaker.kork.client.ServiceClientProvider class ConsulAgent extends Consul { - ConsulAgent(ConsulConfig config, String agentBaseUrl) { - super(agentBaseUrl, config.agentPort, ConsulProperties.DEFAULT_TIMEOUT_MILLIS, AgentApi) + ConsulAgent(ConsulConfig config, String agentBaseUrl, ServiceClientProvider serviceClientProvider) { + super(agentBaseUrl, config.agentPort, ConsulProperties.DEFAULT_TIMEOUT_MILLIS, AgentApi, serviceClientProvider) } } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulCatalog.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulCatalog.groovy index 55154ab5c00..154d932e781 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulCatalog.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulCatalog.groovy @@ -18,9 +18,10 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1 import com.netflix.spinnaker.clouddriver.consul.api.v1.services.CatalogApi import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig +import com.netflix.spinnaker.kork.client.ServiceClientProvider class ConsulCatalog extends Consul { - ConsulCatalog(ConsulConfig config) { - super(config, CatalogApi) + ConsulCatalog(ConsulConfig config, ServiceClientProvider serviceClientProvider) { + super(config, CatalogApi, serviceClientProvider) } } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulKeyValueStore.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulKeyValueStore.groovy index e1606384d7e..9ecc13c2b2a 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulKeyValueStore.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/ConsulKeyValueStore.groovy @@ -18,9 +18,10 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1 import com.netflix.spinnaker.clouddriver.consul.api.v1.services.KeyValueApi import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig +import com.netflix.spinnaker.kork.client.ServiceClientProvider class ConsulKeyValueStore extends Consul { - ConsulKeyValueStore(ConsulConfig config) { - super(config, KeyValueApi) + ConsulKeyValueStore(ConsulConfig config, ServiceClientProvider serviceClientProvider) { + super(config, KeyValueApi, serviceClientProvider) } } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/AgentApi.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/AgentApi.groovy index f4fef274e9d..e13d7eeb918 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/AgentApi.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/AgentApi.groovy @@ -17,37 +17,42 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1.services import com.netflix.spinnaker.clouddriver.consul.api.v1.model.* -import okhttp3.Response -import retrofit.http.* +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path +import retrofit2.http.Query interface AgentApi { @GET("/v1/agent/checks") - Map checks() + Call> checks() @GET("/v1/agent/services") - Map services() + Call> services() @GET("/v1/agent/self") - AgentDefinition self() + Call self() @GET("/v1/agent/join/{address}") - Response join(@Path("address") String address, @Query("wan") Integer wan) + Call join(@Path("address") String address, @Query("wan") Integer wan) @PUT("/v1/agent/check/register") - Response registerCheck(@Body CheckDefinition check, @Query("token") String tokenId) + Call registerCheck(@Body CheckDefinition check, @Query("token") String tokenId) @PUT("/v1/agent/check/deregister/{checkId}") - Response deregisterCheck(@Path("checkId") String checkId) + Call deregisterCheck(@Path("checkId") String checkId) @PUT("/v1/agent/service/register") - Response registerService(@Body ServiceDefinition service, @Query("token") String tokenId) + Call registerService(@Body ServiceDefinition service, @Query("token") String tokenId) @PUT("/v1/agent/service/deregister/{serviceId}") - Response deregisterService(@Path("serviceId") String serviceId) + Call deregisterService(@Path("serviceId") String serviceId) @PUT("/v1/agent/service/maintenance/{serviceId}") - Response maintenance(@Path("serviceId") String serviceId, @Query("enable") boolean enable, @Query("reason") String reason) + Call maintenance(@Path("serviceId") String serviceId, @Query("enable") boolean enable, @Query("reason") String reason) @PUT("/v1/agent/maintenance") - Response maintenance(@Query("enable") boolean enable, @Query("reason") String reason, @Body String _empty /* Retrofit requires a body, even if it's empty... */) + Call maintenance(@Query("enable") boolean enable, @Query("reason") String reason, @Body String _empty /* Retrofit requires a body, even if it's empty... */) } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/CatalogApi.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/CatalogApi.groovy index 9ada66c4d66..0849789dcd4 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/CatalogApi.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/CatalogApi.groovy @@ -17,16 +17,17 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1.services import com.netflix.spinnaker.clouddriver.consul.api.v1.model.NodeDefinition -import retrofit.http.GET -import retrofit.http.Query +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query interface CatalogApi { @GET("/v1/catalog/datacenters") - List datacenters() + Call> datacenters() @GET("/v1/catalog/nodes") - List nodes(@Query("dc") String dc) + Call> nodes(@Query("dc") String dc) @GET("/v1/catalog/services") - Map> services(@Query("dc") String dc) + Call>> services(@Query("dc") String dc) } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/KeyValueApi.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/KeyValueApi.groovy index 73c557df0d1..2f6b17b772f 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/KeyValueApi.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/api/v1/services/KeyValueApi.groovy @@ -17,21 +17,22 @@ package com.netflix.spinnaker.clouddriver.consul.api.v1.services import com.netflix.spinnaker.clouddriver.consul.api.v1.model.KeyValuePair -import okhttp3.Response -import retrofit.http.Body -import retrofit.http.DELETE -import retrofit.http.GET -import retrofit.http.PUT -import retrofit.http.Path -import retrofit.http.Query +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path +import retrofit2.http.Query interface KeyValueApi { @GET("/v1/kv/{key}") - List getKey(@Path("key") String key, @Query("dc") String dc, @Query("recurse") Boolean recurse) + Call> getKey(@Path("key") String key, @Query("dc") String dc, @Query("recurse") Boolean recurse) @PUT("/v1/kv/{key}") - Response putKey(@Path("key") String key, @Body String data, @Query("dc") String dc) + Call putKey(@Path("key") String key, @Body String data, @Query("dc") String dc) @DELETE("/v1/kv/{key}") - Response deleteKey(@Path("key") String key, @Query("dc") String dc, @Query("recurse") Boolean recurse) + Call deleteKey(@Path("key") String key, @Query("dc") String dc, @Query("recurse") Boolean recurse) } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/config/ConsulConfig.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/config/ConsulConfig.groovy index 3887424543e..50bc2e138de 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/config/ConsulConfig.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/config/ConsulConfig.groovy @@ -17,6 +17,8 @@ package com.netflix.spinnaker.clouddriver.consul.config import com.netflix.spinnaker.clouddriver.consul.api.v1.ConsulCatalog +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException import groovy.util.logging.Slf4j @@ -35,7 +37,7 @@ class ConsulConfig { // Since this is config injected into every participating provider's Spring config, there is no easy way to // standardize where default values should come from. Instead, we require this method to be called after the // config is loaded. - void applyDefaults() { + void applyDefaults(ServiceClientProvider serviceClientProvider) { if (!enabled) { throw new IllegalStateException("Consul not enabled, cannot set defaults") } @@ -50,8 +52,8 @@ class ConsulConfig { if (!datacenters) { try { - def catalog = new ConsulCatalog(this) - datacenters = catalog.api.datacenters() + def catalog = new ConsulCatalog(this, serviceClientProvider) + datacenters = Retrofit2SyncCall.execute(catalog.api.datacenters()) } catch (SpinnakerServerException e) { log.warn "Unable to connect to Consul running on the local Clouddriver instance.", e datacenters = [] diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/EnableDisableConsulInstance.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/EnableDisableConsulInstance.groovy index 4a0c7ecf162..90d005db7e5 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/EnableDisableConsulInstance.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/EnableDisableConsulInstance.groovy @@ -19,6 +19,7 @@ package com.netflix.spinnaker.clouddriver.consul.deploy.ops import com.netflix.spinnaker.clouddriver.consul.api.v1.ConsulAgent import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig import com.netflix.spinnaker.clouddriver.consul.config.ConsulProperties +import com.netflix.spinnaker.kork.client.ServiceClientProvider class EnableDisableConsulInstance { static enum State { @@ -26,8 +27,8 @@ class EnableDisableConsulInstance { disable, } - static void operate(ConsulConfig config, String agentEndpoint, State state) { - def agent = new ConsulAgent(config, agentEndpoint) + static void operate(ConsulConfig config, String agentEndpoint, State state, ServiceClientProvider serviceClientProvider) { + def agent = new ConsulAgent(config, agentEndpoint, serviceClientProvider) // Enabling maintenance mode means the instance is removed from discovery & DNS lookups agent.api.maintenance(state == State.disable, "Spinnaker ${state} Operation", "") diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/RegisterConsulInstance.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/RegisterConsulInstance.groovy index c100c5affaa..1f9f196d0cf 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/RegisterConsulInstance.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/RegisterConsulInstance.groovy @@ -18,12 +18,13 @@ package com.netflix.spinnaker.clouddriver.consul.deploy.ops import com.netflix.spinnaker.clouddriver.consul.api.v1.ConsulAgent import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig +import com.netflix.spinnaker.kork.client.ServiceClientProvider // The difference between "Register" and "EnableDisable" is that "Register" first joins a node to the Consul cluster, // whereas "EnableDisable" keeps a node in a cluster, but changes its discovery status class RegisterConsulInstance { - static void operate(ConsulConfig config, String agentEndpoint) { - def agent = new ConsulAgent("${agentEndpoint}:${config.agentPort}") + static void operate(ConsulConfig config, String agentEndpoint, ServiceClientProvider serviceClientProvider) { + def agent = new ConsulAgent(config, "${agentEndpoint}:${config.agentPort}", serviceClientProvider) agent.api.join(config.servers[0], 0 /* Not joining the WAN, since this is a client node */ ) } } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/UpsertConsulLoadBalancer.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/UpsertConsulLoadBalancer.groovy index 5b02808e993..a8fcde66b5d 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/UpsertConsulLoadBalancer.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/deploy/ops/UpsertConsulLoadBalancer.groovy @@ -22,6 +22,8 @@ import com.netflix.spinnaker.clouddriver.consul.api.v1.model.KeyValuePair import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig import com.netflix.spinnaker.clouddriver.consul.config.ConsulProperties import com.netflix.spinnaker.clouddriver.consul.deploy.description.ConsulLoadBalancerDescription +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import groovy.json.JsonOutput import groovy.json.JsonSlurper @@ -29,13 +31,13 @@ class UpsertConsulLoadBalancer { // This operation is a bit odd, since services in Consul don't exist until they are attached to an instance - and // when a service is first registered, it doesn't have any instances attached. Therefore this operation simply sticks // the service in Consuls KV API for later retrieval & use. - static void operate(ConsulConfig config, ConsulLoadBalancerDescription description) { + static void operate(ConsulConfig config, ConsulLoadBalancerDescription description, ServiceClientProvider serviceClientProvider) { // who comes up with these names?? def jsonSlurper = new JsonSlurper() def objectMapper = new ObjectMapper() - def kvApi = new ConsulKeyValueStore(config).api - List services = kvApi.getKey(description.name, description.datacenter, false) + def kvApi = new ConsulKeyValueStore(config, serviceClientProvider).api + List services = Retrofit2SyncCall.execute(kvApi.getKey(description.name, description.datacenter, false)) ConsulLoadBalancerDescription oldDescription = new ConsulLoadBalancerDescription() if (!services) { @@ -56,6 +58,6 @@ class UpsertConsulLoadBalancer { description.tags = description.tags != null ? description.tags : oldDescription.tags // null check to enable tags = [] def serializedDescription = JsonOutput.toJson(description) - kvApi.putKey(description.name, serializedDescription, description.datacenter) + Retrofit2SyncCall.execute(kvApi.putKey(description.name, serializedDescription, description.datacenter)) } } diff --git a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/provider/ConsulProviderUtils.groovy b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/provider/ConsulProviderUtils.groovy index 770a0eb7199..0f6df58ceb5 100644 --- a/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/provider/ConsulProviderUtils.groovy +++ b/clouddriver-consul/src/main/groovy/com/netflix/spinnaker/clouddriver/consul/provider/ConsulProviderUtils.groovy @@ -23,20 +23,23 @@ import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig import com.netflix.spinnaker.clouddriver.consul.model.ConsulHealth import com.netflix.spinnaker.clouddriver.consul.model.ConsulNode import com.netflix.spinnaker.clouddriver.consul.model.ConsulService +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException import groovy.util.logging.Slf4j @Slf4j class ConsulProviderUtils { - static ConsulNode getHealths(ConsulConfig config, String agent) { + static ConsulNode getHealths(ConsulConfig config, String agent, ServiceClientProvider serviceClientProvider) { def healths = [] def services = [] def running = false try { - healths = new ConsulAgent(config, agent).api.checks()?.collect { String name, CheckResult result -> + def consulAgent = new ConsulAgent(config, agent, serviceClientProvider) + healths = Retrofit2SyncCall.execute(consulAgent.api.checks())?.collect { String name, CheckResult result -> return new ConsulHealth(result: result, source: result.checkID) } ?: [] - services = new ConsulAgent(config, agent).api.services()?.collect { String name, ServiceResult result -> + services = Retrofit2SyncCall.execute(consulAgent.api.services())?.collect { String name, ServiceResult result -> return new ConsulService(result) } ?: [] running = true diff --git a/clouddriver-core/clouddriver-core.gradle b/clouddriver-core/clouddriver-core.gradle index 28771fa1d3d..589eb40e72f 100644 --- a/clouddriver-core/clouddriver-core.gradle +++ b/clouddriver-core/clouddriver-core.gradle @@ -29,13 +29,12 @@ dependencies { implementation "io.spinnaker.kork:kork-core" implementation "io.spinnaker.kork:kork-jedis" implementation "io.spinnaker.kork:kork-retrofit" + implementation "io.spinnaker.kork:kork-retrofit2" implementation "io.spinnaker.kork:kork-web" implementation "io.spinnaker.kork:kork-annotations" implementation "io.spinnaker.kork:kork-moniker" implementation "io.spinnaker.kork:kork-secrets" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" - implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" + implementation "com.squareup.retrofit2:converter-jackson" implementation "io.reactivex:rxjava" implementation "net.jodah:failsafe:1.0.4" implementation "org.apache.groovy:groovy" @@ -63,4 +62,5 @@ dependencies { testImplementation "org.springframework.boot:spring-boot-starter-test" testImplementation "com.google.cloud:google-cloud-secretmanager" testImplementation "io.spinnaker.kork:kork-cloud-config-server" + testImplementation "com.squareup.retrofit2:retrofit-mock" } diff --git a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/core/services/Front50Service.groovy b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/core/services/Front50Service.groovy index 71625029375..f9be846c44e 100644 --- a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/core/services/Front50Service.groovy +++ b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/core/services/Front50Service.groovy @@ -18,60 +18,67 @@ package com.netflix.spinnaker.clouddriver.core.services import com.netflix.spinnaker.clouddriver.model.EntityTags import com.netflix.spinnaker.clouddriver.model.Front50Application -import retrofit.client.Response -import retrofit.http.* +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query +import retrofit2.http.QueryMap interface Front50Service { @GET("/credentials") - List getCredentials() + Call> getCredentials() @GET('/v2/applications') - List> searchByName(@Query("name") String applicationName, - @Query("pageSize") Integer pageSize, - @QueryMap Map filters) + Call>> searchByName(@Query("name") String applicationName, + @Query("pageSize") Integer pageSize, + @QueryMap Map filters) @GET('/v2/applications/{applicationName}') - Map getApplication(@Path('applicationName') String applicationName) + Call getApplication(@Path('applicationName') String applicationName) @GET('/v2/applications?restricted=false') - Set getAllApplicationsUnrestricted() + Call> getAllApplicationsUnrestricted() @GET('/v2/projects/{project}') - Map getProject(@Path('project') String project) + Call getProject(@Path('project') String project) @GET('/v2/projects') - List> searchForProjects(@QueryMap Map params, @Query("pageSize") Integer pageSize) + Call>> searchForProjects(@QueryMap Map params, @Query("pageSize") Integer pageSize) @POST('/snapshots') - Response saveSnapshot(@Body Map snapshot) + Call saveSnapshot(@Body Map snapshot) @GET('/snapshots/{id}/{timestamp}') - Map getSnapshotVersion(@Path('id') String id, @Path('timestamp') String timestamp) + Call getSnapshotVersion(@Path('id') String id, @Path('timestamp') String timestamp) @POST('/v2/tags') - EntityTags saveEntityTags(@Body EntityTags entityTags) + Call saveEntityTags(@Body EntityTags entityTags) @POST('/v2/tags/batchUpdate') - Collection batchUpdate(@Body Collection entityTags) + Call> batchUpdate(@Body Collection entityTags) @GET('/v2/tags/{id}') - EntityTags getEntityTags(@Path('id') String id) + Call getEntityTags(@Path('id') String id) @GET('/v2/tags') - List getAllEntityTagsById(@Query("ids") List entityIds) + Call> getAllEntityTagsById(@Query("ids") List entityIds) @GET('/v2/tags?prefix=') - Collection getAllEntityTags(@Query("refresh") boolean refresh) + Call> getAllEntityTags(@Query("refresh") boolean refresh) @DELETE('/v2/tags/{id}') - Response deleteEntityTags(@Path('id') String id) + Call deleteEntityTags(@Path('id') String id) // v2 MPT APIs @GET('/v2/pipelineTemplates/{pipelineTemplateId}') - Map getV2PipelineTemplate(@Path("pipelineTemplateId") String pipelineTemplateId, + Call getV2PipelineTemplate(@Path("pipelineTemplateId") String pipelineTemplateId, @Query("tag") String version, @Query("digest") String digest) @GET('/v2/pipelineTemplates') - List listV2PipelineTemplates(@Query("scopes") List scopes) + Call> listV2PipelineTemplates(@Query("scopes") List scopes) } diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/config/RetrofitConfig.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/config/RetrofitConfig.java index c4bfbb168c1..15f3d99950a 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/config/RetrofitConfig.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/config/RetrofitConfig.java @@ -16,23 +16,17 @@ package com.netflix.spinnaker.clouddriver.config; -import static retrofit.Endpoints.newFixedEndpoint; - -import com.jakewharton.retrofit.Ok3Client; +import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.spinnaker.clouddriver.core.Front50ConfigurationProperties; import com.netflix.spinnaker.clouddriver.core.services.Front50Service; import com.netflix.spinnaker.config.DefaultServiceEndpoint; -import com.netflix.spinnaker.config.okhttp3.OkHttpClientProvider; -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler; -import com.netflix.spinnaker.retrofit.Slf4jRetrofitLogger; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; +import java.util.List; +import okhttp3.Interceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import retrofit.Endpoint; -import retrofit.RequestInterceptor; -import retrofit.RestAdapter; -import retrofit.converter.JacksonConverter; @Configuration @EnableConfigurationProperties(Front50ConfigurationProperties.class) @@ -42,21 +36,12 @@ class RetrofitConfig { @ConditionalOnProperty(name = "services.front50.enabled", matchIfMissing = true) Front50Service front50Service( Front50ConfigurationProperties front50ConfigurationProperties, - RestAdapter.LogLevel retrofitLogLevel, - OkHttpClientProvider clientProvider, - RequestInterceptor spinnakerRequestInterceptor) { - Endpoint endpoint = newFixedEndpoint(front50ConfigurationProperties.getBaseUrl()); - return new RestAdapter.Builder() - .setRequestInterceptor(spinnakerRequestInterceptor) - .setEndpoint(endpoint) - .setClient( - new Ok3Client( - clientProvider.getClient(new DefaultServiceEndpoint("front50", endpoint.getUrl())))) - .setConverter(new JacksonConverter()) - .setLogLevel(retrofitLogLevel) - .setLog(new Slf4jRetrofitLogger(Front50Service.class)) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build() - .create(Front50Service.class); + Interceptor spinnakerRequestHeaderInterceptor, + ServiceClientProvider serviceClientProvider) { + return serviceClientProvider.getService( + Front50Service.class, + new DefaultServiceEndpoint("front50", front50ConfigurationProperties.getBaseUrl()), + new ObjectMapper(), + List.of(spinnakerRequestHeaderInterceptor)); } } diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/core/ProjectClustersService.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/core/ProjectClustersService.java index 746778d7639..fd5e963bd17 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/core/ProjectClustersService.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/core/ProjectClustersService.java @@ -23,6 +23,7 @@ import com.netflix.spinnaker.clouddriver.model.Cluster; import com.netflix.spinnaker.clouddriver.model.ClusterProvider; import com.netflix.spinnaker.clouddriver.model.ServerGroup; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -54,7 +55,7 @@ public Map> getProjectClusters(List projectNa for (String projectName : projectNames) { try { - Map projectMap = front50Service.getProject(projectName); + Map projectMap = Retrofit2SyncCall.execute(front50Service.getProject(projectName)); Project project; try { @@ -80,7 +81,7 @@ public Map> getProjectClusters(List projectNa } public List getProjectClusters(String projectName) { - Map projectData = front50Service.getProject(projectName); + Map projectData = Retrofit2SyncCall.execute(front50Service.getProject(projectName)); if (projectData == null) { return null; diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/orchestration/sagas/LoadFront50App.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/orchestration/sagas/LoadFront50App.java index 5f2a8881c23..92abbcd38c0 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/orchestration/sagas/LoadFront50App.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/orchestration/sagas/LoadFront50App.java @@ -31,6 +31,7 @@ import com.netflix.spinnaker.clouddriver.saga.flow.SagaAction; import com.netflix.spinnaker.clouddriver.saga.models.Saga; import com.netflix.spinnaker.kork.exceptions.SystemException; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import java.util.Collections; import java.util.List; import java.util.Map; @@ -109,7 +110,7 @@ private static SagaCommand applyFront50App(SagaCommand command, Front50App loade @Override public Result apply(@Nonnull LoadFront50AppCommand command, @Nonnull Saga saga) { try { - Map response = front50Service.getApplication(command.getAppName()); + Map response = Retrofit2SyncCall.execute(front50Service.getApplication(command.getAppName())); try { return new Result( Optional.ofNullable(response) diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/safety/TrafficGuard.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/safety/TrafficGuard.java index cd3fae2c1fd..7f2dcdfd9b2 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/safety/TrafficGuard.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/safety/TrafficGuard.java @@ -30,6 +30,7 @@ import com.netflix.spinnaker.clouddriver.model.ServerGroup; import com.netflix.spinnaker.clouddriver.names.NamerRegistry; import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException; import com.netflix.spinnaker.moniker.Moniker; import java.util.Arrays; @@ -345,7 +346,8 @@ public boolean hasDisableLock(Moniker clusterMoniker, String account, String loc } Map application; try { - application = front50Service.getApplication(clusterMoniker.getApp()); + application = + Retrofit2SyncCall.execute(front50Service.getApplication(clusterMoniker.getApp())); } catch (SpinnakerHttpException e) { // ignore an unknown (404) or unauthorized (403) application if (Arrays.asList(404, 403).contains(e.getResponseCode())) { diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ApplicationSearchProvider.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ApplicationSearchProvider.java index 26b196f883e..20f2b9e7d0f 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ApplicationSearchProvider.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ApplicationSearchProvider.java @@ -19,6 +19,7 @@ import com.netflix.spinnaker.clouddriver.core.services.Front50Service; import com.netflix.spinnaker.clouddriver.model.ClusterProvider; import com.netflix.spinnaker.fiat.shared.FiatPermissionEvaluator; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -87,7 +88,8 @@ public SearchResultSet search( Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - List> rawResults = front50Service.searchByName(query, pageSize, filters); + List> rawResults = + Retrofit2SyncCall.execute(front50Service.searchByName(query, pageSize, filters)); List> results = new ArrayList<>(); rawResults.forEach( application -> { diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ProjectSearchProvider.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ProjectSearchProvider.java index f77ce742d0d..9d18beffb13 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ProjectSearchProvider.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/search/ProjectSearchProvider.java @@ -17,6 +17,7 @@ package com.netflix.spinnaker.clouddriver.search; import com.netflix.spinnaker.clouddriver.core.services.Front50Service; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -64,7 +65,8 @@ public SearchResultSet search( Map allFilters = new HashMap<>(Map.of("name", query, "applications", query)); allFilters.putAll(filters); - List> projects = front50Service.searchForProjects(allFilters, pageSize); + List> projects = + Retrofit2SyncCall.execute(front50Service.searchForProjects(allFilters, pageSize)); projects.forEach( project -> { project.put("type", PROJECTS_TYPE); diff --git a/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/core/ProjectClustersServiceSpec.groovy b/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/core/ProjectClustersServiceSpec.groovy index 93e31c29005..5a3c0a996b1 100644 --- a/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/core/ProjectClustersServiceSpec.groovy +++ b/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/core/ProjectClustersServiceSpec.groovy @@ -21,6 +21,7 @@ import com.netflix.spinnaker.clouddriver.model.Cluster import com.netflix.spinnaker.clouddriver.model.ClusterProvider import com.netflix.spinnaker.clouddriver.model.LoadBalancer import com.netflix.spinnaker.clouddriver.model.ServerGroup +import retrofit2.mock.Calls import spock.lang.Shared import spock.lang.Specification @@ -73,7 +74,7 @@ class ProjectClustersServiceSpec extends Specification { then: result["Spinnaker"].isEmpty() - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 0 * _ } @@ -107,7 +108,7 @@ class ProjectClustersServiceSpec extends Specification { clusters[0].applications[1].clusters[0].instanceCounts.down == 1 clusters[0].applications[1].clusters[0].instanceCounts.up == 1 - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [new TestCluster( name: "orca-main", @@ -154,7 +155,7 @@ class ProjectClustersServiceSpec extends Specification { then: clusters.size() == 1 clusters[0].applications.application == ["orca", "deck"] - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [new TestCluster( name: "orca-main", @@ -201,7 +202,7 @@ class ProjectClustersServiceSpec extends Specification { then: clusters.size() == 1 clusters[0].applications.application == ["deck"] - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [new TestCluster( name: "orca-main", @@ -253,7 +254,7 @@ class ProjectClustersServiceSpec extends Specification { clusters[0].instanceCounts.up == 2 clusters[0].instanceCounts.starting == 0 - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("deck") >> [:] 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [ @@ -312,7 +313,7 @@ class ProjectClustersServiceSpec extends Specification { clusters[0].instanceCounts.total == 2 clusters[0].instanceCounts.up == 2 - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("deck") >> [:] 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [ @@ -351,7 +352,7 @@ class ProjectClustersServiceSpec extends Specification { clusters[0].instanceCounts.total == 1 clusters[0].instanceCounts.up == 1 - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("deck") >> [:] 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [ @@ -423,7 +424,7 @@ class ProjectClustersServiceSpec extends Specification { westCluster.instanceCounts.total == 3 westCluster.instanceCounts.up == 3 - 1 * front50Service.getProject(_) >> { projectConfig } + 1 * front50Service.getProject(_) >> Calls.response(projectConfig) 1 * clusterProvider.getClusterSummaries("orca") >> [ prod: [ new TestCluster( diff --git a/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/safety/TrafficGuardSpec.groovy b/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/safety/TrafficGuardSpec.groovy index 0d4eeed388b..7449f8f9e99 100644 --- a/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/safety/TrafficGuardSpec.groovy +++ b/clouddriver-core/src/test/groovy/com/netflix/spinnaker/clouddriver/safety/TrafficGuardSpec.groovy @@ -31,8 +31,12 @@ import com.netflix.spinnaker.clouddriver.model.SimpleServerGroup import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException import com.netflix.spinnaker.moniker.Moniker -import retrofit.RetrofitError -import retrofit.client.Response +import okhttp3.MediaType +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls; import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification @@ -105,7 +109,7 @@ class TrafficGuardSpec extends Specification { makeServerGroup(targetName, 1), makeServerGroup(otherName, 0, 1, [disabled: true]) ]) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) } void "should throw exception when target server group is the only one enabled in cluster"() { @@ -118,7 +122,7 @@ class TrafficGuardSpec extends Specification { then: def e = thrown(TrafficGuardException) e.message.startsWith("This cluster ('app-foo' in test/us-east-1) has traffic guards enabled.") - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 1), makeServerGroup(otherName, 0, 1, [disabled: true]) @@ -150,7 +154,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 2), makeServerGroup(otherName, 1) @@ -169,7 +173,7 @@ class TrafficGuardSpec extends Specification { then: notThrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 2), makeServerGroup(otherName, 1) @@ -197,7 +201,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * dynamicConfigService.getConfig(Double.class, TrafficGuard.MIN_CAPACITY_RATIO, 0d) >> 0.40d } @@ -216,7 +220,7 @@ class TrafficGuardSpec extends Specification { then: notThrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) } void "should still make sure that capacity does not drop to 0 for pinned server groups"() { @@ -235,7 +239,7 @@ class TrafficGuardSpec extends Specification { then: def e = thrown(TrafficGuardException) e.message.contains("would leave the cluster with no instances up") - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) } @Unroll @@ -252,7 +256,7 @@ class TrafficGuardSpec extends Specification { then: def e = thrown(TrafficGuardException) e.message.contains("would leave the cluster with 1 instance up") - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * dynamicConfigService.getConfig(Double.class, TrafficGuard.MIN_CAPACITY_RATIO, 0d) >> 0.4d where: @@ -288,7 +292,7 @@ class TrafficGuardSpec extends Specification { then: notThrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * dynamicConfigService.getConfig(Double.class, TrafficGuard.MIN_CAPACITY_RATIO, 0d) >> 0.40d } @@ -307,7 +311,7 @@ class TrafficGuardSpec extends Specification { then: notThrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) } void "should throw exception when target server group is the only one in cluster"() { @@ -319,7 +323,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 1) ]) @@ -334,7 +338,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 1), makeServerGroup(otherName, 1, 0, [region: 'us-west-1']) @@ -350,7 +354,7 @@ class TrafficGuardSpec extends Specification { then: noExceptionThrown() - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 0, 1) ]) @@ -378,7 +382,7 @@ class TrafficGuardSpec extends Specification { then: notThrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 1), makeServerGroup(otherName, 1) @@ -394,7 +398,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getCluster("app", "test", "app-foo", false) >> makeCluster([ makeServerGroup(targetName, 1), makeServerGroup(otherName, 0, 1) @@ -411,7 +415,7 @@ class TrafficGuardSpec extends Specification { then: result == expected - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) where: cluster | account | guardStack | guardDetail | guardAccount | guardLocation || expected @@ -433,7 +437,7 @@ class TrafficGuardSpec extends Specification { then: result == false - 1 * front50Service.getApplication("app") >> null + 1 * front50Service.getApplication("app") >> Calls.response(null) } void "hasDisableLock returns false on applications with no guards configured"() { @@ -444,7 +448,7 @@ class TrafficGuardSpec extends Specification { !application.containsKey("trafficGuards") result == false 1 * front50Service.getApplication("app") >> { - throw new SpinnakerHttpException(new RetrofitError(null, null, new Response("http://stash.com", 404, "test reason", [], null), null, null, null, null)) + throw makeSpinnakerHttpException(404) } } @@ -467,7 +471,7 @@ class TrafficGuardSpec extends Specification { then: result == false - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) } @Ignore("verifyInstanceTermination has not been ported yet") @@ -482,7 +486,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getSearchResults("i-1", "instances", "aws") >> [[results: [[account: "test", region: location.value, serverGroup: targetName]]]] 1 * clusterProvider.getTargetServerGroup("test", targetName, location.value, "aws") >> (targetServerGroup) @@ -501,7 +505,7 @@ class TrafficGuardSpec extends Specification { then: thrown(TrafficGuardException) - 1 * front50Service.get("app") >> application + 1 * front50Service.get("app") >> Calls.response(application) 1 * clusterProvider.getSearchResults("i-1", "instances", "aws") >> [[results: [[account: "test", region: location.value, serverGroup: targetName]]]] 1 * clusterProvider.getTargetServerGroup("test", targetName, location.value, "aws") >> (targetServerGroup) @@ -524,7 +528,7 @@ class TrafficGuardSpec extends Specification { then: notThrown(TrafficGuardException) - 1 * front50Service.getApplication("app") >> application + 1 * front50Service.getApplication("app") >> Calls.response(application) 1 * clusterProvider.getSearchResults("i-1", "instances", "aws") >> [[results: [[account: "test", region: location.value, serverGroup: targetName]]]] 1 * clusterProvider.getTargetServerGroup("test", targetName, location.value, "aws") >> (targetServerGroup) @@ -595,6 +599,23 @@ class TrafficGuardSpec extends Specification { 0 * _ } + static SpinnakerHttpException makeSpinnakerHttpException(int status) { + String url = "https://some-url"; + Response retrofit2Response = + Response.error( + status, + ResponseBody.create( + MediaType.parse("application/json"), "{ \"message\": \"arbitrary message\" }")) + + Retrofit retrofit = + new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()) + .build() + + return new SpinnakerHttpException(retrofit2Response, retrofit) + } + private void addGuard(Map guard) { if (!guard.containsKey("enabled")) { guard.enabled = true diff --git a/clouddriver-docker/clouddriver-docker.gradle b/clouddriver-docker/clouddriver-docker.gradle index e6fcfe364a2..fda851a87aa 100644 --- a/clouddriver-docker/clouddriver-docker.gradle +++ b/clouddriver-docker/clouddriver-docker.gradle @@ -9,10 +9,8 @@ dependencies { implementation "org.springframework.cloud:spring-cloud-context" implementation "org.apache.groovy:groovy" implementation "com.google.guava:guava" - implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" implementation "com.netflix.spectator:spectator-api" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" + implementation "com.squareup.retrofit2:converter-jackson" implementation "org.apache.commons:commons-compress:1.21" implementation "commons-io:commons-io" implementation "io.spinnaker.fiat:fiat-api:$fiatVersion" @@ -20,6 +18,7 @@ dependencies { implementation "io.spinnaker.kork:kork-credentials" implementation "io.spinnaker.kork:kork-retrofit" implementation "io.spinnaker.kork:kork-exceptions" + implementation "io.spinnaker.kork:kork-web" testImplementation "com.squareup.retrofit2:retrofit-mock" testImplementation "cglib:cglib-nodep" @@ -35,5 +34,8 @@ dependencies { testImplementation "org.springframework:spring-test" testImplementation "org.springframework.boot:spring-boot-starter-test" testImplementation "org.springframework.security:spring-security-test" + testImplementation "com.github.tomakehurst:wiremock-jre8" testImplementation "io.spinnaker.kork:kork-core" + implementation "io.spinnaker.kork:kork-retrofit2" + implementation "io.spinnaker.kork:kork-web" } diff --git a/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenService.groovy b/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenService.groovy index a2d685bd927..ec4595f3267 100644 --- a/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenService.groovy +++ b/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenService.groovy @@ -18,17 +18,18 @@ package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.DockerUserAgent import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryAuthenticationException -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler +import com.netflix.spinnaker.config.DefaultServiceEndpoint +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import groovy.util.logging.Slf4j import org.apache.commons.io.IOUtils -import retrofit.RestAdapter -import retrofit.converter.JacksonConverter -import retrofit.http.GET -import retrofit.http.Headers -import retrofit.http.Path -import retrofit.http.Query - -import java.nio.charset.Charset +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.Path +import retrofit2.http.Query + import java.nio.charset.StandardCharsets @Slf4j @@ -40,23 +41,25 @@ class DockerBearerTokenService { String passwordCommand File passwordFile String authWarning + ServiceClientProvider serviceClientProvider final static String userAgent = DockerUserAgent.getUserAgent() - DockerBearerTokenService() { + DockerBearerTokenService(ServiceClientProvider serviceClientProvider) { realmToService = new HashMap() cachedTokens = new HashMap() + this.serviceClientProvider = serviceClientProvider } - DockerBearerTokenService(String username, String password, String passwordCommand) { - this() + DockerBearerTokenService(String username, String password, String passwordCommand, ServiceClientProvider serviceClientProvider) { + this(serviceClientProvider) this.username = username this.password = password this.passwordCommand = passwordCommand } - DockerBearerTokenService(String username, File passwordFile) { - this() + DockerBearerTokenService(String username, File passwordFile, ServiceClientProvider serviceClientProvider) { + this(serviceClientProvider) this.username = username this.passwordFile = passwordFile } @@ -190,13 +193,7 @@ class DockerBearerTokenService { def tokenService = realmToService.get(realm) if (tokenService == null) { - def builder = new RestAdapter.Builder() - .setEndpoint(realm) - .setConverter(new JacksonConverter()) - .setLogLevel(RestAdapter.LogLevel.NONE) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build() - tokenService = builder.create(TokenService.class) + tokenService = serviceClientProvider.getService(TokenService, new DefaultServiceEndpoint("tokenservice", realm)) realmToService[realm] = tokenService } @@ -219,9 +216,9 @@ class DockerBearerTokenService { def token try { if (basicAuthHeader) { - token = tokenService.getToken(authenticateDetails.path, authenticateDetails.service, authenticateDetails.scope, basicAuthHeader, userAgent) + token = Retrofit2SyncCall.execute(tokenService.getToken(authenticateDetails.path, authenticateDetails.service, authenticateDetails.scope, basicAuthHeader, userAgent)) } else { - token = tokenService.getToken(authenticateDetails.path, authenticateDetails.service, authenticateDetails.scope, userAgent) + token = Retrofit2SyncCall.execute(tokenService.getToken(authenticateDetails.path, authenticateDetails.service, authenticateDetails.scope, userAgent)) } } catch (Exception e) { if (authWarning) { @@ -244,17 +241,17 @@ class DockerBearerTokenService { @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - DockerBearerToken getToken(@Path(value="path", encode=false) String path, - @Query(value="service") String service, @Query(value="scope") String scope, - @retrofit.http.Header("User-Agent") String agent) + Call getToken(@Path(value="path", encoded=true) String path, + @Query(value="service") String service, @Query(value="scope") String scope, + @Header("User-Agent") String agent) @GET("/{path}") @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - DockerBearerToken getToken(@Path(value="path", encode=false) String path, @Query(value="service") String service, - @Query(value="scope") String scope, @retrofit.http.Header("Authorization") String basic, - @retrofit.http.Header("User-Agent") String agent) + Call getToken(@Path(value="path", encoded=true) String path, @Query(value="service") String service, + @Query(value="scope") String scope, @Header("Authorization") String basic, + @Header("User-Agent") String agent) } private class AuthenticateDetails { diff --git a/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClient.groovy b/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClient.groovy index d8200dfcfab..b6bcf771a68 100644 --- a/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClient.groovy +++ b/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClient.groovy @@ -16,28 +16,31 @@ package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client +import com.fasterxml.jackson.databind.ObjectMapper import com.google.gson.Gson -import com.google.gson.GsonBuilder import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.DockerUserAgent import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerToken import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerTokenService import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryAuthenticationException import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryOperationException +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.kork.retrofit.ErrorHandlingExecutorCallAdapterFactory +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException import groovy.util.logging.Slf4j +import okhttp3.ResponseBody import org.slf4j.Logger import org.slf4j.LoggerFactory -import retrofit.RestAdapter -import retrofit.client.Response -import retrofit.converter.GsonConverter -import retrofit.converter.JacksonConverter -import retrofit.http.GET -import retrofit.http.Header -import retrofit.http.Headers -import retrofit.http.Path -import retrofit.http.Query +import retrofit2.Response +import retrofit2.converter.jackson.JacksonConverterFactory; +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.Path +import retrofit2.http.Query import java.time.Instant @@ -58,6 +61,7 @@ class DockerRegistryClient { String repositoriesRegex boolean insecureRegistry DockerOkClientProvider okClientProvider + ServiceClientProvider serviceClientProvider Builder address(String address) { this.address = address @@ -125,17 +129,22 @@ class DockerRegistryClient { return this } + Builder serviceClientProvider(ServiceClientProvider serviceClientProvider) { + this.serviceClientProvider = serviceClientProvider + return this + } + DockerRegistryClient build() { if (password && passwordFile || password && passwordCommand || passwordFile && passwordCommand) { throw new IllegalArgumentException('Error, at most one of "password", "passwordFile", "passwordCommand" or "dockerconfigFile" can be specified') } if (password || passwordCommand) { - return new DockerRegistryClient(address, email, username, password, passwordCommand, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider) + return new DockerRegistryClient(address, email, username, password, passwordCommand, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider, serviceClientProvider) } else if (passwordFile) { - return new DockerRegistryClient(address, email, username, passwordFile, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider) + return new DockerRegistryClient(address, email, username, passwordFile, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider, serviceClientProvider) } else { - return new DockerRegistryClient(address, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider) + return new DockerRegistryClient(address, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider, serviceClientProvider) } } @@ -148,7 +157,6 @@ class DockerRegistryClient { String address String email DockerRegistryService registryService - GsonConverter converter String catalogFile String repositoriesRegex @@ -165,20 +173,18 @@ class DockerRegistryClient { String catalogFile, String repositoriesRegex, boolean insecureRegistry, - DockerOkClientProvider okClientProvider) { + DockerOkClientProvider okClientProvider, + ServiceClientProvider serviceClientProvider) { this.paginateSize = paginateSize - this.tokenService = new DockerBearerTokenService() - - this.registryService = new RestAdapter.Builder() - .setEndpoint(address) - .setClient(okClientProvider.provide(address, clientTimeoutMillis, insecureRegistry)) - .setConverter(new JacksonConverter()) - .setLogLevel(RestAdapter.LogLevel.NONE) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) + this.tokenService = new DockerBearerTokenService(serviceClientProvider) + this.registryService = new Retrofit.Builder() + .baseUrl(address) + .client(okClientProvider.provide(address, clientTimeoutMillis, insecureRegistry)) + .addCallAdapterFactory(ErrorHandlingExecutorCallAdapterFactory.getInstance()) + .addConverterFactory(JacksonConverterFactory.create()) .build() - .create(DockerRegistryService) - this.converter = new GsonConverter(new GsonBuilder().create()) + .create(DockerRegistryService); this.address = address this.catalogFile = catalogFile this.repositoriesRegex = repositoriesRegex @@ -194,9 +200,10 @@ class DockerRegistryClient { String catalogFile, String repositoriesRegex, boolean insecureRegistry, - DockerOkClientProvider okClientProvider) { - this(address, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider) - this.tokenService = new DockerBearerTokenService(username, password, passwordCommand) + DockerOkClientProvider okClientProvider, + ServiceClientProvider serviceClientProvider) { + this(address, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider, serviceClientProvider) + this.tokenService = new DockerBearerTokenService(username, password, passwordCommand, serviceClientProvider) this.email = email } @@ -207,7 +214,6 @@ class DockerRegistryClient { DockerRegistryService dockerRegistryService, DockerBearerTokenService dockerBearerTokenService) { this.paginateSize = paginateSize - this.converter = new GsonConverter(new GsonBuilder().create()) this.address = address this.catalogFile = catalogFile this.repositoriesRegex = repositoriesRegex @@ -224,9 +230,10 @@ class DockerRegistryClient { String catalogFile, String repositoriesRegex, boolean insecureRegistry, - DockerOkClientProvider okClientProvider) { - this(address, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider) - this.tokenService = new DockerBearerTokenService(username, passwordFile) + DockerOkClientProvider okClientProvider, + ServiceClientProvider serviceClientProvider) { + this(address, clientTimeoutMillis, paginateSize, catalogFile, repositoriesRegex, insecureRegistry, okClientProvider, serviceClientProvider) + this.tokenService = new DockerBearerTokenService(username, passwordFile, serviceClientProvider) this.email = email } @@ -235,45 +242,45 @@ class DockerRegistryClient { @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - Response getTags(@Path(value="repository", encode=false) String repository, @Header("Authorization") String token, @Header("User-Agent") String agent) + Call getTags(@Path(value="repository", encoded=true) String repository, @Header("Authorization") String token, @Header("User-Agent") String agent) @GET("/v2/{name}/manifests/{reference}") @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - Response getManifest(@Path(value="name", encode=false) String name, @Path(value="reference", encode=false) String reference, @Header("Authorization") String token, @Header("User-Agent") String agent) + Call getManifest(@Path(value="name", encoded=true) String name, @Path(value="reference", encoded=true) String reference, @Header("Authorization") String token, @Header("User-Agent") String agent) @GET("/v2/{name}/manifests/{reference}") @Headers([ "Docker-Distribution-API-Version: registry/2.0", "Accept: application/vnd.docker.distribution.manifest.v2+json" ]) - Response getSchemaV2Manifest(@Path(value="name", encode=false) String name, @Path(value="reference", encode=false) String reference, @Header("Authorization") String token, @Header("User-Agent") String agent) + Call getSchemaV2Manifest(@Path(value="name", encoded=true) String name, @Path(value="reference", encoded=true) String reference, @Header("Authorization") String token, @Header("User-Agent") String agent) @GET("/v2/_catalog") @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - Response getCatalog(@Query(value="n") int paginateSize, @Header("Authorization") String token, @Header("User-Agent") String agent) + Call getCatalog(@Query(value="n") int paginateSize, @Header("Authorization") String token, @Header("User-Agent") String agent) @GET("/{path}") @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - Response get(@Path(value="path", encode=false) String path, @Header("Authorization") String token, @Header("User-Agent") String agent) + Call get(@Path(value="path", encoded=true) String path, @Header("Authorization") String token, @Header("User-Agent") String agent) @GET("/v2/") @Headers([ "User-Agent: Spinnaker-Clouddriver", "Docker-Distribution-API-Version: registry/2.0" ]) - Response checkVersion(@Header("Authorization") String token, @Header("User-Agent") String agent) + Call checkVersion(@Header("Authorization") String token, @Header("User-Agent") String agent) @GET("/v2/{repository}/blobs/{digest}") @Headers([ "Docker-Distribution-API-Version: registry/2.0" ]) - Response getDigestContent(@Path(value="repository", encode=false) String repository, @Path(value="digest", encode=false) String digest, @Header("Authorization") String token, @Header("User-Agent") String agent) + Call getDigestContent(@Path(value="repository", encoded=true) String repository, @Path(value="digest", encoded=true) String digest, @Header("Authorization") String token, @Header("User-Agent") String agent) } public String getDigest(String name, String tag) { @@ -287,17 +294,31 @@ class DockerRegistryClient { public String getConfigDigest(String name, String tag) { def response = getSchemaV2Manifest(name, tag) - def manifestMap = converter.fromBody(response.body, Map) as Map + def manifestMap = convertResponseBody(response.body(), Map) return manifestMap?.config?.digest } public Map getDigestContent(String name, String digest) { def response = request({ - registryService.getDigestContent(name, digest, tokenService.basicAuthHeader, userAgent) + Retrofit2SyncCall.executeCall(registryService.getDigestContent(name, digest, tokenService.basicAuthHeader, userAgent)) }, { token -> - registryService.getDigestContent(name, digest, token, userAgent) + Retrofit2SyncCall.executeCall(registryService.getDigestContent(name, digest, token, userAgent)) }, name) - return converter.fromBody(response.body, Map) + + return convertResponseBody(response.body(), Map) + } + + static def convertResponseBody(ResponseBody responseBody, Class aClass) { + if (responseBody == null) { + throw new DockerRegistryOperationException("ResponseBody cannot be null") + } + try { + def objectMapper = new ObjectMapper() + def jsonString = responseBody.string() + return objectMapper.readValue(jsonString, aClass) + } catch (Exception e) { + throw new DockerRegistryOperationException("Failed to parse ResponseBody : ${e.message}", e) + } } private Map tagDateCache = [:] @@ -307,7 +328,7 @@ class DockerRegistryClient { if(tagDateCache.containsKey(key) && tag !='latest'){ return tagDateCache[key] } - Map manifest = converter.fromBody(getManifest(name, tag).body, Map) + Map manifest = convertResponseBody(getManifest(name, tag).body(), Map) Instant dateCreated = Instant.parse(new Gson().fromJson(manifest.history[0].v1Compatibility, Map).created) tagDateCache[key] = dateCreated dateCreated @@ -315,26 +336,24 @@ class DockerRegistryClient { private getManifest(String name, String tag) { request({ - registryService.getManifest(name, tag, tokenService.basicAuthHeader, userAgent) + Retrofit2SyncCall.executeCall(registryService.getManifest(name, tag, tokenService.basicAuthHeader, userAgent)) }, { token -> - registryService.getManifest(name, tag, token, userAgent) + Retrofit2SyncCall.executeCall(registryService.getManifest(name, tag, token, userAgent)) }, name) } private getSchemaV2Manifest(String name, String tag) { request({ - registryService.getSchemaV2Manifest(name, tag, tokenService.basicAuthHeader, userAgent) + Retrofit2SyncCall.executeCall(registryService.getSchemaV2Manifest(name, tag, tokenService.basicAuthHeader, userAgent)) }, { token -> - registryService.getSchemaV2Manifest(name, tag, token, userAgent) + Retrofit2SyncCall.executeCall(registryService.getSchemaV2Manifest(name, tag, token, userAgent)) }, name) } - private static String parseLink(retrofit.client.Header header) { - if (!header.name.equalsIgnoreCase("link")) { - return null - } - def links = header.value.split(";").collect { it.trim() } + private static String parseLink(String headerValue) { + + def links = headerValue.split(";").collect { it.trim() } if (!(links.findAll { String tok -> tok.replace(" ", "").equalsIgnoreCase("rel=\"next\"") @@ -360,23 +379,27 @@ class DockerRegistryClient { return link.startsWith('/') ? link.replaceFirst('/', '') : link } - private static String findNextLink(List headers) { + private static String findNextLink(okhttp3.Headers headers) { if (!headers) { return null } - def paths = headers.collect { header -> - parseLink(header) - }.findAll { it } + def caseInsensitiveHeaders = [:].withDefault { [] } + headers.names().each { name -> + caseInsensitiveHeaders[name.toLowerCase()] += headers.values(name) + } + + def headerValues = caseInsensitiveHeaders["link"] + headers.values("link") // We are at the end of the pagination. - if (!paths || paths.size() == 0) { + if (!headerValues || headerValues.size() == 0) { return null - } else if (paths.size() > 1) { - throw new DockerRegistryOperationException("Ambiguous number of Link headers provided, the following paths were identified: $paths") + } else if (headerValues.size() > 1) { + throw new DockerRegistryOperationException("Ambiguous number of Link headers provided, the following paths were identified: $headerValues") } - return paths[0] + return parseLink(headerValues[0] as String) } /* @@ -397,19 +420,19 @@ class DockerRegistryClient { def response try { response = request({ - path ? registryService.get(path, tokenService.basicAuthHeader, userAgent) : - registryService.getCatalog(paginateSize, tokenService.basicAuthHeader, userAgent) + path ? Retrofit2SyncCall.executeCall(registryService.get(path, tokenService.basicAuthHeader, userAgent)) : + Retrofit2SyncCall.executeCall(registryService.getCatalog(paginateSize, tokenService.basicAuthHeader, userAgent)) }, { token -> - path ? registryService.get(path, token, userAgent) : - registryService.getCatalog(paginateSize, token, userAgent) + path ? Retrofit2SyncCall.executeCall(registryService.get(path, token, userAgent)) : + Retrofit2SyncCall.executeCall(registryService.getCatalog(paginateSize, token, userAgent)) }, "_catalog") } catch (Exception e) { log.warn("Error encountered during catalog of $path", e) return new DockerRegistryCatalog(repositories: []) } - def nextPath = findNextLink(response?.headers) - def catalog = (DockerRegistryCatalog) converter.fromBody(response.body, DockerRegistryCatalog) + def nextPath = findNextLink(response?.headers()) + def catalog = convertResponseBody(response.body(), DockerRegistryCatalog) if(repositoriesRegex) { catalog.repositories = catalog.repositories.findAll { it ==~ repositoriesRegex } @@ -424,15 +447,15 @@ class DockerRegistryClient { public DockerRegistryTags getTags(String repository, String path = null) { def response = request({ - path ? registryService.get(path, tokenService.basicAuthHeader, userAgent) : - registryService.getTags(repository, tokenService.basicAuthHeader, userAgent) + path ? Retrofit2SyncCall.executeCall(registryService.get(path, tokenService.basicAuthHeader, userAgent)) : + Retrofit2SyncCall.executeCall(registryService.getTags(repository, tokenService.basicAuthHeader, userAgent)) }, { token -> - path ? registryService.get(path, token, userAgent) : - registryService.getTags(repository, token, userAgent) + path ? Retrofit2SyncCall.executeCall(registryService.get(path, token, userAgent)) : + Retrofit2SyncCall.executeCall(registryService.getTags(repository, token, userAgent)) }, repository) - def nextPath = findNextLink(response?.headers) - def tags = (DockerRegistryTags) converter.fromBody(response.body, DockerRegistryTags) + def nextPath = findNextLink(response?.headers()) + def tags = convertResponseBody(response.body(), DockerRegistryTags) if (nextPath) { def nextTags = getTags(repository, nextPath) @@ -455,8 +478,8 @@ class DockerRegistryClient { if (!tokenService.basicAuthHeader && error instanceof SpinnakerHttpException && ((SpinnakerHttpException)error).getResponseCode() == 401) { return } - Response response = doCheckV2Availability(tokenService.basicAuthHeader) - if (!response){ + def response = doCheckV2Availability(tokenService.basicAuthHeader) + if (!response.body()){ LOG.error "checkV2Availability", error throw error } @@ -465,11 +488,11 @@ class DockerRegistryClient { null } - private Response doCheckV2Availability(String basicAuthHeader = null) { + private Response doCheckV2Availability(String basicAuthHeader = null) { request({ - registryService.checkVersion(basicAuthHeader, userAgent) + Retrofit2SyncCall.executeCall(registryService.checkVersion(basicAuthHeader, userAgent)) }, { token -> - registryService.checkVersion(token, userAgent) + Retrofit2SyncCall.executeCall(registryService.checkVersion(token, userAgent)) }, "v2 version check") } @@ -477,7 +500,7 @@ class DockerRegistryClient { * Implements token request flow described here https://docs.docker.com/registry/spec/auth/token/ * The tokenService also caches tokens for us, so it will attempt to use an old token before retrying. */ - public Response request(Closure withoutToken, Closure withToken, String target) { + public Response request(Closure> withoutToken, Closure> withToken, String target) { try { DockerBearerToken dockerToken = tokenService.getToken(target) String token @@ -485,7 +508,7 @@ class DockerRegistryClient { token = "Bearer ${(dockerToken.bearer_token ?: dockerToken.token) ?: dockerToken.access_token}" } - Response response + Response response try { if (token) { response = withToken(token) diff --git a/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentials.groovy b/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentials.groovy index 4bd49c68766..058ac5b11f6 100644 --- a/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentials.groovy +++ b/clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentials.groovy @@ -23,6 +23,7 @@ import com.netflix.spinnaker.clouddriver.docker.registry.exception.DockerRegistr import com.netflix.spinnaker.clouddriver.security.AbstractAccountCredentials import com.netflix.spinnaker.fiat.model.Authorization import com.netflix.spinnaker.fiat.model.resources.Permissions +import com.netflix.spinnaker.kork.client.ServiceClientProvider import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException import groovy.transform.CompileStatic import groovy.util.logging.Slf4j @@ -57,6 +58,7 @@ class DockerRegistryNamedAccountCredentials extends AbstractAccountCredentials requiredGroupMembership, Permissions permissions, - DockerOkClientProvider dockerOkClientProvider) { + DockerOkClientProvider dockerOkClientProvider, + ServiceClientProvider serviceClientProvider) { if (!accountName) { throw new IllegalArgumentException("Docker Registry account must be provided with a name.") } @@ -316,6 +327,7 @@ class DockerRegistryNamedAccountCredentials extends AbstractAccountCredentials - dockerRegistryCredentialsRepository) { + dockerRegistryCredentialsRepository, + ServiceClientProvider serviceClientProvider) { if (dockerRegistryCredentialsSource == null) { dockerRegistryCredentialsSource = accountProperties::getAccounts; @@ -110,6 +112,7 @@ public DockerRegistryHealthIndicator dockerRegistryHealthIndicator( .skip(a.getSkip()) .permissions(a.getPermissions().build()) .dockerOkClientProvider(dockerOkClientProvider) + .serviceClientProvider(serviceClientProvider) .build(), dockerRegistryCredentialsRepository); } diff --git a/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenServiceSpec.groovy b/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenServiceSpec.groovy index c1f00e567b4..723e78accd0 100644 --- a/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenServiceSpec.groovy +++ b/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenServiceSpec.groovy @@ -17,11 +17,24 @@ package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth import com.fasterxml.jackson.databind.ObjectMapper -import spock.lang.Ignore +import com.netflix.spinnaker.config.DefaultServiceClientProvider +import com.netflix.spinnaker.config.okhttp3.DefaultOkHttpClientBuilderProvider +import com.netflix.spinnaker.config.okhttp3.OkHttpClientProvider +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.kork.retrofit.Retrofit2ServiceFactoryAutoConfiguration +import com.netflix.spinnaker.kork.retrofit.Retrofit2ServiceFactory +import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties +import okhttp3.OkHttpClient +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import spock.lang.Shared import spock.lang.Specification -import java.util.Base64 + +@SpringBootTest( + classes = [OkHttpClientConfigurationProperties, Retrofit2ServiceFactory, ServiceClientProvider, OkHttpClientProvider, + OkHttpClient, DefaultServiceClientProvider, DefaultOkHttpClientBuilderProvider, Retrofit2ServiceFactoryAutoConfiguration, ObjectMapper], + webEnvironment = SpringBootTest.WebEnvironment.NONE) class DockerBearerTokenServiceSpec extends Specification { private static final REALM1 = "https://auth.docker.io" private static final PATH1 = "token" @@ -30,11 +43,15 @@ class DockerBearerTokenServiceSpec extends Specification { private static final SCOPE2 = "repository:library/ubuntu:push" private static final REPOSITORY1 = "library/ubuntu" + @Autowired + DefaultServiceClientProvider serviceClientProvider + @Shared DockerBearerTokenService tokenService - def setupSpec() { - tokenService = new DockerBearerTokenService() + + def setup() { + tokenService = new DockerBearerTokenService(serviceClientProvider) } void "should parse Www-Authenticate header with full privileges and path."() { @@ -142,7 +159,7 @@ class DockerBearerTokenServiceSpec extends Specification { def username = "username" def passwordContents = new BufferedReader(new FileReader(passwordFile)).getText() when: - def fileTokenService = new DockerBearerTokenService(username, passwordFile) + def fileTokenService = new DockerBearerTokenService(username, passwordFile, serviceClientProvider) then: new String(Base64.decoder.decode(fileTokenService.getBasicAuth().bytes)) == "$username:$passwordContents" @@ -155,7 +172,7 @@ class DockerBearerTokenServiceSpec extends Specification { def password = "" def actualPassword = "hunter2" when: - def passwordCommandService = new DockerBearerTokenService(username, password, passwordCommand) + def passwordCommandService = new DockerBearerTokenService(username, password, passwordCommand, serviceClientProvider) then: new String(Base64.decoder.decode(passwordCommandService.getBasicAuth().bytes)) == "$username:$actualPassword" } diff --git a/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClientSpec.groovy b/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClientSpec.groovy index 31b5e31fee7..e4c0735cda4 100644 --- a/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClientSpec.groovy +++ b/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClientSpec.groovy @@ -19,16 +19,18 @@ package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerToken import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerTokenService import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException +import okhttp3.MediaType +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.ResponseBody import org.springframework.http.HttpStatus -import retrofit.RetrofitError -import retrofit.client.Header -import retrofit.client.Response -import retrofit.mime.TypedByteArray -import retrofit.mime.TypedInput +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls; import spock.lang.Shared import spock.lang.Specification -import java.util.concurrent.TimeUnit /* * These tests all communicate with dockerhub (index.docker.io), and will either fail @@ -44,19 +46,16 @@ class DockerRegistryClientSpec extends Specification { def stubbedRegistryService = Stub(DockerRegistryClient.DockerRegistryService){ String tagsJson = "{\"name\":\"library/ubuntu\",\"tags\":[\"latest\",\"xenial\",\"rolling\"]}" - TypedInput tagsTypedInput = new TypedByteArray("application/json", tagsJson.getBytes()) - Response tagsResponse = new Response("/v2/{repository}/tags/list",200, "nothing", Collections.EMPTY_LIST, tagsTypedInput) - getTags(_,_,_) >> tagsResponse + Response tagsResponse = Response.success(200, ResponseBody.create(MediaType.parse("application/json"), tagsJson)) + getTags(_,_,_) >> Calls.response(tagsResponse) String checkJson = "{}" - TypedInput checkTypedInput = new TypedByteArray("application/json", checkJson.getBytes()) - Response checkResponse = new Response("/v2/",200, "nothing", Collections.EMPTY_LIST, checkTypedInput) - checkVersion(_,_) >> checkResponse + Response checkResponse = Response.success(200, ResponseBody.create(MediaType.parse("application/json"), checkJson)) + checkVersion(_,_) >> Calls.response(checkResponse) String json = "{\"repositories\":[\"armory-io/armorycommons\",\"armory/aquascan\",\"other/keel\"]}" - TypedInput catalogTypedInput = new TypedByteArray("application/json", json.getBytes()) - Response catalogResponse = new Response("/v2/_catalog/",200, "nothing", Collections.EMPTY_LIST, catalogTypedInput) - getCatalog(_,_,_) >> catalogResponse + Response catalogResponse = Response.success(200, ResponseBody.create(MediaType.parse("application/json"), json)) + getCatalog(_,_,_) >> Calls.response(catalogResponse) String schemaJson = '''{ "schemaVersion": 2, @@ -74,9 +73,8 @@ class DockerRegistryClientSpec extends Specification { } ] }''' - TypedInput schemaV2Input = new TypedByteArray("application/json", schemaJson.getBytes()) - Response schemaV2Response = new Response("/v2/{name}/manifests/{reference}",200, "nothing", Collections.EMPTY_LIST, schemaV2Input) - getSchemaV2Manifest(_,_,_,_) >> schemaV2Response + Response schemaV2Response = Response.success(200, ResponseBody.create(MediaType.parse("application/json"), schemaJson)) + getSchemaV2Manifest(_,_,_,_) >> Calls.response(schemaV2Response) String configDigestContentJson = '''{ "architecture": "amd64", @@ -106,9 +104,8 @@ class DockerRegistryClientSpec extends Specification { "os": "linux", "rootfs": {} }''' - TypedInput configDigestContentInput = new TypedByteArray("application/json", configDigestContentJson.getBytes()) - Response contentDigestResponse = new Response("/v2/{repository}/blobs/{digest}",200, "nothing", Collections.EMPTY_LIST, configDigestContentInput) - getDigestContent(_,_,_,_) >> contentDigestResponse + Response contentDigestResponse = Response.success(200, ResponseBody.create(MediaType.parse("application/json"), configDigestContentJson)) + getDigestContent(_,_,_,_) >> Calls.response(contentDigestResponse) } def setupSpec() { @@ -155,7 +152,7 @@ class DockerRegistryClientSpec extends Specification { then: userAgent.startsWith("Spinnaker") - 1 * mockService.checkVersion(_,_) + 1 * mockService.checkVersion(_,_) >> Calls.response(null) } void "DockerRegistryClient should filter repositories by regular expression."() { @@ -191,17 +188,41 @@ class DockerRegistryClientSpec extends Specification { void "DockerRegistryClient should honor the www-authenticate header"() { setup: def authenticateDetails = "realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\",scope=\"repository:${REPOSITORY1}:pull\"" - def unauthorizedRetroFitError = RetrofitError.httpError("url", - new Response("url", HttpStatus.UNAUTHORIZED.value(), "authentication required", [new Header("www-authenticate", "Bearer ${authenticateDetails}")], null), - null, null) DockerBearerToken token = new DockerBearerToken() token.bearer_token = "bearer-token" when: client = new DockerRegistryClient("https://index.docker.io", 100, "", "", stubbedRegistryService, dockerBearerTokenService) - client.request(() -> {throw new SpinnakerHttpException(unauthorizedRetroFitError)}, (_) -> null, REPOSITORY1) + client.request(() -> {throw makeSpinnakerHttpException(authenticateDetails)}, (_) -> null, REPOSITORY1) then: 1 * dockerBearerTokenService.getToken(REPOSITORY1, authenticateDetails) >> token } + public static SpinnakerHttpException makeSpinnakerHttpException(String authenticateDetails) { + String url = "https://some-url"; + + okhttp3.Headers headers = new okhttp3.Headers.Builder() + .add("www-authenticate", "Bearer ${authenticateDetails}") + .build(); + + + Response retrofit2Response = + Response.error( + ResponseBody.create(MediaType.parse("application/json"), "{ \"message\": \"arbitrary message\" }"), + new okhttp3.Response.Builder() + .code(HttpStatus.UNAUTHORIZED.value()) + .message("authentication required") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url(url).build()) + .headers(headers) + .build()) + + Retrofit retrofit = + new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + return new SpinnakerHttpException(retrofit2Response, retrofit); + } } diff --git a/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/provider/agent/DockerRegistryImageCachingAgentTest.groovy b/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/provider/agent/DockerRegistryImageCachingAgentTest.groovy index 24ceb8047d4..00ab98d9e73 100644 --- a/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/provider/agent/DockerRegistryImageCachingAgentTest.groovy +++ b/clouddriver-docker/src/test/groovy/com/netflix/spinnaker/clouddriver/docker/registry/provider/agent/DockerRegistryImageCachingAgentTest.groovy @@ -21,7 +21,6 @@ import com.netflix.spinnaker.clouddriver.docker.registry.DockerRegistryCloudProv import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client.DockerRegistryClient import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client.DockerRegistryTags import com.netflix.spinnaker.clouddriver.docker.registry.security.DockerRegistryCredentials -import retrofit.RetrofitError import spock.lang.Specification import java.time.Instant @@ -202,7 +201,7 @@ class DockerRegistryImageCachingAgentTest extends Specification { ["repo-1", "tag-2"], ] client.getCreationDate("repo-1", "tag-1") >> { - throw RetrofitError.httpError("", null, null, null) + throw new IOException() } client.getCreationDate("repo-1", "tag-2") >> Instant.EPOCH diff --git a/clouddriver-docker/src/test/java/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentialsTest.java b/clouddriver-docker/src/test/java/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentialsTest.java index 7fb04a70ff7..ebcc8671cb4 100644 --- a/clouddriver-docker/src/test/java/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentialsTest.java +++ b/clouddriver-docker/src/test/java/com/netflix/spinnaker/clouddriver/docker/registry/security/DockerRegistryNamedAccountCredentialsTest.java @@ -19,32 +19,30 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.jakewharton.retrofit.Ok3Client; import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client.DockerOkClientProvider; import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client.DockerRegistryTags; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.time.format.DateTimeFormatter; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.Getter; import lombok.RequiredArgsConstructor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.ResponseBody; import org.junit.jupiter.api.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import retrofit.client.Request; -import retrofit.client.Response; -import retrofit.mime.TypedString; final class DockerRegistryNamedAccountCredentialsTest { private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -55,12 +53,12 @@ final class DockerRegistryNamedAccountCredentialsTest { @Test void getTags() throws IOException { ImmutableList tags = ImmutableList.of("latest", "other", "something"); - Ok3Client ok3Client = mockDockerOkClient(tags, ImmutableMap.of()); + OkHttpClient okHttpClient = mockDockerOkClient(tags, ImmutableMap.of()); DockerRegistryNamedAccountCredentials credentials = new DockerRegistryNamedAccountCredentials.Builder() .accountName(ACCOUNT_NAME) .address("https://gcr.io") - .dockerOkClientProvider(new MockDockerOkClientProvider(ok3Client)) + .dockerOkClientProvider(new MockDockerOkClientProvider(okHttpClient)) .build(); assertThat(credentials.getTags(REPO_NAME)).containsExactlyInAnyOrderElementsOf(tags); } @@ -77,67 +75,81 @@ void getTagsInOrder() throws IOException { "oldest", LATEST_DATE.minus(Duration.ofDays(1))); - Ok3Client ok3Client = mockDockerOkClient(tags, creationDates); + OkHttpClient okHttpClient = mockDockerOkClient(tags, creationDates); DockerRegistryNamedAccountCredentials credentials = new DockerRegistryNamedAccountCredentials.Builder() .accountName(ACCOUNT_NAME) .address("https://gcr.io") .sortTagsByDate(true) - .dockerOkClientProvider(new MockDockerOkClientProvider(ok3Client)) + .dockerOkClientProvider(new MockDockerOkClientProvider(okHttpClient)) + .serviceClientProvider(mock(ServiceClientProvider.class)) .build(); assertThat(credentials.getTags(REPO_NAME)) .containsExactly("latest", "older", "oldest", "nodate"); } /** - * Generates a mock Ok3Client that simulates responses from a docker registry with the supplied + * Generates a mock OkHttpClient that simulates responses from a docker registry with the supplied * tags and supplied creation dates for each tag. Tags that are not present in the map of creation * dates will return null as their creation date. */ - private static Ok3Client mockDockerOkClient( + private static OkHttpClient mockDockerOkClient( Iterable tags, Map creationDates) throws IOException { - Ok3Client ok3Client = mock(Ok3Client.class); - doReturn( - new Response( - "https://gcr.io/v2/myrepo/tags/list", - 200, - "", - Collections.emptyList(), - new TypedString(objectMapper.writeValueAsString(getTagsResponse(tags))))) - .when(ok3Client) - .execute(argThat(r -> r.getUrl().equals("https://gcr.io/v2/myrepo/tags/list"))); + OkHttpClient okHttpClient = mock(OkHttpClient.class); + okhttp3.Call mockTagListCall = mock(okhttp3.Call.class); + okhttp3.Response tagListResponse = + new okhttp3.Response.Builder() + .request(new Request.Builder().url("https://gcr.io/v2/myrepo/tags/list").build()) + .protocol(okhttp3.Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body( + ResponseBody.create( + MediaType.parse("application/json"), + objectMapper.writeValueAsString(getTagsResponse(tags)))) + .build(); + when(mockTagListCall.execute()).thenReturn(tagListResponse); + when(okHttpClient.newCall( + argThat(r -> r.url().toString().equals("https://gcr.io/v2/myrepo/tags/list")))) + .thenReturn(mockTagListCall); doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - Request request = (Request) args[0]; - String tag = getTag(request.getUrl()); - Instant optionalDate = creationDates.get(tag); - return new Response( - "https://gcr.io/v2/myrepo/manifests/latest", - 200, - "", - Collections.emptyList(), - new TypedString( - objectMapper.writeValueAsString( - DockerManifestResponse.withCreationDate(optionalDate)))); - } - - private String getTag(String url) { - Matcher matcher = - Pattern.compile("https://gcr.io/v2/myrepo/manifests/(.*)").matcher(url); - if (matcher.matches()) { - return matcher.group(1); - } - throw new IllegalArgumentException(); - } + invocation -> { + Request request = invocation.getArgument(0); + String tag = extractTag(request.url().toString()); + Instant optionalDate = creationDates.get(tag); + + okhttp3.Response manifestResponse = + new okhttp3.Response.Builder() + .request(request) + .protocol(okhttp3.Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body( + ResponseBody.create( + MediaType.parse("application/json"), + objectMapper.writeValueAsString( + DockerManifestResponse.withCreationDate(optionalDate)))) + .build(); + + okhttp3.Call mockManifestCall = mock(okhttp3.Call.class); + when(mockManifestCall.execute()).thenReturn(manifestResponse); + + return mockManifestCall; }) - .when(ok3Client) - .execute(argThat(r -> r.getUrl().matches("https://gcr.io/v2/myrepo/manifests/.*"))); + .when(okHttpClient) + .newCall( + argThat(r -> r.url().toString().matches("https://gcr\\.io/v2/myrepo/manifests/.*"))); + + return okHttpClient; + } - return ok3Client; + private static String extractTag(String url) { + Matcher matcher = Pattern.compile("https://gcr.io/v2/myrepo/manifests/(.*)").matcher(url); + if (matcher.matches()) { + return matcher.group(1); + } + throw new IllegalArgumentException(); } private static DockerRegistryTags getTagsResponse(Iterable tags) { @@ -177,10 +189,10 @@ static HistoryEntry withCreationDate(Instant instant) throws IOException { @RequiredArgsConstructor private static class MockDockerOkClientProvider implements DockerOkClientProvider { - private final Ok3Client mockClient; + private final OkHttpClient mockClient; @Override - public Ok3Client provide(String address, long timeoutMs, boolean insecure) { + public OkHttpClient provide(String address, long timeoutMs, boolean insecure) { return mockClient; } } diff --git a/clouddriver-ecs/clouddriver-ecs.gradle b/clouddriver-ecs/clouddriver-ecs.gradle index d3a151080f2..a763861d817 100644 --- a/clouddriver-ecs/clouddriver-ecs.gradle +++ b/clouddriver-ecs/clouddriver-ecs.gradle @@ -31,8 +31,6 @@ dependencies { implementation "io.spinnaker.kork:kork-credentials-api" implementation "io.spinnaker.kork:kork-exceptions" implementation "io.spinnaker.kork:kork-moniker" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" implementation "commons-io:commons-io" implementation "org.apache.commons:commons-lang3" implementation "org.apache.httpcomponents:httpclient" diff --git a/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle b/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle index 39450f54819..3154c56c7c5 100644 --- a/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle +++ b/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle @@ -9,7 +9,7 @@ dependencies { implementation "io.spinnaker.kork:kork-moniker" implementation "io.spinnaker.kork:kork-retrofit" implementation "io.spinnaker.kork:kork-security" - implementation "com.squareup.retrofit:retrofit" + implementation "com.squareup.retrofit2:converter-jackson" implementation "org.apache.groovy:groovy" implementation "org.elasticsearch:elasticsearch" implementation "org.springframework.boot:spring-boot-starter-web" @@ -20,6 +20,7 @@ dependencies { testImplementation "org.spockframework:spock-core" testImplementation "org.spockframework:spock-spring" testImplementation "org.springframework:spring-test" + testImplementation "com.squareup.retrofit2:retrofit-mock" testRuntimeOnly "net.bytebuddy:byte-buddy" } diff --git a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProvider.java b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProvider.java index ca0b467257b..88298a9c14e 100644 --- a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProvider.java +++ b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProvider.java @@ -28,6 +28,7 @@ import com.netflix.spinnaker.clouddriver.model.EntityTagsProvider; import com.netflix.spinnaker.config.ElasticSearchConfigProperties; import com.netflix.spinnaker.kork.core.RetrySupport; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import com.netflix.spinnaker.security.AuthenticatedRequest; import io.searchbox.client.JestClient; import io.searchbox.client.JestResult; @@ -324,7 +325,8 @@ public void reindex() { "Unable to re-create index '" + activeElasticSearchIndex + "'"); } - Collection entityTags = front50Service.getAllEntityTags(true); + Collection entityTags = + Retrofit2SyncCall.execute(front50Service.getAllEntityTags(true)); Collection filteredEntityTags = getElasticSearchEntityTagsReconciler().filter(entityTags); @@ -343,7 +345,8 @@ public void reindex() { @Override public Map delta() { - Collection allEntityTagsFront50 = front50Service.getAllEntityTags(false); + Collection allEntityTagsFront50 = + Retrofit2SyncCall.execute(front50Service.getAllEntityTags(false)); Map> entityTagsByEntityTypeFront50 = allEntityTagsFront50.stream() .collect( @@ -603,7 +606,7 @@ private List getAllMatchingEntityTags(String namespace, String tag) Set entityTagsIdentifiers = new HashSet<>(); List entityTagsForTag = - front50Service.getAllEntityTags(false).stream() + Retrofit2SyncCall.execute(front50Service.getAllEntityTags(false)).stream() .filter( e -> e.getTags().stream() diff --git a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconciler.java b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconciler.java index 87fd7031630..015b4db8f72 100644 --- a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconciler.java +++ b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconciler.java @@ -22,6 +22,7 @@ import com.netflix.spinnaker.clouddriver.core.services.Front50Service; import com.netflix.spinnaker.clouddriver.model.EntityTags; import com.netflix.spinnaker.clouddriver.model.ServerGroupProvider; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -60,7 +61,8 @@ Map reconcile( String account, String region, boolean dryRun) { - Collection allEntityTags = front50Service.getAllEntityTags(false); + Collection allEntityTags = + Retrofit2SyncCall.execute(front50Service.getAllEntityTags(false)); List allServerGroupEntityTags = filter(allEntityTags, cloudProvider, account, region); diff --git a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperation.java b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperation.java index 2f3dae64c2f..6226e8c5008 100644 --- a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperation.java +++ b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperation.java @@ -30,6 +30,7 @@ import com.netflix.spinnaker.clouddriver.security.AccountCredentials; import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider; import com.netflix.spinnaker.kork.core.RetrySupport; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import com.netflix.spinnaker.security.AuthenticatedRequest; import java.util.*; import java.util.function.Function; @@ -111,7 +112,9 @@ public BulkUpsertEntityTagsAtomicOperationResult operate(List priorOutputs) { getTask() .updateStatus(BASE_PHASE, "Performing batch update to durable tagging service"); Map durableTags = - front50Service.batchUpdate(new ArrayList<>(modifiedEntityTags)).stream() + Retrofit2SyncCall.execute( + front50Service.batchUpdate(new ArrayList<>(modifiedEntityTags))) + .stream() .collect(Collectors.toMap(EntityTags::getId, Function.identity())); getTask().updateStatus(BASE_PHASE, "Pushing tags to Elastic Search"); @@ -127,7 +130,7 @@ private Map retrieveExistingTags(List entityTags try { return retrySupport.retry( () -> - front50Service.getAllEntityTagsById(ids).stream() + Retrofit2SyncCall.execute(front50Service.getAllEntityTagsById(ids)).stream() .collect(Collectors.toMap(EntityTags::getId, Function.identity())), 10, 2000, diff --git a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperation.java b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperation.java index 2a63f3bde93..67c02fc5340 100644 --- a/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperation.java +++ b/clouddriver-elasticsearch/src/main/java/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperation.java @@ -25,6 +25,7 @@ import com.netflix.spinnaker.clouddriver.elasticsearch.model.ElasticSearchEntityTagsProvider; import com.netflix.spinnaker.clouddriver.model.EntityTags; import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException; import java.util.Collection; import java.util.List; @@ -54,7 +55,8 @@ public Void operate(List priorOutputs) { BASE_PHASE, format("Retrieving %s from Front50", entityTagsDescription.getId())); EntityTags currentTags; try { - currentTags = front50Service.getEntityTags(entityTagsDescription.getId()); + currentTags = + Retrofit2SyncCall.execute(front50Service.getEntityTags(entityTagsDescription.getId())); } catch (SpinnakerHttpException e) { if (e.getResponseCode() == HttpStatus.NOT_FOUND.value()) { getTask() @@ -108,7 +110,8 @@ public Void operate(List priorOutputs) { entityTagsDescription.getId(), entityTagsDescription.getTags())); entityTagsDescription.getTags().forEach(currentTags::removeEntityTag); - EntityTags durableEntityTags = front50Service.saveEntityTags(currentTags); + EntityTags durableEntityTags = + Retrofit2SyncCall.execute(front50Service.saveEntityTags(currentTags)); getTask().updateStatus(BASE_PHASE, format("Updated %s in Front50", durableEntityTags.getId())); currentTags.setLastModified(durableEntityTags.getLastModified()); diff --git a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProviderSpec.groovy b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProviderSpec.groovy index efb9f1e9ea9..d128565fa3a 100644 --- a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProviderSpec.groovy +++ b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsProviderSpec.groovy @@ -32,6 +32,7 @@ import io.searchbox.indices.template.PutTemplate import org.springframework.context.ApplicationContext import org.testcontainers.DockerClientFactory import org.testcontainers.elasticsearch.ElasticsearchContainer +import retrofit2.mock.Calls import spock.lang.Requires import spock.lang.Shared import spock.lang.Specification @@ -298,7 +299,7 @@ class ElasticSearchEntityTagsProviderSpec extends Specification { refreshIndices() then: - 1 * front50Service.getAllEntityTags(true) >> { return allEntityTags } + 1 * front50Service.getAllEntityTags(true) >> Calls.response(allEntityTags) 1 * entityTagsReconciler.filter(allEntityTags) >> { return [ allEntityTags[1] ] } entityTagsProvider.verifyIndex(allEntityTags[1]) @@ -346,22 +347,21 @@ class ElasticSearchEntityTagsProviderSpec extends Specification { entityTagsProvider.deleteByNamespace("my_namespace", true, false) // dry-run then: - 1 * front50Service.getAllEntityTags(false) >> { - return entityTagsProvider.getAll( + 1 * front50Service.getAllEntityTags(false) >> + Calls.response(entityTagsProvider.getAll( null, null, null, null, null, null, null, null, [:], 100 - ) - } + )) + 0 * _ when: entityTagsProvider.deleteByNamespace("my_namespace", true, true) // dry-run then: - 1 * front50Service.getAllEntityTags(false) >> { - return entityTagsProvider.getAll( + 1 * front50Service.getAllEntityTags(false) >> Calls.response( entityTagsProvider.getAll( null, null, null, null, null, null, null, null, [:], 100 - ) - } + )) + 0 * _ when: @@ -373,11 +373,10 @@ class ElasticSearchEntityTagsProviderSpec extends Specification { ) then: - 1 * front50Service.getAllEntityTags(false) >> { - return entityTagsProvider.getAll( + 1 * front50Service.getAllEntityTags(false) >> Calls.response(entityTagsProvider.getAll( null, null, null, null, null, null, null, null, [:], 100 - ) - } + )) + _ * retrySupport.retry(_, _, _, _) >> { Supplier fn, int maxRetries, long retryBackoff, boolean exponential -> fn.get() } 0 * _ @@ -389,8 +388,8 @@ class ElasticSearchEntityTagsProviderSpec extends Specification { entityTagsProvider.deleteByNamespace("my_namespace", false, true) // remove from elasticsearch and front50 then: - 1 * front50Service.getAllEntityTags(false) >> { return allEntityTags } - 1 * front50Service.batchUpdate(_) + 1 * front50Service.getAllEntityTags(false) >> Calls.response(allEntityTags) + 1 * front50Service.batchUpdate(_) >> Calls.response(null) _ * retrySupport.retry(_, _, _, _) >> { Supplier fn, int maxRetries, long retryBackoff, boolean exponential -> fn.get() } 0 * _ } diff --git a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconcilerSpec.groovy b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconcilerSpec.groovy index d029b329e18..af616ac6adc 100644 --- a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconcilerSpec.groovy +++ b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/model/ElasticSearchEntityTagsReconcilerSpec.groovy @@ -19,6 +19,7 @@ package com.netflix.spinnaker.clouddriver.elasticsearch.model import com.netflix.spinnaker.clouddriver.core.services.Front50Service import com.netflix.spinnaker.clouddriver.model.EntityTags import com.netflix.spinnaker.clouddriver.model.ServerGroupProvider +import retrofit2.mock.Calls import spock.lang.Shared import spock.lang.Specification import spock.lang.Subject @@ -37,7 +38,7 @@ class ElasticSearchEntityTagsReconcilerSpec extends Specification { ] def front50Service = Mock(Front50Service) { - _ * getAllEntityTags(_) >> { return allEntityTags } + _ * getAllEntityTags(_) >> { return Calls.response(allEntityTags) } } def amazonServerGroupProvider = Mock(ServerGroupProvider) { diff --git a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperationSpec.groovy b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperationSpec.groovy index 0a9d2dc0d2b..e5331dc9acc 100644 --- a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperationSpec.groovy +++ b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/BulkUpsertEntityTagsAtomicOperationSpec.groovy @@ -30,6 +30,7 @@ import com.netflix.spinnaker.clouddriver.model.EntityTags.EntityTagValueType import com.netflix.spinnaker.clouddriver.security.AccountCredentials import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider import com.netflix.spinnaker.kork.core.RetrySupport +import retrofit2.mock.Calls import spock.lang.Specification import spock.lang.Unroll @@ -70,9 +71,11 @@ class BulkUpsertEntityTagsAtomicOperationSpec extends Specification { then: 1000 * accountCredentialsProvider.getAll() >> { return [testCredentials] } - 20 * front50Service.getAllEntityTagsById(_) >> [] - 20 * front50Service.batchUpdate(_) >> { - description.entityTags.findResults { new EntityTags(id: it.id, lastModified: 123, lastModifiedBy: "unknown")} + 20 * front50Service.getAllEntityTagsById(_) >> { return Calls.response([])} + 20 * front50Service.batchUpdate(_) >> { args -> + Calls.response(args[0].findResults { + new EntityTags(id: it.id, lastModified: 123, lastModifiedBy: "unknown") + }) } 20 * entityTagsProvider.bulkIndex(_) 1000 * entityTagsProvider.verifyIndex(_) @@ -107,10 +110,8 @@ class BulkUpsertEntityTagsAtomicOperationSpec extends Specification { description.entityTags.size() == 2 description.entityTags[0].tags.size() == 3 4 * accountCredentialsProvider.getAll() >> { return [testCredentials] } - 1 * front50Service.getAllEntityTagsById(_) >> [] - 1 * front50Service.batchUpdate(_) >> { - description.entityTags.findResults { new EntityTags(id: it.id, lastModified: 123, lastModifiedBy: "unknown")} - } + 1 * front50Service.getAllEntityTagsById(_) >> Calls.response([]) + 1 * front50Service.batchUpdate(_) >> {args -> Calls.response(args[0].findResults { new EntityTags(id: it.id, lastModified: 123, lastModifiedBy: "unknown")})} } void 'should create new tag if none exists'() { @@ -135,10 +136,8 @@ class BulkUpsertEntityTagsAtomicOperationSpec extends Specification { tag.tagsMetadata[0].lastModifiedBy == tag.tagsMetadata[0].createdBy 1 * accountCredentialsProvider.getAll() >> { return [testCredentials] } - 1 * front50Service.batchUpdate(_) >> { - [new EntityTags(id: "aws:servergroup:orca-v001:100:us-east-1", lastModified: 123, lastModifiedBy: "unknown")] - } - 1 * front50Service.getAllEntityTagsById(_) >> [] + 1 * front50Service.batchUpdate(_) >> Calls.response([new EntityTags(id: "aws:servergroup:orca-v001:100:us-east-1", lastModified: 123, lastModifiedBy: "unknown")]) + 1 * front50Service.getAllEntityTagsById(_) >> Calls.response([]) 1 * entityTagsProvider.bulkIndex(description.entityTags) 1 * entityTagsProvider.verifyIndex(tag) } @@ -225,10 +224,8 @@ class BulkUpsertEntityTagsAtomicOperationSpec extends Specification { result.upserted.size() == 3 description.entityTags.size() == 3 4 * accountCredentialsProvider.getAll() >> { return [testCredentials] } - 1 * front50Service.getAllEntityTagsById(_) >> [] - 1 * front50Service.batchUpdate(_) >> { - description.entityTags.findResults { new EntityTags(id: it.id, lastModified: 123, lastModifiedBy: "unknown")} - } + 1 * front50Service.getAllEntityTagsById(_) >> Calls.response([]) + 1 * front50Service.batchUpdate(_) >> {args -> Calls.response(args[0].findResults { new EntityTags(id: it.id, lastModified: 123, lastModifiedBy: "unknown")}) } entityTagsProvider.index() } diff --git a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperationSpec.groovy b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperationSpec.groovy index 39c178475d1..fa3fe2b0517 100644 --- a/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperationSpec.groovy +++ b/clouddriver-elasticsearch/src/test/groovy/com/netflix/spinnaker/clouddriver/elasticsearch/ops/DeleteEntityTagsAtomicOperationSpec.groovy @@ -23,9 +23,12 @@ import com.netflix.spinnaker.clouddriver.elasticsearch.descriptions.DeleteEntity import com.netflix.spinnaker.clouddriver.elasticsearch.model.ElasticSearchEntityTagsProvider import com.netflix.spinnaker.clouddriver.model.EntityTags import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException -import org.springframework.http.HttpStatus -import retrofit.RetrofitError -import retrofit.client.Response +import okhttp3.MediaType +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls import spock.lang.Specification class DeleteEntityTagsAtomicOperationSpec extends Specification { @@ -47,17 +50,12 @@ class DeleteEntityTagsAtomicOperationSpec extends Specification { } void 'should remove entityTag from ElasticSearch if not found in Front50'() { - given: - RetrofitError notFoundRetrofitError = RetrofitError.httpError("url", - new Response("url", HttpStatus.NOT_FOUND.value(), "Application Not Found", [], null), - null, null) - SpinnakerHttpException spinnakerHttpException = new SpinnakerHttpException(notFoundRetrofitError) when: description.id = 'abc' operation.operate([]) then: - 1 * front50Service.getEntityTags('abc') >> { throw spinnakerHttpException } + 1 * front50Service.getEntityTags('abc') >> { throw makeSpinnakerHttpException(404) } 1 * entityTagsProvider.delete('abc') 0 * _ } @@ -73,9 +71,9 @@ class DeleteEntityTagsAtomicOperationSpec extends Specification { operation.operate([]) then: - 1 * front50Service.getEntityTags('abc') >> current + 1 * front50Service.getEntityTags('abc') >> Calls.response(current) 1 * entityTagsProvider.delete('abc') - 1 * front50Service.deleteEntityTags('abc') + 1 * front50Service.deleteEntityTags('abc') >> Calls.response(null) 0 * _ } @@ -89,9 +87,9 @@ class DeleteEntityTagsAtomicOperationSpec extends Specification { operation.operate([]) then: - 1 * front50Service.getEntityTags('abc') >> current + 1 * front50Service.getEntityTags('abc') >> Calls.response(current) 1 * entityTagsProvider.delete('abc') - 1 * front50Service.deleteEntityTags('abc') + 1 * front50Service.deleteEntityTags('abc') >> Calls.response(null) 0 * _ } @@ -114,14 +112,32 @@ class DeleteEntityTagsAtomicOperationSpec extends Specification { current.tags*.name == ['b'] current.tags*.value == ['something else'] current.tagsMetadata*.name == ['b'] - 1 * front50Service.getEntityTags('abc') >> current + 1 * front50Service.getEntityTags('abc') >> Calls.response(current) 1 * entityTagsProvider.index(current) 1 * entityTagsProvider.verifyIndex(current) - 1 * front50Service.saveEntityTags(current) >> current + 1 * front50Service.saveEntityTags(current) >> Calls.response(current) 0 * _ } Collection buildTags(Map tags) { return tags.collect { k, v -> new EntityTags.EntityTag(name: k, value: v) } } + + static SpinnakerHttpException makeSpinnakerHttpException(int status) { + String url = "https://some-url"; + Response retrofit2Response = + Response.error( + status, + ResponseBody.create( + MediaType.parse("application/json"), "{ \"message\": \"arbitrary message\" }")) + + Retrofit retrofit = + new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()) + .build() + + return new SpinnakerHttpException(retrofit2Response, retrofit) + } + } diff --git a/clouddriver-eureka/clouddriver-eureka.gradle b/clouddriver-eureka/clouddriver-eureka.gradle index f76c3572633..82bbb08c7c0 100644 --- a/clouddriver-eureka/clouddriver-eureka.gradle +++ b/clouddriver-eureka/clouddriver-eureka.gradle @@ -7,9 +7,6 @@ dependencies { implementation "io.spinnaker.kork:kork-web" implementation "io.spinnaker.kork:kork-retrofit" implementation "com.amazonaws:aws-java-sdk" - implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" implementation "org.apache.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-web" @@ -18,4 +15,5 @@ dependencies { testImplementation "org.spockframework:spock-core" testImplementation "org.spockframework:spock-spring" testImplementation "org.springframework:spring-test" + testImplementation "com.squareup.retrofit2:retrofit-mock" } diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/Eureka.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/Eureka.groovy index e821fccff67..f60c7389e1c 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/Eureka.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/Eureka.groovy @@ -17,28 +17,29 @@ package com.netflix.spinnaker.clouddriver.eureka.api import com.netflix.spinnaker.clouddriver.eureka.model.EurekaApplication -import retrofit.client.Response -import retrofit.http.GET -import retrofit.http.Headers -import retrofit.http.PUT -import retrofit.http.DELETE -import retrofit.http.Path -import retrofit.http.Query +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.PUT +import retrofit2.http.DELETE +import retrofit2.http.Path +import retrofit2.http.Query interface Eureka { @Headers('Accept: application/json') @GET('/instances/{instanceId}') - Map getInstanceInfo(@Path('instanceId') String instanceId) + Call getInstanceInfo(@Path('instanceId') String instanceId) @Headers('Accept: application/json') @PUT('/apps/{application}/{instanceId}/status') - Response updateInstanceStatus(@Path('application') String application, @Path('instanceId') String instanceId, @Query('value') String status) + Call updateInstanceStatus(@Path('application') String application, @Path('instanceId') String instanceId, @Query('value') String status) @Headers('Accept: application/json') @DELETE('/apps/{application}/{instanceId}/status') - Response resetInstanceStatus(@Path('application') String application, @Path('instanceId') String instanceId, @Query('value') String status) + Call resetInstanceStatus(@Path('application') String application, @Path('instanceId') String instanceId, @Query('value') String status) @Headers('Accept: application/json') @GET('/apps/{application}') - EurekaApplication getApplication(@Path('application') String application) + Call getApplication(@Path('application') String application) } diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApi.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApi.groovy index cd2b9af6490..a25085eb057 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApi.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApi.groovy @@ -17,12 +17,13 @@ package com.netflix.spinnaker.clouddriver.eureka.api import com.netflix.spinnaker.clouddriver.eureka.model.EurekaApplications -import retrofit.http.GET -import retrofit.http.Headers +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Headers interface EurekaApi { @GET('/apps') @Headers(['Accept: application/json']) - EurekaApplications loadEurekaApplications() + Call loadEurekaApplications() } diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApiFactory.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApiFactory.groovy index e2a9027354c..a7c2da55454 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApiFactory.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/api/EurekaApiFactory.groovy @@ -16,29 +16,24 @@ package com.netflix.spinnaker.clouddriver.eureka.api -import com.jakewharton.retrofit.Ok3Client -import com.netflix.spinnaker.config.OkHttp3ClientConfiguration -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler -import retrofit.RestAdapter -import retrofit.converter.Converter +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spinnaker.config.DefaultServiceEndpoint +import com.netflix.spinnaker.kork.client.ServiceClientProvider +import com.netflix.spinnaker.okhttp.SpinnakerRequestHeaderInterceptor class EurekaApiFactory { - private Converter eurekaConverter - private OkHttp3ClientConfiguration okHttp3ClientConfiguration + private ServiceClientProvider serviceClientProvider + private ObjectMapper objectMapper + private SpinnakerRequestHeaderInterceptor spinnakerRequestHeaderInterceptor - EurekaApiFactory(Converter eurekaConverter, OkHttp3ClientConfiguration okHttp3ClientConfiguration) { - this.eurekaConverter = eurekaConverter - this.okHttp3ClientConfiguration = okHttp3ClientConfiguration + EurekaApiFactory(ServiceClientProvider serviceClientProvider, ObjectMapper objectMapper, SpinnakerRequestHeaderInterceptor spinnakerRequestHeaderInterceptor) { + this.serviceClientProvider = serviceClientProvider + this.objectMapper = objectMapper + this.spinnakerRequestHeaderInterceptor = spinnakerRequestHeaderInterceptor } public EurekaApi createApi(String endpoint) { - new RestAdapter.Builder() - .setConverter(eurekaConverter) - .setClient(new Ok3Client(okHttp3ClientConfiguration.create().build())) - .setEndpoint(endpoint) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build() - .create(EurekaApi) + serviceClientProvider.getService(EurekaApi, new DefaultServiceEndpoint("eurekaapi", endpoint), objectMapper, List.of(spinnakerRequestHeaderInterceptor)) } } diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupport.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupport.groovy index 78d78146628..d9be0b8cc7b 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupport.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupport.groovy @@ -21,6 +21,7 @@ import com.netflix.spinnaker.clouddriver.eureka.api.Eureka import com.netflix.spinnaker.clouddriver.helpers.EnableDisablePercentageCategorizer import com.netflix.spinnaker.clouddriver.model.ClusterProvider import com.netflix.spinnaker.clouddriver.model.ServerGroup +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException @@ -28,7 +29,6 @@ import groovy.transform.InheritConstructors import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component -import retrofit.client.Response @Slf4j @Component @@ -90,7 +90,7 @@ abstract class AbstractEurekaSupport { def instanceId = instanceIds[random.nextInt(instanceIds.size())] task.updateStatus phaseName, "Looking up discovery application name for instance $instanceId (attempt: $retryCount)" - def details = eureka.getInstanceInfo(instanceId) + def details = Retrofit2SyncCall.execute(eureka.getInstanceInfo(instanceId)) def appName = details?.instance?.app if (!appName) { throw new RetryableException("Looking up instance application name in Discovery failed for instance ${instanceId} (attempt: $retryCount)") @@ -142,16 +142,10 @@ abstract class AbstractEurekaSupport { retry(task, phaseName, updateEurekaRetryMax) { retryCount -> task.updateStatus phaseName, "Attempting to mark ${instanceId} as '${discoveryStatus.value}' in discovery (attempt: ${retryCount})." - Response resp - if (discoveryStatus == DiscoveryStatus.OUT_OF_SERVICE) { - resp = eureka.updateInstanceStatus(applicationName, instanceId, discoveryStatus.value) + Retrofit2SyncCall.execute(eureka.updateInstanceStatus(applicationName, instanceId, discoveryStatus.value)) } else { - resp = eureka.resetInstanceStatus(applicationName, instanceId, DiscoveryStatus.OUT_OF_SERVICE.value) - } - - if (resp?.status != 200) { - throw new RetryableException("Non HTTP 200 response from discovery for instance ${instanceId}, will retry (attempt: $retryCount}).") + Retrofit2SyncCall.execute(eureka.resetInstanceStatus(applicationName, instanceId, DiscoveryStatus.OUT_OF_SERVICE.value)) } } } catch (SpinnakerServerException e) { diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/EurekaUtil.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/EurekaUtil.groovy index a0268b17918..fa1bd8bc2cd 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/EurekaUtil.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/EurekaUtil.groovy @@ -16,38 +16,15 @@ package com.netflix.spinnaker.clouddriver.eureka.deploy.ops import com.netflix.spinnaker.clouddriver.eureka.api.Eureka -import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler -import org.apache.http.impl.client.HttpClients -import retrofit.RestAdapter -import retrofit.client.ApacheClient -import retrofit.converter.JacksonConverter - -import java.util.concurrent.atomic.AtomicReference +import com.netflix.spinnaker.config.DefaultServiceEndpoint +import com.netflix.spinnaker.kork.client.ServiceClientProvider import java.util.regex.Pattern class EurekaUtil { - static Eureka getWritableEureka(String endpoint, String region) { + static Eureka getWritableEureka(String endpoint, String region, ServiceClientProvider serviceClientProvider) { String eurekaEndpoint = endpoint.replaceAll(Pattern.quote('{{region}}'), region) - new RestAdapter.Builder() - .setEndpoint(eurekaEndpoint) - .setClient(getApacheClient()) - .setConverter(new JacksonConverter()) - .setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance()) - .build().create(Eureka) + serviceClientProvider.getService(Eureka, new DefaultServiceEndpoint("eureka", eurekaEndpoint)) } - //Lazy-create apache client on request if there is a discoveryEnabled AmazonCredentials: - private static final AtomicReference apacheClient = new AtomicReference<>(null) - - private static ApacheClient getApacheClient() { - if (apacheClient.get() == null) { - synchronized (apacheClient) { - if (apacheClient.get() == null) { - apacheClient.set(new ApacheClient(HttpClients.createDefault())) - } - } - } - return apacheClient.get() - } } diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgent.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgent.groovy index 0db3f5db13a..f7157da1d19 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgent.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgent.groovy @@ -33,6 +33,7 @@ import com.netflix.spinnaker.clouddriver.eureka.model.EurekaApplication import com.netflix.spinnaker.clouddriver.eureka.model.EurekaApplications import com.netflix.spinnaker.clouddriver.model.HealthState import com.netflix.spinnaker.kork.core.RetrySupport +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall import com.netflix.spinnaker.security.AuthenticatedRequest import groovy.util.logging.Slf4j @@ -95,7 +96,7 @@ class EurekaCachingAgent implements CachingAgent, HealthProvidingCachingAgent, C CacheResult loadData(ProviderCache providerCache) { log.info("Describing items in ${agentType}") EurekaApplications disco = AuthenticatedRequest.allowAnonymous({ - retry.retry({ eurekaApi.loadEurekaApplications() }, 3, 100, false) + retry.retry({ Retrofit2SyncCall.execute(eurekaApi.loadEurekaApplications()) }, 3, 100, false) }) Map> instanceHealthRelationships = [:].withDefault { new HashSet() } diff --git a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/config/EurekaProviderConfiguration.groovy b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/config/EurekaProviderConfiguration.groovy index 47963bb923c..95a2f2223a8 100644 --- a/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/config/EurekaProviderConfiguration.groovy +++ b/clouddriver-eureka/src/main/groovy/com/netflix/spinnaker/config/EurekaProviderConfiguration.groovy @@ -26,8 +26,9 @@ import com.netflix.spinnaker.clouddriver.eureka.provider.EurekaCachingProvider import com.netflix.spinnaker.clouddriver.eureka.provider.agent.EurekaAwareProvider import com.netflix.spinnaker.clouddriver.eureka.provider.agent.EurekaCachingAgent import com.netflix.spinnaker.clouddriver.eureka.provider.config.EurekaAccountConfigurationProperties -import com.netflix.spinnaker.okhttp.OkHttp3MetricsInterceptor +import com.netflix.spinnaker.kork.client.ServiceClientProvider import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties +import com.netflix.spinnaker.okhttp.SpinnakerRequestHeaderInterceptor import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty @@ -40,8 +41,6 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.core.env.Environment -import retrofit.converter.Converter -import retrofit.converter.JacksonConverter import java.util.regex.Pattern @@ -76,18 +75,12 @@ class EurekaProviderConfiguration { return properties } - private static Converter eurekaConverter() { - new JacksonConverter(new ObjectMapper() + private static ObjectMapper getObjectMapper() { + new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .enable(DeserializationFeature.UNWRAP_ROOT_VALUE) - .enable(MapperFeature.AUTO_DETECT_CREATORS)) - } - - private EurekaApiFactory eurekaApiFactory(OkHttpMetricsInterceptorProperties okHttpMetricsInterceptorProperties) { - OkHttp3ClientConfiguration config = new OkHttp3ClientConfiguration(eurekaClientConfig(), - new OkHttp3MetricsInterceptor({ registry }, okHttpMetricsInterceptorProperties)) - return new EurekaApiFactory(eurekaConverter(), config) + .enable(MapperFeature.AUTO_DETECT_CREATORS) } @Value('${eureka.poll-interval-millis:15000}') @@ -100,9 +93,11 @@ class EurekaProviderConfiguration { EurekaCachingProvider eurekaCachingProvider(EurekaAccountConfigurationProperties eurekaAccountConfigurationProperties, OkHttpMetricsInterceptorProperties okHttpMetricsInterceptorProperties, List eurekaAwareProviderList, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + ServiceClientProvider serviceClientProvider, + SpinnakerRequestHeaderInterceptor spinnakerRequestHeaderInterceptor) { List agents = [] - def eurekaApiFactory = eurekaApiFactory(okHttpMetricsInterceptorProperties) + def eurekaApiFactory = new EurekaApiFactory(serviceClientProvider, getObjectMapper(), spinnakerRequestHeaderInterceptor) eurekaAccountConfigurationProperties.accounts.each { EurekaAccountConfigurationProperties.EurekaAccount accountConfig -> accountConfig.regions.each { region -> String eurekaHost = accountConfig.readOnlyUrl.replaceAll(Pattern.quote('{{region}}'), region) diff --git a/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupportSpec.groovy b/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupportSpec.groovy index 1bc457a5fe3..00513d28d4c 100644 --- a/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupportSpec.groovy +++ b/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/deploy/ops/AbstractEurekaSupportSpec.groovy @@ -22,7 +22,6 @@ import com.netflix.spinnaker.clouddriver.eureka.api.Eureka import com.netflix.spinnaker.clouddriver.model.ClusterProvider import com.netflix.spinnaker.clouddriver.model.Instance import com.netflix.spinnaker.clouddriver.model.ServerGroup -import retrofit.client.Response import spock.lang.Specification import spock.lang.Subject import spock.lang.Unroll diff --git a/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgentSpec.groovy b/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgentSpec.groovy index 1f2457641eb..38adb8544ab 100644 --- a/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgentSpec.groovy +++ b/clouddriver-eureka/src/test/groovy/com/netflix/spinnaker/clouddriver/eureka/provider/agent/EurekaCachingAgentSpec.groovy @@ -9,6 +9,7 @@ import com.netflix.spinnaker.clouddriver.eureka.model.EurekaApplication import com.netflix.spinnaker.clouddriver.eureka.model.EurekaApplications import com.netflix.spinnaker.clouddriver.eureka.model.EurekaInstance import com.netflix.spinnaker.clouddriver.model.HealthState +import retrofit2.mock.Calls import spock.lang.Specification import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.HEALTH @@ -23,12 +24,13 @@ class EurekaCachingAgentSpec extends Specification { def "it should cache instances"() { given: - eurekaApi.loadEurekaApplications() >> new EurekaApplications(applications: [ - new EurekaApplication(name: "foo", instances: [ + eurekaApi.loadEurekaApplications() >> Calls.response(new EurekaApplications(applications: [ + new EurekaApplication(name: "foo", instances: [ instance("foo", "i-1", "UP"), instance("foo", "i-2", "UP") ]) - ]) + ])) + when: def result = agent.loadData(providerCache) @@ -43,12 +45,12 @@ class EurekaCachingAgentSpec extends Specification { def "it should dedupe multiple discovery records prefering HealthState order"() { given: - eurekaApi.loadEurekaApplications() >> new EurekaApplications(applications: [ + eurekaApi.loadEurekaApplications() >> Calls.response(new EurekaApplications(applications: [ new EurekaApplication(name: "foo", instances: [ instance("foo", "i-1", "UP"), instance("foo", "i-1", "DOWN") ]) - ]) + ])) when: def result = agent.loadData(providerCache) @@ -63,13 +65,13 @@ class EurekaCachingAgentSpec extends Specification { def "it should dedupe multiple discovery records preferring newest"() { given: - eurekaApi.loadEurekaApplications() >> new EurekaApplications(applications: [ + eurekaApi.loadEurekaApplications() >> Calls.response(new EurekaApplications(applications: [ new EurekaApplication(name: "foo", instances: [ instance("foo", "i-1", "UP", 12345), instance("foo", "i-1", "UP", 23451), instance("foo", "i-1", "UP", 12344) ]) - ]) + ])) when: def result = agent.loadData(providerCache) diff --git a/clouddriver-google/clouddriver-google.gradle b/clouddriver-google/clouddriver-google.gradle index 5b59fc0368f..c0568555a51 100644 --- a/clouddriver-google/clouddriver-google.gradle +++ b/clouddriver-google/clouddriver-google.gradle @@ -26,8 +26,9 @@ dependencies { implementation "io.spinnaker.kork:kork-cloud-config-server" implementation "io.spinnaker.kork:kork-moniker" implementation "io.spinnaker.kork:kork-retrofit" + implementation "io.spinnaker.kork:kork-retrofit2" implementation "io.spinnaker.kork:kork-exceptions" - implementation "com.squareup.retrofit:retrofit" + implementation "io.spinnaker.kork:kork-web" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.cloud:spring-cloud-context" diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/GCEUtil.groovy b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/GCEUtil.groovy index 34a781404e6..f09b9df69fd 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/GCEUtil.groovy +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/GCEUtil.groovy @@ -50,6 +50,7 @@ import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleSubnetProvid import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials import com.netflix.spinnaker.config.GoogleConfiguration import com.netflix.spinnaker.kork.artifacts.model.Artifact +import com.netflix.spinnaker.kork.client.ServiceClientProvider import groovy.util.logging.Slf4j import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.HEALTH_CHECKS @@ -2251,7 +2252,7 @@ class GCEUtil { return healthChecks } - static List fetchInstances(GoogleExecutorTraits agent, GoogleNamedAccountCredentials credentials) { + static List fetchInstances(GoogleExecutorTraits agent, GoogleNamedAccountCredentials credentials, ServiceClientProvider serviceClientProvider) { List instances = new ArrayList() String pageToken = null @@ -2261,7 +2262,7 @@ class GCEUtil { "compute.instances.aggregatedList", agent.TAG_SCOPE, agent.SCOPE_GLOBAL) - instances += transformInstances(instanceAggregatedList, credentials) + instances += transformInstances(instanceAggregatedList, credentials, serviceClientProvider) pageToken = instanceAggregatedList.getNextPageToken() if (!pageToken) { @@ -2272,12 +2273,12 @@ class GCEUtil { return instances } - private static List transformInstances(InstanceAggregatedList instanceAggregatedList, GoogleNamedAccountCredentials credentials) throws IOException { + private static List transformInstances(InstanceAggregatedList instanceAggregatedList, GoogleNamedAccountCredentials credentials, ServiceClientProvider serviceClientProvider) throws IOException { List instances = [] instanceAggregatedList?.items?.each { String zone, InstancesScopedList instancesScopedList -> instancesScopedList?.instances?.each { Instance instance -> - instances << GoogleInstances.createFromComputeInstance(instance, credentials) + instances << GoogleInstances.createFromComputeInstance(instance, credentials, serviceClientProvider) } } diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbstractEnableDisableAtomicOperation.groovy b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbstractEnableDisableAtomicOperation.groovy index fcc5014f458..8d93d4f899e 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbstractEnableDisableAtomicOperation.groovy +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbstractEnableDisableAtomicOperation.groovy @@ -28,6 +28,7 @@ import com.netflix.spinnaker.clouddriver.google.deploy.description.EnableDisable import com.netflix.spinnaker.clouddriver.google.model.GoogleAutoscalingPolicy import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleLoadBalancerProvider +import com.netflix.spinnaker.kork.client.ServiceClientProvider import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException import org.springframework.beans.factory.annotation.Autowired @@ -56,6 +57,9 @@ abstract class AbstractEnableDisableAtomicOperation extends GoogleAtomicOperatio @Autowired SafeRetry safeRetry + @Autowired + ServiceClientProvider serviceClientProvider + AbstractEnableDisableAtomicOperation(EnableDisableGoogleServerGroupDescription description) { this.description = description } @@ -106,7 +110,8 @@ abstract class AbstractEnableDisableAtomicOperation extends GoogleAtomicOperatio GCEUtil.getLocalName(instance.getInstance()), disable ? EnableDisableConsulInstance.State.disable - : EnableDisableConsulInstance.State.enable) + : EnableDisableConsulInstance.State.enable, + serviceClientProvider) } catch (SpinnakerServerException ignored) { // Consul isn't running } diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleInstanceCachingAgent.groovy b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleInstanceCachingAgent.groovy index 7789ae77cc8..ebeaef73a2a 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleInstanceCachingAgent.groovy +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleInstanceCachingAgent.groovy @@ -26,6 +26,7 @@ import com.netflix.spinnaker.clouddriver.google.cache.Keys import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil import com.netflix.spinnaker.clouddriver.google.model.GoogleInstance import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials +import com.netflix.spinnaker.kork.client.ServiceClientProvider import groovy.transform.InheritConstructors import groovy.util.logging.Slf4j @@ -44,21 +45,25 @@ class GoogleInstanceCachingAgent extends AbstractGoogleCachingAgent { INFORMATIVE.forType(CLUSTERS.ns) ] + ServiceClientProvider serviceClientProvider + String agentType = "${accountName}/global/${GoogleInstanceCachingAgent.simpleName}" GoogleInstanceCachingAgent(String clouddriverUserAgentApplicationName, GoogleNamedAccountCredentials credentials, ObjectMapper objectMapper, - Registry registry){ + Registry registry, + ServiceClientProvider serviceClientProvider){ super(clouddriverUserAgentApplicationName, credentials, objectMapper, registry) + this.serviceClientProvider = serviceClientProvider } @Override CacheResult loadData(ProviderCache providerCache) { - List instances = GCEUtil.fetchInstances(this, credentials) + List instances = GCEUtil.fetchInstances(this, credentials, serviceClientProvider) buildCacheResults(providerCache, instances) } diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/security/GoogleNamedAccountCredentials.groovy b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/security/GoogleNamedAccountCredentials.groovy index e028123bcee..dc0c584abe3 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/security/GoogleNamedAccountCredentials.groovy +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/security/GoogleNamedAccountCredentials.groovy @@ -31,6 +31,7 @@ import com.netflix.spinnaker.clouddriver.security.AbstractAccountCredentials import com.netflix.spinnaker.clouddriver.security.AccountCredentials import com.netflix.spinnaker.fiat.model.resources.Permissions +import com.netflix.spinnaker.kork.client.ServiceClientProvider import com.netflix.spinnaker.moniker.Namer import groovy.transform.TupleConstructor import groovy.util.logging.Slf4j @@ -197,9 +198,9 @@ class GoogleNamedAccountCredentials extends AbstractAccountCredentials @@ -89,7 +92,7 @@ public class GoogleCredentialsConfiguration { .requiredGroupMembership(a.getRequiredGroupMembership()) .permissions(a.getPermissions().build()) .applicationName(clouddriverUserAgentApplicationName) - .consulConfig(a.getConsul()) + .consulConfig(a.getConsul(), serviceClientProvider) .instanceTypeDisks(googleDeployDefaults.getInstanceTypeDisks()) .userDataFile(a.getUserDataFile()) .regionsToManage( diff --git a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/model/GoogleInstances.java b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/model/GoogleInstances.java index bd9a8cd44ec..80aa3c0bdb9 100644 --- a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/model/GoogleInstances.java +++ b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/model/GoogleInstances.java @@ -22,13 +22,16 @@ import com.netflix.spinnaker.clouddriver.google.model.callbacks.Utils; import com.netflix.spinnaker.clouddriver.google.model.health.GoogleInstanceHealth; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import java.math.BigInteger; import java.util.Optional; public final class GoogleInstances { public static GoogleInstance createFromComputeInstance( - Instance input, GoogleNamedAccountCredentials credentials) { + Instance input, + GoogleNamedAccountCredentials credentials, + ServiceClientProvider serviceClientProvider) { String localZone = Utils.getLocalName(input.getZone()); @@ -49,7 +52,7 @@ public static GoogleInstance createFromComputeInstance( output.setSelfLink(input.getSelfLink()); output.setTags(input.getTags()); output.setLabels(input.getLabels()); - output.setConsulNode(calculateConsulNode(input, credentials)); + output.setConsulNode(calculateConsulNode(input, credentials, serviceClientProvider)); output.setInstanceHealth(createInstanceHealth(input)); return output; } @@ -70,9 +73,12 @@ private static String calculateNetworkName( } private static ConsulNode calculateConsulNode( - Instance input, GoogleNamedAccountCredentials credentials) { + Instance input, + GoogleNamedAccountCredentials credentials, + ServiceClientProvider serviceClientProvider) { return credentials.getConsulConfig() != null && credentials.getConsulConfig().isEnabled() - ? ConsulProviderUtils.getHealths(credentials.getConsulConfig(), input.getName()) + ? ConsulProviderUtils.getHealths( + credentials.getConsulConfig(), input.getName(), serviceClientProvider) : null; } diff --git a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgent.java b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgent.java index 81200a45bae..15fb63c2852 100644 --- a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgent.java +++ b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgent.java @@ -101,6 +101,7 @@ import com.netflix.spinnaker.clouddriver.google.provider.GoogleInfrastructureProvider; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import com.netflix.spinnaker.clouddriver.names.NamerRegistry; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import com.netflix.spinnaker.moniker.Moniker; import com.netflix.spinnaker.moniker.Namer; import java.io.IOException; @@ -145,13 +146,15 @@ public abstract class AbstractGoogleServerGroupCachingAgent private final OnDemandMetricsSupport onDemandMetricsSupport; private final ObjectMapper objectMapper; private final Namer naming; + private final ServiceClientProvider serviceClientProvider; AbstractGoogleServerGroupCachingAgent( GoogleNamedAccountCredentials credentials, GoogleComputeApiFactory computeApiFactory, Registry registry, String region, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + ServiceClientProvider serviceClientProvider) { this.credentials = credentials; this.computeApiFactory = computeApiFactory; this.region = region; @@ -162,6 +165,7 @@ public abstract class AbstractGoogleServerGroupCachingAgent .withProvider(GoogleCloudProvider.getID()) .withAccount(credentials.getName()) .withResource(GoogleLabeledResource.class); + this.serviceClientProvider = serviceClientProvider; } @Override @@ -457,7 +461,10 @@ private List getServerGroups(ProviderCache providerCache) thr ImmutableList instances = retrieveAllInstancesInRegion().stream() - .map(instance -> GoogleInstances.createFromComputeInstance(instance, credentials)) + .map( + instance -> + GoogleInstances.createFromComputeInstance( + instance, credentials, serviceClientProvider)) .collect(toImmutableList()); return constructServerGroups( providerCache, @@ -493,7 +500,10 @@ private Optional getServerGroup(String name, ProviderCache pr List instances = retrieveRelevantInstances(manager).stream() - .map(instance -> GoogleInstances.createFromComputeInstance(instance, credentials)) + .map( + instance -> + GoogleInstances.createFromComputeInstance( + instance, credentials, serviceClientProvider)) .collect(toImmutableList()); List autoscalers = diff --git a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgent.java b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgent.java index 7b019a9821d..a53beba14fd 100644 --- a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgent.java +++ b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgent.java @@ -28,6 +28,7 @@ import com.netflix.spinnaker.clouddriver.google.compute.RegionAutoscalers; import com.netflix.spinnaker.clouddriver.google.compute.RegionInstanceGroupManagers; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import java.io.IOException; import java.util.Collection; import java.util.Map; @@ -45,8 +46,9 @@ public GoogleRegionalServerGroupCachingAgent( GoogleComputeApiFactory computeApiFactory, Registry registry, String region, - ObjectMapper objectMapper) { - super(credentials, computeApiFactory, registry, region, objectMapper); + ObjectMapper objectMapper, + ServiceClientProvider serviceClientProvider) { + super(credentials, computeApiFactory, registry, region, objectMapper, serviceClientProvider); } @Override diff --git a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgent.java b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgent.java index 2412f6ab479..bba81c236d3 100644 --- a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgent.java +++ b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgent.java @@ -39,6 +39,7 @@ import com.netflix.spinnaker.clouddriver.google.compute.ZoneInstanceGroupManagers; import com.netflix.spinnaker.clouddriver.google.model.callbacks.Utils; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import java.io.IOException; import java.util.Collection; import java.util.Map; @@ -56,8 +57,9 @@ public GoogleZonalServerGroupCachingAgent( GoogleComputeApiFactory computeApiFactory, Registry registry, String region, - ObjectMapper objectMapper) { - super(credentials, computeApiFactory, registry, region, objectMapper); + ObjectMapper objectMapper, + ServiceClientProvider serviceClientProvider) { + super(credentials, computeApiFactory, registry, region, objectMapper, serviceClientProvider); } @Override diff --git a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/security/GoogleCredentialsLifecycleHandler.java b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/security/GoogleCredentialsLifecycleHandler.java index 742e1e13ddb..b8ab7bdb1ab 100644 --- a/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/security/GoogleCredentialsLifecycleHandler.java +++ b/clouddriver-google/src/main/java/com/netflix/spinnaker/clouddriver/google/security/GoogleCredentialsLifecycleHandler.java @@ -23,6 +23,7 @@ import com.netflix.spinnaker.clouddriver.google.provider.GoogleInfrastructureProvider; import com.netflix.spinnaker.clouddriver.google.provider.agent.*; import com.netflix.spinnaker.credentials.CredentialsLifecycleHandler; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import java.util.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -38,6 +39,7 @@ public class GoogleCredentialsLifecycleHandler private final ObjectMapper objectMapper; private final Registry registry; private final String clouddriverUserAgentApplicationName; + private final ServiceClientProvider serviceClientProvider; @Override public void credentialsAdded(GoogleNamedAccountCredentials credentials) { @@ -99,7 +101,11 @@ private void addAgentFor(GoogleNamedAccountCredentials credentials) { clouddriverUserAgentApplicationName, credentials, objectMapper, registry)); googleCachingAgents.add( new GoogleInstanceCachingAgent( - clouddriverUserAgentApplicationName, credentials, objectMapper, registry)); + clouddriverUserAgentApplicationName, + credentials, + objectMapper, + registry, + serviceClientProvider)); googleCachingAgents.add( new GoogleImageCachingAgent( clouddriverUserAgentApplicationName, @@ -130,10 +136,20 @@ private void addAgentFor(GoogleNamedAccountCredentials credentials) { clouddriverUserAgentApplicationName, credentials, objectMapper, registry, region)); googleServerGroupAgents.add( new GoogleRegionalServerGroupCachingAgent( - credentials, googleComputeApiFactory, registry, region, objectMapper)); + credentials, + googleComputeApiFactory, + registry, + region, + objectMapper, + serviceClientProvider)); googleServerGroupAgents.add( new GoogleZonalServerGroupCachingAgent( - credentials, googleComputeApiFactory, registry, region, objectMapper)); + credentials, + googleComputeApiFactory, + registry, + region, + objectMapper, + serviceClientProvider)); } googleInfrastructureProvider.addAgents(googleCachingAgents); diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgentTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgentTest.java index 4b2b768e5ab..140c1c4ed03 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgentTest.java +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/AbstractGoogleServerGroupCachingAgentTest.java @@ -73,6 +73,7 @@ import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup; import com.netflix.spinnaker.clouddriver.google.model.health.GoogleInstanceHealth; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import java.math.BigInteger; import java.util.Collection; import java.util.List; @@ -83,6 +84,7 @@ import org.assertj.core.data.Offset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; class AbstractGoogleServerGroupCachingAgentTest { @@ -93,6 +95,8 @@ class AbstractGoogleServerGroupCachingAgentTest { private static final String ZONE = REGION + "-myzone"; private static final String ZONE_URL = "http://compute/zones/" + ZONE; + @Mock private static ServiceClientProvider serviceClientProvider; + private ObjectMapper objectMapper; @BeforeEach @@ -962,7 +966,13 @@ private static class TestCachingAgent extends AbstractGoogleServerGroupCachingAg GoogleComputeApiFactory computeApiFactory, Collection instanceGroupManagers, Collection autoscalers) { - super(credentials, computeApiFactory, new DefaultRegistry(), REGION, new ObjectMapper()); + super( + credentials, + computeApiFactory, + new DefaultRegistry(), + REGION, + new ObjectMapper(), + serviceClientProvider); this.instanceGroupManagers = instanceGroupManagers; this.autoscalers = autoscalers; } diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgentTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgentTest.java index 51f9b944230..844aa4bd7ce 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgentTest.java +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleRegionalServerGroupCachingAgentTest.java @@ -60,6 +60,7 @@ import com.netflix.spinnaker.clouddriver.google.names.GoogleLabeledResourceNamer; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import com.netflix.spinnaker.clouddriver.names.NamingStrategy; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import com.netflix.spinnaker.moniker.Moniker; import java.io.IOException; import java.util.Collection; @@ -67,6 +68,7 @@ import java.util.concurrent.Executors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; final class GoogleRegionalServerGroupCachingAgentTest { @@ -79,6 +81,8 @@ final class GoogleRegionalServerGroupCachingAgentTest { private static final String REGION_URL = "http://compute/regions/" + REGION; private static final String ZONE = REGION + "-myzone"; + @Mock private static ServiceClientProvider serviceClientProvider; + private ObjectMapper objectMapper; @BeforeEach @@ -636,7 +640,8 @@ public static GoogleRegionalServerGroupCachingAgent createCachingAgent(Compute c MoreExecutors.listeningDecorator(Executors.newCachedThreadPool())), new DefaultRegistry(), REGION, - new ObjectMapper()); + new ObjectMapper(), + serviceClientProvider); } private static InstanceGroupManager instanceGroupManager(String name) { diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgentTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgentTest.java index f9292340c24..a1b08b5195d 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgentTest.java +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/GoogleZonalServerGroupCachingAgentTest.java @@ -55,6 +55,7 @@ import com.netflix.spinnaker.clouddriver.google.names.GoogleLabeledResourceNamer; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import com.netflix.spinnaker.clouddriver.names.NamingStrategy; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import com.netflix.spinnaker.moniker.Moniker; import java.io.IOException; import java.util.Collection; @@ -62,6 +63,7 @@ import java.util.concurrent.Executors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; final class GoogleZonalServerGroupCachingAgentTest { @@ -74,6 +76,8 @@ final class GoogleZonalServerGroupCachingAgentTest { private static final String ZONE = REGION + "-myzone"; private static final String ZONE_URL = "http://compute/zones/" + ZONE; + @Mock private static ServiceClientProvider serviceClientProvider; + private ObjectMapper objectMapper; @BeforeEach @@ -517,7 +521,8 @@ public static GoogleZonalServerGroupCachingAgent createCachingAgent(Compute comp MoreExecutors.listeningDecorator(Executors.newCachedThreadPool())), new DefaultRegistry(), REGION, - new ObjectMapper()); + new ObjectMapper(), + serviceClientProvider); } private static InstanceGroupManager instanceGroupManager(String name) { diff --git a/clouddriver-google/src/test/java/com/netflix/spinnaker/clouddriver/google/config/GoogleCredentialsConfigurationTest.java b/clouddriver-google/src/test/java/com/netflix/spinnaker/clouddriver/google/config/GoogleCredentialsConfigurationTest.java index 6292a3a1d4d..a0d16713f46 100644 --- a/clouddriver-google/src/test/java/com/netflix/spinnaker/clouddriver/google/config/GoogleCredentialsConfigurationTest.java +++ b/clouddriver-google/src/test/java/com/netflix/spinnaker/clouddriver/google/config/GoogleCredentialsConfigurationTest.java @@ -27,6 +27,7 @@ import com.netflix.spinnaker.config.GoogleConfiguration; import com.netflix.spinnaker.credentials.CredentialsLifecycleHandler; import com.netflix.spinnaker.credentials.CredentialsRepository; +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import com.netflix.spinnaker.kork.configserver.ConfigFileService; import org.junit.jupiter.api.Test; import org.springframework.boot.context.annotation.UserConfigurations; @@ -94,5 +95,10 @@ Registry getRegistry() { String getClouddriverUserAgentApplicationName() { return "clouddriverUserAgentApplicationName"; } + + @Bean + ServiceClientProvider getServiceClientProvider() { + return mock(ServiceClientProvider.class); + } } } diff --git a/clouddriver-kubernetes/clouddriver-kubernetes.gradle b/clouddriver-kubernetes/clouddriver-kubernetes.gradle index f9f3a83b6d0..b1a09ff273a 100644 --- a/clouddriver-kubernetes/clouddriver-kubernetes.gradle +++ b/clouddriver-kubernetes/clouddriver-kubernetes.gradle @@ -73,6 +73,7 @@ dependencies { implementation "io.spinnaker.kork:kork-cloud-config-server" implementation "io.spinnaker.kork:kork-exceptions" implementation "io.spinnaker.kork:kork-moniker" + implementation "io.spinnaker.kork:kork-retrofit" implementation "io.spinnaker.kork:kork-secrets" implementation "io.spinnaker.kork:kork-security" implementation "io.kubernetes:client-java" @@ -101,6 +102,7 @@ dependencies { testImplementation "org.springframework:spring-test" testImplementation "org.springframework.boot:spring-boot-test" testImplementation "org.apache.groovy:groovy-templates" + testImplementation "com.squareup.retrofit2:retrofit-mock" integrationImplementation project(":clouddriver-web") integrationImplementation "org.springframework.boot:spring-boot-starter-test" diff --git a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/Front50ApplicationLoader.java b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/Front50ApplicationLoader.java index 4430d8b6607..0a32b3429a0 100644 --- a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/Front50ApplicationLoader.java +++ b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/Front50ApplicationLoader.java @@ -18,6 +18,7 @@ import com.netflix.spinnaker.clouddriver.core.services.Front50Service; import com.netflix.spinnaker.clouddriver.model.Front50Application; +import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall; import com.netflix.spinnaker.security.AuthenticatedRequest; import java.util.Collections; import java.util.Set; @@ -64,7 +65,8 @@ protected void refreshCache() { return; } Set response = - AuthenticatedRequest.allowAnonymous(front50Service::getAllApplicationsUnrestricted); + AuthenticatedRequest.allowAnonymous( + () -> Retrofit2SyncCall.execute(front50Service.getAllApplicationsUnrestricted())); Set applicationsKnownToFront50 = response.stream().map(Front50Application::getName).collect(Collectors.toSet()); log.info("received {} applications from front50", applicationsKnownToFront50.size()); diff --git a/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/KubernetesCoreCachingAgentTest.java b/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/KubernetesCoreCachingAgentTest.java index 4a1dc24982f..965f988048f 100644 --- a/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/KubernetesCoreCachingAgentTest.java +++ b/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/caching/agent/KubernetesCoreCachingAgentTest.java @@ -67,6 +67,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.stubbing.Answer; +import retrofit2.mock.Calls; final class KubernetesCoreCachingAgentTest { private static final String ACCOUNT = "my-account"; @@ -345,7 +346,8 @@ public void testCheckingOfApplicationsInFront50ForLoadData(boolean checkApplicat getNamedAccountCredentials(), 1, front50ApplicationLoader, checkApplicationInFront50); when(front50Service.getAllApplicationsUnrestricted()) - .thenReturn(getApplicationsFromFront50("applications-response-from-front50.json")); + .thenReturn( + Calls.response(getApplicationsFromFront50("applications-response-from-front50.json"))); front50ApplicationLoader.refreshCache(); verify(front50Service).getAllApplicationsUnrestricted(); @@ -378,7 +380,8 @@ public void testK8sManifestWithNoApplicationInFront50ShouldNotBeCachedInLoadData getNamedAccountCredentials(deploymentName), 1, front50ApplicationLoader, true); when(front50Service.getAllApplicationsUnrestricted()) - .thenReturn(getApplicationsFromFront50("applications-response-from-front50.json")); + .thenReturn( + Calls.response(getApplicationsFromFront50("applications-response-from-front50.json"))); front50ApplicationLoader.refreshCache(); verify(front50Service).getAllApplicationsUnrestricted(); diff --git a/clouddriver-lambda/clouddriver-lambda.gradle b/clouddriver-lambda/clouddriver-lambda.gradle index f9318a202fb..ab6762f8e22 100644 --- a/clouddriver-lambda/clouddriver-lambda.gradle +++ b/clouddriver-lambda/clouddriver-lambda.gradle @@ -19,8 +19,6 @@ dependencies { implementation "org.apache.commons:commons-compress:1.20" implementation "org.apache.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-web" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.squareup.retrofit:retrofit" implementation "com.netflix.spectator:spectator-api" implementation "com.netflix.frigga:frigga" diff --git a/clouddriver-titus/clouddriver-titus.gradle b/clouddriver-titus/clouddriver-titus.gradle index b567e6a99f5..44fc45a0c19 100644 --- a/clouddriver-titus/clouddriver-titus.gradle +++ b/clouddriver-titus/clouddriver-titus.gradle @@ -29,6 +29,8 @@ dependencies { implementation "io.spinnaker.kork:kork-exceptions" implementation "io.spinnaker.kork:kork-security" implementation "io.spinnaker.kork:kork-moniker" + implementation "io.spinnaker.kork:kork-retrofit" + implementation "io.spinnaker.kork:kork-web" implementation "com.squareup.okhttp3:okhttp:3.1.2" implementation "io.grpc:grpc-netty-shaded:$grpcVersion" implementation "io.grpc:grpc-protobuf:$grpcVersion" diff --git a/clouddriver-titus/src/main/groovy/com/netflix/spinnaker/clouddriver/titus/deploy/ops/discovery/TitusEurekaSupport.groovy b/clouddriver-titus/src/main/groovy/com/netflix/spinnaker/clouddriver/titus/deploy/ops/discovery/TitusEurekaSupport.groovy index 0bf36921480..2c01f43b9b6 100644 --- a/clouddriver-titus/src/main/groovy/com/netflix/spinnaker/clouddriver/titus/deploy/ops/discovery/TitusEurekaSupport.groovy +++ b/clouddriver-titus/src/main/groovy/com/netflix/spinnaker/clouddriver/titus/deploy/ops/discovery/TitusEurekaSupport.groovy @@ -16,14 +16,13 @@ package com.netflix.spinnaker.clouddriver.titus.deploy.ops.discovery -import com.google.common.annotations.VisibleForTesting import com.netflix.spinnaker.clouddriver.eureka.api.Eureka import com.netflix.spinnaker.clouddriver.eureka.deploy.ops.AbstractEurekaSupport import com.netflix.spinnaker.clouddriver.eureka.deploy.ops.EurekaUtil import com.netflix.spinnaker.clouddriver.titus.TitusClientProvider import com.netflix.spinnaker.clouddriver.titus.model.TitusInstance import com.netflix.spinnaker.clouddriver.titus.model.TitusServerGroup -import groovy.transform.PackageScope +import com.netflix.spinnaker.kork.client.ServiceClientProvider; import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -35,11 +34,14 @@ class TitusEurekaSupport extends AbstractEurekaSupport { @Autowired TitusClientProvider titusClientProvider + @Autowired + ServiceClientProvider serviceClientProvider + Eureka getEureka(def credentials, String region) { if (!credentials.discoveryEnabled) { throw new AbstractEurekaSupport.DiscoveryNotConfiguredException() } - EurekaUtil.getWritableEureka(credentials.discovery, region) + EurekaUtil.getWritableEureka(credentials.discovery, region, serviceClientProvider) } boolean verifyInstanceAndAsgExist(def credentials, diff --git a/clouddriver-yandex/clouddriver-yandex.gradle b/clouddriver-yandex/clouddriver-yandex.gradle index f404306f9a3..ca29c878be2 100644 --- a/clouddriver-yandex/clouddriver-yandex.gradle +++ b/clouddriver-yandex/clouddriver-yandex.gradle @@ -23,7 +23,6 @@ dependencies { implementation "io.spinnaker.kork:kork-artifacts" implementation "io.spinnaker.kork:kork-config" implementation "io.spinnaker.kork:kork-moniker" - implementation "com.squareup.retrofit:retrofit" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation "com.google.protobuf:protobuf-java"