Skip to content

Commit

Permalink
feat(aws): Implement Kork credentials and provider specific Credentia…
Browse files Browse the repository at this point in the history
…lsRepository (#5021)

* feat(aws): Implement Kork credentials and provider specific CredentialsRepository

* Keep CredentialsLoader and AmazonAccountSynchronizer due to clouddriver-ecs' dependency on them
* Replace uses or AccountCredentialsProvider with CredentialsRepository
* Update AgentProvider and its implementatons to take Credentials in agents() since agents must be generated before credentials are added to repository
* Move agent generation tasks to AmazonCredentialsLifecycleHandler to generate/remove agents when credentials are added/removed.

* Add missing unit tests

* Ensure InstanceTerminationLifecycleWorker is created after credentials are available

* Ensure ReservationReportCachingAgent is scheduled once

* minimize calls to EC2 in ReservationReportCachingAgent

* Ensure one public ImageCachingAgent per region
  • Loading branch information
nabuskey committed Oct 26, 2020
1 parent cdca4de commit 80e07fe
Show file tree
Hide file tree
Showing 72 changed files with 1,616 additions and 730 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@

package com.netflix.spinnaker.cats.agent;

import com.netflix.spinnaker.credentials.Credentials;
import com.netflix.spinnaker.kork.annotations.Beta;
import java.util.Collection;

@Beta
public interface AgentProvider {
boolean supports(String providerName);

Collection<Agent> agents();
default Collection<Agent> agents(Credentials credentials) {
return null;
}
}
1 change: 1 addition & 0 deletions clouddriver-aws/clouddriver-aws.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
implementation "com.netflix.spinnaker.kork:kork-aws"
implementation "com.netflix.spinnaker.kork:kork-exceptions"
implementation "com.netflix.spinnaker.kork:kork-security"
implementation "com.netflix.spinnaker.kork:kork-credentials"
implementation "com.netflix.spinnaker.kork:kork-moniker"
implementation "com.squareup.okhttp:okhttp"
implementation "com.squareup.okhttp:okhttp-apache"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ import com.amazonaws.services.cloudwatch.model.MetricAlarm
import com.amazonaws.services.cloudwatch.model.StateValue
import com.amazonaws.services.gamelift.model.DescribeScalingPoliciesRequest
import com.netflix.spinnaker.cats.agent.RunnableAgent
import com.netflix.spinnaker.clouddriver.aws.AmazonCloudProvider
import com.netflix.spinnaker.clouddriver.aws.provider.AwsCleanupProvider
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials
import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import com.netflix.spinnaker.clouddriver.cache.CustomScheduledAgent
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsRepository
import com.netflix.spinnaker.clouddriver.security.ProviderUtils
import com.netflix.spinnaker.credentials.CredentialsRepository
import groovy.util.logging.Slf4j
import org.joda.time.DateTime

Expand All @@ -45,25 +43,25 @@ class CleanupAlarmsAgent implements RunnableAgent, CustomScheduledAgent {
public static final Pattern ALARM_NAME_PATTERN = Pattern.compile(".+-v[0-9]{3}-alarm-.+")

final AmazonClientProvider amazonClientProvider
final AccountCredentialsRepository accountCredentialsRepository
final CredentialsRepository<NetflixAmazonCredentials> credentialsRepository
final long pollIntervalMillis
final long timeoutMillis
final int daysToLeave


CleanupAlarmsAgent(AmazonClientProvider amazonClientProvider,
AccountCredentialsRepository accountCredentialsRepository,
CredentialsRepository<NetflixAmazonCredentials> credentialsRepository,
int daysToLeave) {
this(amazonClientProvider, accountCredentialsRepository, POLL_INTERVAL_MILLIS, DEFAULT_TIMEOUT_MILLIS, daysToLeave)
this(amazonClientProvider, credentialsRepository, POLL_INTERVAL_MILLIS, DEFAULT_TIMEOUT_MILLIS, daysToLeave)
}

CleanupAlarmsAgent(AmazonClientProvider amazonClientProvider,
AccountCredentialsRepository accountCredentialsRepository,
CredentialsRepository<NetflixAmazonCredentials> credentialsRepository,
long pollIntervalMillis,
long timeoutMills,
int daysToLeave) {
this.amazonClientProvider = amazonClientProvider
this.accountCredentialsRepository = accountCredentialsRepository
this.credentialsRepository = credentialsRepository
this.pollIntervalMillis = pollIntervalMillis
this.timeoutMillis = timeoutMills
this.daysToLeave = daysToLeave
Expand Down Expand Up @@ -120,7 +118,7 @@ class CleanupAlarmsAgent implements RunnableAgent, CustomScheduledAgent {
}

private Set<NetflixAmazonCredentials> getAccounts() {
ProviderUtils.buildThreadSafeSetOfAccounts(accountCredentialsRepository, NetflixAmazonCredentials, AmazonCloudProvider.ID)
return credentialsRepository.getAll()
}

private static Set<String> getAttachedAlarms(AmazonAutoScaling autoScaling) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import com.netflix.spinnaker.clouddriver.cache.CustomScheduledAgent
import com.netflix.spinnaker.clouddriver.aws.deploy.ops.DetachInstancesAtomicOperation
import com.netflix.spinnaker.clouddriver.aws.provider.AwsCleanupProvider
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsRepository
import com.netflix.spinnaker.clouddriver.security.ProviderUtils
import com.netflix.spinnaker.credentials.CredentialsRepository
import groovy.util.logging.Slf4j

import java.util.concurrent.TimeUnit
Expand All @@ -40,17 +40,17 @@ class CleanupDetachedInstancesAgent implements RunnableAgent, CustomScheduledAge
public static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20)

final AmazonClientProvider amazonClientProvider
final AccountCredentialsRepository accountCredentialsRepository
final CredentialsRepository<NetflixAmazonCredentials> accountCredentialsRepository
final long pollIntervalMillis
final long timeoutMillis

CleanupDetachedInstancesAgent(AmazonClientProvider amazonClientProvider,
AccountCredentialsRepository accountCredentialsRepository) {
CredentialsRepository<NetflixAmazonCredentials> accountCredentialsRepository) {
this(amazonClientProvider, accountCredentialsRepository, DEFAULT_POLL_INTERVAL_MILLIS, DEFAULT_TIMEOUT_MILLIS)
}

CleanupDetachedInstancesAgent(AmazonClientProvider amazonClientProvider,
AccountCredentialsRepository accountCredentialsRepository,
CredentialsRepository<NetflixAmazonCredentials> accountCredentialsRepository,
long pollIntervalMillis,
long timeoutMills) {
this.amazonClientProvider = amazonClientProvider
Expand Down Expand Up @@ -108,7 +108,7 @@ class CleanupDetachedInstancesAgent implements RunnableAgent, CustomScheduledAge
}

private Set<NetflixAmazonCredentials> getAccounts() {
ProviderUtils.buildThreadSafeSetOfAccounts(accountCredentialsRepository, NetflixAmazonCredentials, AmazonCloudProvider.ID)
return accountCredentialsRepository.getAll()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.amazonaws.services.autoscaling.model.Activity
import com.amazonaws.services.autoscaling.model.DescribeScalingActivitiesRequest
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import com.netflix.spinnaker.credentials.CredentialsRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
Expand All @@ -31,7 +31,7 @@ import org.springframework.web.bind.annotation.*
class AmazonClusterController {

@Autowired
AccountCredentialsProvider accountCredentialsProvider
CredentialsRepository<NetflixAmazonCredentials> credentialsRepository

@Autowired
AmazonClientProvider amazonClientProvider
Expand All @@ -40,8 +40,8 @@ class AmazonClusterController {

@RequestMapping(value = "/scalingActivities", method = RequestMethod.GET)
ResponseEntity getScalingActivities(@PathVariable String account, @PathVariable String serverGroupName, @RequestParam(value = "region", required = true) String region) {
def credentials = accountCredentialsProvider.getCredentials(account)
if (!(credentials instanceof NetflixAmazonCredentials)) {
def credentials = credentialsRepository.getOne(account)
if (credentials == null) {
return new ResponseEntity([message: "bad credentials"], HttpStatus.BAD_REQUEST)
}
def autoScaling = amazonClientProvider.getAutoScaling(credentials, region)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import com.netflix.spinnaker.clouddriver.deploy.DeployDescription
import com.netflix.spinnaker.clouddriver.deploy.DeployHandler
import com.netflix.spinnaker.clouddriver.deploy.DeploymentResult
import com.netflix.spinnaker.clouddriver.orchestration.events.CreateServerGroupEvent
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsRepository
import com.netflix.spinnaker.credentials.CredentialsRepository
import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
Expand Down Expand Up @@ -74,7 +74,7 @@ class BasicAmazonDeployHandler implements DeployHandler<BasicAmazonDeployDescrip
}

private final RegionScopedProviderFactory regionScopedProviderFactory
private final AccountCredentialsRepository accountCredentialsRepository
private final CredentialsRepository<NetflixAmazonCredentials> accountCredentialsRepository
private final AwsConfiguration.AmazonServerGroupProvider amazonServerGroupProvider
private final AwsConfiguration.DeployDefaults deployDefaults
private final ScalingPolicyCopier scalingPolicyCopier
Expand All @@ -84,7 +84,7 @@ class BasicAmazonDeployHandler implements DeployHandler<BasicAmazonDeployDescrip
private List<CreateServerGroupEvent> deployEvents = []

BasicAmazonDeployHandler(RegionScopedProviderFactory regionScopedProviderFactory,
AccountCredentialsRepository accountCredentialsRepository,
CredentialsRepository<NetflixAmazonCredentials> accountCredentialsRepository,
AwsConfiguration.AmazonServerGroupProvider amazonServerGroupProvider,
AwsConfiguration.DeployDefaults deployDefaults,
ScalingPolicyCopier scalingPolicyCopier,
Expand Down Expand Up @@ -246,8 +246,8 @@ class BasicAmazonDeployHandler implements DeployHandler<BasicAmazonDeployDescrip
validateInstanceType(ami, description.instanceType)

def account = accountCredentialsRepository.getOne(description.credentials.name)
if (!(account instanceof NetflixAmazonCredentials)) {
throw new IllegalArgumentException("Unsupported account type ${account.class.simpleName} for this operation")
if (account == null) {
throw new IllegalArgumentException("Account with name ${description.credentials.name} could not be found.")
}

if (description.useAmiBlockDeviceMappings) {
Expand Down Expand Up @@ -543,7 +543,7 @@ class BasicAmazonDeployHandler implements DeployHandler<BasicAmazonDeployDescrip
BasicAmazonDeployDescription.Source source) {
if (source.account && source.region && source.asgName) {
def sourceRegion = source.region
def sourceAsgCredentials = accountCredentialsRepository.getOne(source.account) as NetflixAmazonCredentials
def sourceAsgCredentials = accountCredentialsRepository.getOne(source.account)
def regionScopedProvider = regionScopedProviderFactory.forRegion(sourceAsgCredentials, sourceRegion)

def sourceAsgs = regionScopedProvider.autoScaling.describeAutoScalingGroups(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ package com.netflix.spinnaker.clouddriver.aws.deploy.ops

import com.amazonaws.services.ec2.AmazonEC2
import com.amazonaws.services.ec2.model.*
import com.netflix.spinnaker.clouddriver.aws.deploy.AmiIdResolver
import com.netflix.spinnaker.clouddriver.aws.deploy.ResolvedAmiResult
import com.netflix.spinnaker.clouddriver.aws.deploy.description.AllowLaunchDescription
import com.netflix.spinnaker.clouddriver.aws.model.AwsResultsRetriever
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials
import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import com.netflix.spinnaker.clouddriver.data.task.Task
import com.netflix.spinnaker.clouddriver.data.task.TaskRepository
import com.netflix.spinnaker.clouddriver.helpers.OperationPoller
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import com.netflix.spinnaker.clouddriver.aws.deploy.AmiIdResolver
import com.netflix.spinnaker.clouddriver.aws.deploy.ResolvedAmiResult
import com.netflix.spinnaker.clouddriver.aws.deploy.description.AllowLaunchDescription
import com.netflix.spinnaker.clouddriver.aws.model.AwsResultsRetriever
import com.netflix.spinnaker.credentials.CredentialsRepository
import com.netflix.spinnaker.kork.core.RetrySupport
import groovy.transform.Canonical
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -51,14 +51,14 @@ class AllowLaunchAtomicOperation implements AtomicOperation<ResolvedAmiResult> {
AmazonClientProvider amazonClientProvider

@Autowired
AccountCredentialsProvider accountCredentialsProvider
CredentialsRepository<NetflixAmazonCredentials> credentialsRepository

@Override
ResolvedAmiResult operate(List priorOutputs) {
task.updateStatus BASE_PHASE, "Initializing Allow Launch Operation..."

def sourceCredentials = description.credentials
def targetCredentials = accountCredentialsProvider.getCredentials(description.targetAccount) as NetflixAmazonCredentials
def targetCredentials = credentialsRepository.getOne(description.targetAccount)
def sourceAmazonEC2 = amazonClientProvider.getAmazonEC2(description.credentials, description.region, true)
def targetAmazonEC2 = amazonClientProvider.getAmazonEC2(targetCredentials, description.region, true)

Expand All @@ -83,10 +83,9 @@ class AllowLaunchAtomicOperation implements AtomicOperation<ResolvedAmiResult> {
// Spinnaker, switch to using that for modifying the image
if (resolvedAmi.ownerId != sourceCredentials.accountId) {
if (resolvedAmi.getRegion()) {
ownerCredentials = accountCredentialsProvider.all.find { accountCredentials ->
accountCredentials instanceof NetflixAmazonCredentials &&
ownerCredentials = credentialsRepository.getAll().find { accountCredentials ->
((AmazonCredentials) accountCredentials).accountId == resolvedAmi.ownerId
} as NetflixAmazonCredentials
}
}
if (ownerCredentials) {
ownerAmazonEC2 = amazonClientProvider.getAmazonEC2(ownerCredentials, description.region, true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,29 @@
*/

package com.netflix.spinnaker.clouddriver.aws.deploy.ops

import com.amazonaws.services.autoscaling.model.AutoScalingGroup
import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsRequest
import com.amazonaws.services.ec2.model.DescribeSubnetsRequest
import com.amazonaws.services.ec2.model.LaunchTemplateVersion
import com.amazonaws.services.elasticloadbalancingv2.model.DescribeTargetGroupsRequest
import com.netflix.frigga.Names
import com.netflix.frigga.autoscaling.AutoScalingGroupNameBuilder
import com.netflix.spinnaker.clouddriver.aws.deploy.description.BasicAmazonDeployDescription
import com.netflix.spinnaker.clouddriver.aws.deploy.handlers.BasicAmazonDeployHandler
import com.netflix.spinnaker.clouddriver.aws.deploy.userdata.LocalFileUserDataProperties
import com.netflix.spinnaker.clouddriver.aws.deploy.validators.BasicAmazonDeployDescriptionValidator
import com.netflix.spinnaker.clouddriver.aws.model.SubnetData
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import com.netflix.spinnaker.clouddriver.aws.services.RegionScopedProviderFactory
import com.netflix.spinnaker.clouddriver.data.task.Task
import com.netflix.spinnaker.clouddriver.data.task.TaskRepository
import com.netflix.spinnaker.clouddriver.deploy.DeploymentResult
import com.netflix.spinnaker.clouddriver.deploy.DescriptionValidationErrors
import com.netflix.spinnaker.clouddriver.deploy.DescriptionValidationException
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import com.netflix.spinnaker.clouddriver.aws.deploy.description.BasicAmazonDeployDescription
import com.netflix.spinnaker.clouddriver.aws.deploy.handlers.BasicAmazonDeployHandler
import com.netflix.spinnaker.clouddriver.aws.model.SubnetData
import com.netflix.spinnaker.clouddriver.aws.services.RegionScopedProviderFactory
import com.netflix.spinnaker.credentials.CredentialsRepository
import org.springframework.beans.factory.annotation.Autowired

class CopyLastAsgAtomicOperation implements AtomicOperation<DeploymentResult> {
Expand All @@ -56,7 +57,7 @@ class CopyLastAsgAtomicOperation implements AtomicOperation<DeploymentResult> {
AmazonClientProvider amazonClientProvider

@Autowired
AccountCredentialsProvider accountCredentialsProvider
CredentialsRepository<NetflixAmazonCredentials> credentialsRepository

@Autowired
RegionScopedProviderFactory regionScopedProviderFactory
Expand Down Expand Up @@ -90,7 +91,7 @@ class CopyLastAsgAtomicOperation implements AtomicOperation<DeploymentResult> {
def sourceAsgCredentials
if (description.source.account && description.source.region && description.source.asgName) {
sourceRegion = description.source.region
sourceAsgCredentials = accountCredentialsProvider.getCredentials(description.source.account) as NetflixAmazonCredentials
sourceAsgCredentials = credentialsRepository.getOne(description.source.account)
def sourceAutoScaling = amazonClientProvider.getAutoScaling(sourceAsgCredentials, sourceRegion, true)
def request = new DescribeAutoScalingGroupsRequest(autoScalingGroupNames: [description.source.asgName])
List<AutoScalingGroup> ancestorAsgs = sourceAutoScaling.describeAutoScalingGroups(request).autoScalingGroups
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import com.netflix.spinnaker.clouddriver.saga.SagaCommand;
import com.netflix.spinnaker.clouddriver.saga.flow.SagaAction;
import com.netflix.spinnaker.clouddriver.saga.models.Saga;
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsRepository;
import com.netflix.spinnaker.credentials.CredentialsRepository;
import java.util.Collections;
import javax.annotation.Nonnull;
import lombok.Builder;
Expand All @@ -46,12 +46,12 @@
public class ModifyServerGroupLaunchTemplate
implements SagaAction<ModifyServerGroupLaunchTemplate.ModifyServerGroupLaunchTemplateCommand> {
private final BlockDeviceConfig blockDeviceConfig;
private final AccountCredentialsRepository credentialsRepository;
private final CredentialsRepository<NetflixAmazonCredentials> credentialsRepository;
private final RegionScopedProviderFactory regionScopedProviderFactory;

public ModifyServerGroupLaunchTemplate(
BlockDeviceConfig blockDeviceConfig,
AccountCredentialsRepository credentialsRepository,
CredentialsRepository<NetflixAmazonCredentials> credentialsRepository,
RegionScopedProviderFactory regionScopedProviderFactory) {
this.blockDeviceConfig = blockDeviceConfig;
this.credentialsRepository = credentialsRepository;
Expand Down
Loading

0 comments on commit 80e07fe

Please sign in to comment.