├── CODEOWNERS ├── docs ├── delete.png ├── marking.png ├── swabbie-work-items.png └── swabbie-services-config.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── dokka.gradle ├── license.gradle ├── junit5.gradle └── kotlin.gradle ├── .editorconfig ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── release_info.sh ├── swabbie-web ├── src │ ├── logback-test.xml │ └── main │ │ ├── resources │ │ └── logback-defaults.xml │ │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── swabbie │ │ ├── controllers │ │ ├── ControllerUtils.kt │ │ └── AdminController.kt │ │ └── Main.kt ├── config │ └── swabbie.yml └── swabbie-web.gradle ├── swabbie-test ├── swabbie-test.gradle └── src │ └── main │ └── kotlin │ └── com │ └── netflix │ └── spinnaker │ └── swabbie │ └── test │ ├── NoopCacheStatus.kt │ ├── InMemoryNotificationQueue.kt │ ├── InMemoryWorkQueue.kt │ ├── TestResource.kt │ └── WorkConfigurationTestHelper.kt ├── Dockerfile.compile ├── swabbie-core ├── src │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ ├── swabbie │ │ │ ├── model │ │ │ │ ├── EddaEndpoint.kt │ │ │ │ ├── SwabbieNamespace.kt │ │ │ │ ├── OnDemandMarkData.kt │ │ │ │ ├── Application.kt │ │ │ │ ├── Rule.kt │ │ │ │ ├── AlwaysCleanRule.kt │ │ │ │ └── Account.kt │ │ │ ├── CachedViewProvider.kt │ │ │ ├── notifications │ │ │ │ ├── NotificationTask.kt │ │ │ │ ├── NotificationQueue.kt │ │ │ │ └── Notifier.kt │ │ │ ├── AccountProvider.kt │ │ │ ├── EndpointProvider.kt │ │ │ ├── exclusions │ │ │ │ ├── ExclusionsSupplier.kt │ │ │ │ ├── LiteralExclusionPolicy.kt │ │ │ │ ├── AccountExclusionPolicy.kt │ │ │ │ ├── NaiveExclusionPolicy.kt │ │ │ │ └── AllowListExclusionPolicy.kt │ │ │ ├── tagging │ │ │ │ ├── TaggingService.kt │ │ │ │ ├── ResourceTagger.kt │ │ │ │ ├── TemporalTags.kt │ │ │ │ └── EntityTag.kt │ │ │ ├── exception │ │ │ │ ├── SwabbieException.kt │ │ │ │ ├── CacheSizeException.kt │ │ │ │ └── StaleCacheException.kt │ │ │ ├── repository │ │ │ │ ├── ResourceStateRepository.kt │ │ │ │ ├── UsedResourceRepository.kt │ │ │ │ ├── ResourceTrackingRepository.kt │ │ │ │ ├── TaskTrackingRepository.kt │ │ │ │ └── ResourceUseTrackingRepository.kt │ │ │ ├── work │ │ │ │ └── WorkQueue.kt │ │ │ ├── utils │ │ │ │ └── ApplicationUtils.kt │ │ │ ├── events │ │ │ │ ├── ResourceTrackingManager.kt │ │ │ │ └── Event.kt │ │ │ ├── CacheStatus.kt │ │ │ ├── Cacheable.kt │ │ │ └── rules │ │ │ │ └── Rules.kt │ │ │ └── config │ │ │ ├── LockingConfigurationProperties.kt │ │ │ └── TestingConfiguration.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── swabbie │ │ ├── ScheduleTest.kt │ │ ├── rules │ │ └── AgeRuleTest.kt │ │ ├── events │ │ └── ResourceTrackingManagerTest.kt │ │ ├── model │ │ └── WorkConfigurationTest.kt │ │ └── exclusions │ │ └── LiteralExclusionPolicyTest.kt └── swabbie-core.gradle ├── .detekt.yml ├── Dockerfile.ubuntu ├── Dockerfile.slim ├── swabbie-aws ├── src │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── swabbie │ │ │ └── aws │ │ │ ├── edda │ │ │ ├── providers │ │ │ │ └── EddaEndpointProvider.kt │ │ │ ├── EddaEndpointsService.kt │ │ │ └── caches │ │ │ │ └── EddaEndpointCache.kt │ │ │ ├── Parameters.kt │ │ │ ├── exclusions │ │ │ ├── TemporalTagExclusionSupplier.kt │ │ │ └── AmazonTagExclusionPolicy.kt │ │ │ ├── loadbalancers │ │ │ ├── AmazonElasticLoadBalancer.kt │ │ │ └── Rules.kt │ │ │ ├── AmazonTagOwnerResolutionStrategy.kt │ │ │ ├── securitygroups │ │ │ └── AmazonSecurityGroup.kt │ │ │ ├── instances │ │ │ └── AmazonInstance.kt │ │ │ ├── launchtemplates │ │ │ ├── AmazonLaunchTemplate.kt │ │ │ └── AmazonLaunchTemplateVersion.kt │ │ │ ├── images │ │ │ ├── AmazonImage.kt │ │ │ └── Rules.kt │ │ │ ├── launchconfigurations │ │ │ └── LaunchConfiguration.kt │ │ │ ├── caches │ │ │ ├── AmazonImagesUsedByInstancesInMemoryCache.kt │ │ │ ├── AmazonLaunchConfigurationCache.kt │ │ │ ├── AmazonLaunchTemplateVersionCache.kt │ │ │ └── ImagesUsedByInstancesProvider.kt │ │ │ ├── ExpiredResourceRule.kt │ │ │ ├── snapshots │ │ │ ├── SnapshotRules.kt │ │ │ └── AmazonSnapshot.kt │ │ │ └── model │ │ │ └── AmazonResource.kt │ └── test │ │ └── java │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── swabbie │ │ └── aws │ │ ├── autoscalinggroups │ │ ├── ZeroLoadBalancerRuleTest.kt │ │ ├── ZeroInstanceRuleTest.kt │ │ ├── DisabledLoadBalancerRuleTest.kt │ │ └── ZeroInstanceDisabledServerGroupRuleTest.kt │ │ ├── AmazonTagOwnerResolutionStrategyTest.kt │ │ └── ExpiredResourceRuleTest.kt └── swabbie-aws.gradle ├── gradle.properties ├── swabbie-front50 ├── src │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ ├── swabbie │ │ │ └── front50 │ │ │ │ ├── Front50Service.kt │ │ │ │ ├── Front50ApplicationCache.kt │ │ │ │ └── Front50ApplicationExclusionPolicy.kt │ │ │ └── config │ │ │ └── Front50Configuration.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── swabbie │ │ └── front50 │ │ └── ApplicationResourceOwnerResolutionStrategyTest.kt └── swabbie-front50.gradle ├── swabbie-bom └── swabbie-bom.gradle ├── swabbie-clouddriver ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ ├── swabbie │ │ └── clouddriver │ │ │ ├── CloudDriverService.kt │ │ │ └── ClouddriverAccountProvider.kt │ │ └── config │ │ └── ClouddriverConfiguration.kt └── swabbie-clouddriver.gradle ├── swabbie-retrofit └── swabbie-retrofit.gradle ├── swabbie-redis ├── swabbie-redis.gradle └── src │ └── main │ └── kotlin │ └── com │ └── netflix │ └── spinnaker │ ├── config │ └── RedisConfiguration.kt │ └── swabbie │ └── redis │ ├── RedisClientDelegateSupport.kt │ ├── RedisNotificationQueue.kt │ ├── RedisUsedResourceRepository.kt │ ├── LockingConfiguration.kt │ └── RedisWorkQueue.kt ├── swabbie-echo ├── swabbie-echo.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ ├── swabbie │ │ └── echo │ │ │ ├── EchoService.kt │ │ │ └── EchoNotifier.kt │ │ └── config │ │ └── EchoConfiguration.kt │ └── test │ └── kotlin │ └── com │ └── netflix │ └── spinnaker │ └── swabbie │ └── echo │ └── EchoNotifierTest.kt ├── swabbie-orca ├── swabbie-orca.gradle └── src │ └── main │ └── kotlin │ └── com │ └── netflix │ └── spinnaker │ └── config │ └── OrcaConfiguration.kt ├── settings.gradle └── .mergify.yml /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jeyrschabu @aravindmd 2 | -------------------------------------------------------------------------------- /docs/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/swabbie/HEAD/docs/delete.png -------------------------------------------------------------------------------- /docs/marking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/swabbie/HEAD/docs/marking.png -------------------------------------------------------------------------------- /docs/swabbie-work-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/swabbie/HEAD/docs/swabbie-work-items.png -------------------------------------------------------------------------------- /docs/swabbie-services-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/swabbie/HEAD/docs/swabbie-services-config.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/swabbie/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 -------------------------------------------------------------------------------- /gradle/dokka.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.dokka" 2 | 3 | dokkaHtml { 4 | dokkaSourceSets { 5 | configureEach { 6 | jdkVersion.set(17) 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /classes/ 2 | /.gradle/ 3 | /.idea/ 4 | build/ 5 | /.gradletasknamecache 6 | *.iml 7 | *.ipr 8 | *.iws 9 | out/ 10 | .DS_Store 11 | /json/ 12 | *.rdb 13 | /.shelf/ 14 | **/*/out/* -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /swabbie-web/src/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /swabbie-test/swabbie-test.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation project(":swabbie-core") 3 | implementation "com.natpryce:hamkrest" 4 | implementation "io.strikt:strikt-core" 5 | implementation "org.slf4j:jcl-over-slf4j" 6 | implementation "com.fasterxml.jackson.core:jackson-annotations" 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile.compile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | RUN apt-get update && apt-get install -y \ 3 | openjdk-17-jdk \ 4 | && rm -rf /var/lib/apt/lists/* 5 | LABEL maintainer="sig-platform@spinnaker.io" 6 | ENV GRADLE_USER_HOME /workspace/.gradle 7 | ENV GRADLE_OPTS -Xmx4g 8 | CMD ./gradlew --no-daemon swabbie-web:installDist -x test 9 | -------------------------------------------------------------------------------- /gradle/license.gradle: -------------------------------------------------------------------------------- 1 | def licenseExtension = project.extensions.findByName("license") 2 | if (licenseExtension != null) { 3 | licenseExtension.exclude "**/*.json" 4 | licenseExtension.exclude "**/*.md" 5 | licenseExtension.exclude "**/*.yml" 6 | licenseExtension.mapping { 7 | java = "SLASHSTAR_STYLE" 8 | kt = "SLASHSTAR_STYLE" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/EddaEndpoint.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie.model 2 | 3 | import com.netflix.spinnaker.swabbie.Cacheable 4 | 5 | data class EddaEndpoint( 6 | val region: String, 7 | val accountId: String, 8 | val environment: String, 9 | val endpoint: String, 10 | override val name: String 11 | ) : Cacheable 12 | -------------------------------------------------------------------------------- /swabbie-web/config/swabbie.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8092 3 | 4 | front50: 5 | baseUrl: ${services.front50.baseUrl:http://localhost:8080} 6 | 7 | orca: 8 | baseUrl: ${services.orca.baseUrl:http://localhost:8083} 9 | 10 | clouddriver: 11 | baseUrl: ${services.clouddriver.baseUrl:http://localhost:7002} 12 | 13 | echo: 14 | baseUrl: ${services.echo.baseUrl:http://localhost:8089} 15 | -------------------------------------------------------------------------------- /.detekt.yml: -------------------------------------------------------------------------------- 1 | complexity: 2 | TooManyFunctions: 3 | active: false 4 | 5 | style: 6 | ThrowsCount: 7 | active: false 8 | MaxLineLength: 9 | active: false 10 | ReturnCount: 11 | active: false 12 | 13 | performance: 14 | SpreadOperator: 15 | active: false 16 | 17 | comments: 18 | UndocumentedPublicClass: 19 | active: true 20 | UndocumentedPublicFunction: 21 | active: true 22 | UndocumentedPublicProperty: 23 | active: true 24 | -------------------------------------------------------------------------------- /Dockerfile.ubuntu: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | LABEL maintainer="sig-platform@spinnaker.io" 3 | RUN apt-get update && apt-get -y install curl openjdk-17-jre-headless wget 4 | RUN adduser --system --uid 10111 --group spinnaker 5 | COPY swabbie-web/build/install/swabbie /opt/swabbie 6 | RUN mkdir -p /opt/swabbie/plugins && chown -R spinnaker:nogroup /opt/swabbie/plugins 7 | USER spinnaker 8 | HEALTHCHECK CMD curl --fail http://localhost:8092/health 9 | CMD ["/opt/swabbie/bin/swabbie"] 10 | -------------------------------------------------------------------------------- /Dockerfile.slim: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | LABEL maintainer="sig-platform@spinnaker.io" 3 | RUN apk --no-cache add --update bash curl openjdk17-jre 4 | RUN addgroup -S -g 10111 spinnaker 5 | RUN adduser -S -G spinnaker -u 10111 spinnaker 6 | COPY swabbie-web/build/install/swabbie /opt/swabbie 7 | RUN mkdir -p /opt/swabbie/plugins && chown -R spinnaker:nogroup /opt/swabbie/plugins 8 | USER spinnaker 9 | HEALTHCHECK CMD curl --fail http://localhost:8092/health 10 | CMD ["/opt/swabbie/bin/swabbie"] 11 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/edda/providers/EddaEndpointProvider.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie.aws.edda.providers 2 | 3 | import com.netflix.spinnaker.swabbie.EndpointProvider 4 | import com.netflix.spinnaker.swabbie.InMemoryCache 5 | import com.netflix.spinnaker.swabbie.model.EddaEndpoint 6 | 7 | class EddaEndpointProvider( 8 | private val endpointCache: InMemoryCache 9 | ) : EndpointProvider { 10 | override fun getEndpoints(): Set { 11 | return endpointCache.get() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/SwabbieNamespace.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie.model 2 | 3 | data class SwabbieNamespace( 4 | val cloudProvider: String, 5 | val accountName: String, 6 | val region: String, 7 | val resourceType: String 8 | ) { 9 | companion object { 10 | fun namespaceParser(namespace: String): SwabbieNamespace { 11 | namespace.split(":").let { 12 | return SwabbieNamespace(it[0], it[1], it[2], it[3]) 13 | } 14 | } 15 | } 16 | 17 | override fun toString(): String { 18 | return "$cloudProvider:$accountName:$region:$resourceType" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/OnDemandMarkData.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie.model 2 | 3 | import com.netflix.spinnaker.swabbie.notifications.Notifier 4 | import com.netflix.spinnaker.swabbie.repository.LastSeenInfo 5 | 6 | data class OnDemandMarkData( 7 | var projectedDeletionStamp: Long, 8 | var markTs: Long? = null, 9 | var resourceOwner: String = "swabbie@spinnaker.io", 10 | var notificationInfo: NotificationInfo? = NotificationInfo( 11 | recipient = resourceOwner, 12 | notificationType = Notifier.NotificationType.EMAIL.name 13 | ), 14 | var lastSeenInfo: LastSeenInfo? = null, 15 | val resourceId: String? = null 16 | ) 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | korkVersion=7.254.0 2 | kotlinVersion=1.6.21 3 | org.gradle.parallel=true 4 | spinnakerGradleVersion=8.32.1 5 | targetJava17=true 6 | 7 | org.gradle.jvmargs=-Xmx2g 8 | # To enable a composite reference to a project, set the 9 | # project property `'Composite=true'`. 10 | # 11 | # This can be done either as 12 | # * a command line flag, e.g. `-PkorkComposite=true` 13 | # * a project property via gradle.properties 14 | # * a global project property via ~/.gradle/gradle.properties 15 | # 16 | # The composite project must checked out in a sibling directory 17 | # to this project, matching the name of the project 18 | # e.g. '../kork' 19 | # 20 | #korkComposite=true 21 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/CachedViewProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie 18 | 19 | interface CachedViewProvider { 20 | fun load(): T 21 | } 22 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/notifications/NotificationTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.notifications 18 | 19 | data class NotificationTask( 20 | val resourceType: String, 21 | val namespace: String 22 | ) 23 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/AccountProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie 18 | 19 | import com.netflix.spinnaker.swabbie.model.Account 20 | 21 | interface AccountProvider { 22 | fun getAccounts(): Set 23 | } 24 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/Parameters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws 18 | 19 | data class Parameters( 20 | val region: String, 21 | val account: String, 22 | val environment: String, 23 | val id: String = "" 24 | ) 25 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/EndpointProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie 18 | 19 | import com.netflix.spinnaker.swabbie.model.EddaEndpoint 20 | 21 | interface EndpointProvider { 22 | fun getEndpoints(): Set 23 | } 24 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exclusions/ExclusionsSupplier.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exclusions 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | 21 | interface ExclusionsSupplier { 22 | fun get(): List 23 | } 24 | -------------------------------------------------------------------------------- /swabbie-test/src/main/kotlin/com/netflix/spinnaker/swabbie/test/NoopCacheStatus.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.test 20 | 21 | import com.netflix.spinnaker.swabbie.CacheStatus 22 | 23 | class NoopCacheStatus : CacheStatus { 24 | override fun cachesLoaded() = true 25 | } 26 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/tagging/TaggingService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.tagging 18 | 19 | interface TaggingService { 20 | fun entityTag(tagRequest: TagRequest) 21 | fun removeEntityTag(tagRequest: TagRequest) 22 | } 23 | 24 | interface TagRequest 25 | interface Tag 26 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exception/SwabbieException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exception 18 | 19 | class SwabbieException : RuntimeException { 20 | constructor(message: String) : super(message) 21 | constructor(message: String, cause: Throwable) : super(message, cause) 22 | } 23 | -------------------------------------------------------------------------------- /swabbie-front50/src/main/kotlin/com/netflix/spinnaker/swabbie/front50/Front50Service.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.front50 18 | 19 | import com.netflix.spinnaker.swabbie.model.Application 20 | import retrofit.http.GET 21 | 22 | interface Front50Service { 23 | @GET("/v2/applications?restricted=false") 24 | fun getApplications(): Set 25 | } 26 | -------------------------------------------------------------------------------- /swabbie-bom/swabbie-bom.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: "java-platform" 18 | 19 | javaPlatform { 20 | allowDependencies() 21 | } 22 | 23 | dependencies { 24 | api(platform("io.spinnaker.kork:kork-bom:$korkVersion")) 25 | 26 | constraints { 27 | rootProject 28 | .subprojects 29 | .findAll { it != project } 30 | .each { api(project(it.path)) } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swabbie-clouddriver/src/main/kotlin/com/netflix/spinnaker/swabbie/clouddriver/CloudDriverService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.clouddriver 18 | 19 | import com.netflix.spinnaker.swabbie.model.SpinnakerAccount 20 | import retrofit.http.GET 21 | 22 | interface CloudDriverService { 23 | @GET("/credentials?expand=true") 24 | fun getAccounts(): Set 25 | } 26 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exception/CacheSizeException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.exception 20 | 21 | class CacheSizeException : RuntimeException { 22 | constructor(message: String) : super(message) 23 | constructor(message: String, cause: Throwable) : super(message, cause) 24 | } 25 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exception/StaleCacheException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.exception 20 | 21 | class StaleCacheException : RuntimeException { 22 | constructor(message: String) : super(message) 23 | constructor(message: String, cause: Throwable) : super(message, cause) 24 | } 25 | -------------------------------------------------------------------------------- /swabbie-retrofit/swabbie-retrofit.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" 19 | implementation "com.squareup.retrofit:retrofit" 20 | implementation "com.squareup.retrofit:converter-jackson" 21 | implementation "io.spinnaker.kork:kork-web" 22 | implementation "io.reactivex:rxjava" 23 | implementation project(":swabbie-core") 24 | } 25 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/notifications/NotificationQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.notifications 18 | 19 | /** 20 | * A queue for storing notification tasks 21 | */ 22 | interface NotificationQueue { 23 | fun add(notificationTask: NotificationTask) 24 | fun popAll(): List 25 | fun isEmpty(): Boolean 26 | fun size(): Int 27 | } 28 | -------------------------------------------------------------------------------- /swabbie-redis/swabbie-redis.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation project(":swabbie-core") 19 | 20 | implementation "redis.clients:jedis" 21 | implementation "io.spinnaker.kork:kork-jedis" 22 | implementation "io.spinnaker.kork:kork-test" 23 | implementation "com.fasterxml.jackson.module:jackson-module-kotlin" 24 | 25 | testImplementation "io.spinnaker.kork:kork-jedis-test" 26 | testImplementation project(":swabbie-test") 27 | testImplementation("io.strikt:strikt-core") 28 | } 29 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/config/LockingConfigurationProperties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.config 18 | 19 | import org.springframework.boot.context.properties.ConfigurationProperties 20 | 21 | @ConfigurationProperties("locking") 22 | open class LockingConfigurationProperties { 23 | var enabled: Boolean? = false 24 | var maximumLockDurationMillis: Long? = 60 * 60 * 1000L // 1hour 25 | var heartbeatRateMillis: Long = 6000 26 | var leaseDurationMillis: Long = 10000 27 | } 28 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/tagging/ResourceTagger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.tagging 18 | 19 | import com.netflix.spinnaker.swabbie.model.MarkedResource 20 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 21 | 22 | interface ResourceTagger { 23 | fun tag(markedResource: MarkedResource, workConfiguration: WorkConfiguration, description: String) 24 | fun unTag(markedResource: MarkedResource, workConfiguration: WorkConfiguration, description: String) 25 | } 26 | -------------------------------------------------------------------------------- /swabbie-echo/swabbie-echo.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation project(":swabbie-core") 19 | implementation project(":swabbie-retrofit") 20 | implementation "io.spinnaker.kork:kork-core" 21 | implementation "io.spinnaker.kork:kork-web" 22 | implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" 23 | implementation "com.squareup.retrofit:retrofit" 24 | implementation "com.squareup.retrofit:converter-jackson" 25 | 26 | testImplementation project(":swabbie-test") 27 | testImplementation "io.strikt:strikt-core" 28 | } 29 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/repository/ResourceStateRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.repository 18 | 19 | import com.netflix.spinnaker.swabbie.model.ResourceState 20 | 21 | interface ResourceStateRepository { 22 | fun get(resourceId: String, namespace: String): ResourceState? 23 | fun upsert(resourceState: ResourceState): ResourceState 24 | fun getAll(): List 25 | fun getByStatus(status: String): List 26 | fun remove(resourceId: String, namespace: String) 27 | } 28 | -------------------------------------------------------------------------------- /swabbie-front50/src/main/kotlin/com/netflix/spinnaker/swabbie/front50/Front50ApplicationCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.front50 18 | 19 | import com.netflix.spinnaker.security.AuthenticatedRequest 20 | import com.netflix.spinnaker.swabbie.InMemoryCache 21 | import com.netflix.spinnaker.swabbie.model.Application 22 | import org.springframework.stereotype.Component 23 | 24 | @Component 25 | class Front50ApplicationCache(front50Service: Front50Service) : InMemoryCache({ AuthenticatedRequest.allowAnonymous(front50Service::getApplications) }) 26 | -------------------------------------------------------------------------------- /swabbie-front50/swabbie-front50.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation project(":swabbie-core") 19 | implementation project(":swabbie-retrofit") 20 | implementation "io.spinnaker.kork:kork-core" 21 | implementation "io.spinnaker.kork:kork-web" 22 | implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" 23 | implementation "com.squareup.retrofit:retrofit" 24 | implementation "com.squareup.retrofit:converter-jackson" 25 | 26 | testImplementation project(":swabbie-test") 27 | testImplementation "com.squareup.retrofit:retrofit-mock" 28 | } 29 | -------------------------------------------------------------------------------- /swabbie-orca/swabbie-orca.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation project(":swabbie-core") 19 | implementation project(":swabbie-retrofit") 20 | implementation "io.spinnaker.kork:kork-core" 21 | implementation "io.spinnaker.kork:kork-web" 22 | implementation "io.spinnaker.kork:kork-moniker" 23 | implementation "net.logstash.logback:logstash-logback-encoder" 24 | implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" 25 | implementation "com.squareup.retrofit:retrofit" 26 | implementation "com.squareup.retrofit:converter-jackson" 27 | } 28 | -------------------------------------------------------------------------------- /gradle/junit5.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | testImplementation "org.junit.platform:junit-platform-runner" 19 | testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" 20 | testImplementation "com.natpryce:hamkrest" 21 | testImplementation "org.junit.jupiter:junit-jupiter-api" 22 | testImplementation "org.junit.jupiter:junit-jupiter-params" 23 | 24 | testRuntimeOnly "org.junit.platform:junit-platform-launcher" 25 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" 26 | } 27 | 28 | test { 29 | useJUnitPlatform { 30 | includeEngines("junit-jupiter") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swabbie-redis/src/main/kotlin/com/netflix/spinnaker/config/RedisConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.config 18 | 19 | import com.netflix.spinnaker.kork.jedis.JedisClientConfiguration 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression 21 | import org.springframework.context.annotation.Configuration 22 | import org.springframework.context.annotation.Import 23 | 24 | @Configuration 25 | @ConditionalOnExpression("\${redis.enabled:false}") 26 | @Import(JedisClientConfiguration::class) 27 | open class RedisConfiguration 28 | 29 | const val REDIS_CHUNK_SIZE = 100 30 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/work/WorkQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.work 18 | 19 | import com.netflix.spinnaker.swabbie.model.WorkItem 20 | 21 | interface WorkQueue { 22 | /** 23 | * For initializing the queue with a list of [WorkItem] 24 | */ 25 | fun seed() 26 | fun pop(): WorkItem? 27 | fun push(workItem: WorkItem) 28 | fun isEmpty(): Boolean 29 | 30 | /** 31 | * Empties the queue 32 | */ 33 | fun clear() { 34 | var tmp = pop() 35 | while (tmp != null) { 36 | tmp = pop() 37 | } 38 | } 39 | 40 | fun size(): Int 41 | } 42 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exclusions/LiteralExclusionPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exclusions 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | import com.netflix.spinnaker.config.ExclusionType 21 | import org.springframework.stereotype.Component 22 | 23 | @Component 24 | class LiteralExclusionPolicy : ResourceExclusionPolicy { 25 | override fun getType(): ExclusionType = ExclusionType.Literal 26 | override fun apply(excludable: Excludable, exclusions: List): String? { 27 | return byPropertyMatchingResult(exclusions, excludable) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/Application.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.model 18 | 19 | import com.netflix.spinnaker.swabbie.Cacheable 20 | import com.netflix.spinnaker.swabbie.exclusions.Excludable 21 | 22 | data class Application( 23 | val email: String?, 24 | override val name: String 25 | ) : HasDetails(), Cacheable, Excludable { 26 | override val resourceId: String 27 | get() = name 28 | override val resourceType: String 29 | get() = "application" 30 | override val cloudProvider: String 31 | get() = "*" 32 | override val grouping: Grouping? 33 | get() = Grouping(name, GroupingType.APPLICATION) 34 | } 35 | -------------------------------------------------------------------------------- /swabbie-web/src/main/resources/logback-defaults.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} 23 | %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} [%X{X-SPINNAKER-USER}] %m%n 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/edda/EddaEndpointsService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.edda 18 | 19 | import retrofit.http.GET 20 | 21 | /** 22 | * This is a service for discovering which edda endpoints are available. 23 | */ 24 | interface EddaEndpointsService { 25 | @GET("/api/v1/edda/endpoints.json") 26 | fun getEddaEndpoints(): EddaEndpointsContainer 27 | 28 | data class EddaEndpointsContainer( 29 | val edda_endpoints: EddaEndpoints 30 | ) { 31 | data class EddaEndpoints( 32 | val by_account: List?, 33 | val by_cluster: List? 34 | ) 35 | 36 | fun get(): List = edda_endpoints.by_account ?: emptyList() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exclusions/AccountExclusionPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exclusions 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | import com.netflix.spinnaker.config.ExclusionType 21 | import com.netflix.spinnaker.swabbie.model.Account 22 | import org.springframework.stereotype.Component 23 | 24 | @Component 25 | class AccountExclusionPolicy : BasicExclusionPolicy { 26 | override fun getType(): ExclusionType = ExclusionType.Account 27 | override fun apply(excludable: Excludable, exclusions: List): String? { 28 | if (excludable is Account) { 29 | return byPropertyMatchingResult(exclusions, excludable) 30 | } 31 | 32 | return null 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/repository/UsedResourceRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.repository 20 | 21 | /** 22 | * Track a resource that is in use by another type of resource. 23 | * This repository provides a way to collect information about use from one resource and query that while processing 24 | * another resource. For example, AWS provides some one way mappings about use (image -> snapshot). This repository 25 | * is used to record if a snapshot is in use by an image. 26 | */ 27 | interface UsedResourceRepository { 28 | fun recordUse(resourceType: String, id: String, namespace: String) 29 | fun isUsed(resourceType: String, id: String, namespace: String): Boolean 30 | } 31 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/notifications/Notifier.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.notifications 18 | 19 | import com.netflix.spinnaker.config.NotificationConfiguration 20 | 21 | interface Notifier { 22 | fun notify( 23 | recipient: String, 24 | notificationContext: Map, 25 | notificationConfiguration: NotificationConfiguration 26 | ): NotificationResult 27 | 28 | enum class NotificationType { 29 | EMAIL, 30 | NONE 31 | } 32 | 33 | enum class NotificationSeverity { 34 | NORMAL, 35 | HIGH 36 | } 37 | 38 | data class NotificationResult( 39 | val recipient: String, 40 | val notificationType: NotificationType = NotificationType.NONE, 41 | val success: Boolean = false 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/config/TestingConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.config 20 | 21 | import com.netflix.spinnaker.swabbie.model.AlwaysCleanRule 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression 23 | import org.springframework.context.annotation.Bean 24 | import org.springframework.context.annotation.Configuration 25 | 26 | @Configuration 27 | @ConditionalOnExpression("\${swabbie.testing.enabled:false}") 28 | open class TestingConfiguration { 29 | 30 | @ConditionalOnExpression("\${swabbie.testing.always-clean-rule-config.enabled:false}") 31 | @Bean 32 | open fun alwaysCleanRule(swabbieProperties: SwabbieProperties) = 33 | AlwaysCleanRule(swabbieProperties) 34 | } 35 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | ['kork'].each { prj -> 18 | String propName = "${prj}Composite" 19 | String projectPath = "../$prj" 20 | if (settings.ext.has(propName) && Boolean.parseBoolean(settings.ext.get(propName) as String)) { 21 | includeBuild projectPath 22 | } 23 | } 24 | 25 | rootProject.name = 'swabbie' 26 | 27 | include 'swabbie-test', 28 | 'swabbie-core', 29 | 'swabbie-retrofit', 30 | 'swabbie-redis', 31 | 'swabbie-aws', 32 | 'swabbie-echo', 33 | 'swabbie-orca', 34 | 'swabbie-clouddriver', 35 | 'swabbie-front50', 36 | 'swabbie-web', 37 | 'swabbie-bom' 38 | 39 | def setBuildFile(project) { 40 | project.buildFileName = "${project.name}.gradle" 41 | project.children.each { 42 | setBuildFile(it) 43 | } 44 | } 45 | 46 | rootProject.children.each { 47 | setBuildFile it 48 | } 49 | -------------------------------------------------------------------------------- /swabbie-clouddriver/swabbie-clouddriver.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation project(":swabbie-retrofit") 19 | implementation project(":swabbie-core") 20 | implementation "io.spinnaker.kork:kork-core" 21 | implementation "io.spinnaker.kork:kork-web" 22 | implementation "com.fasterxml.jackson.core:jackson-annotations" 23 | implementation "com.fasterxml.jackson.core:jackson-core" 24 | implementation "com.fasterxml.jackson.core:jackson-databind" 25 | implementation "io.spinnaker.kork:kork-moniker" 26 | implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client" 27 | implementation "com.squareup.retrofit:retrofit" 28 | implementation "com.squareup.retrofit:converter-jackson" 29 | 30 | testImplementation project(":swabbie-test") 31 | testImplementation "com.squareup.retrofit:retrofit-mock" 32 | } 33 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/Rule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.model 18 | 19 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 20 | 21 | /** 22 | * A resource specific rule 23 | * If the rule finds the resource to be invalid, it will return a violation summary 24 | * Rules should be kept to simple logic and not perform any I/O operations 25 | */ 26 | interface Rule { 27 | fun applicableForType(clazz: Class): Boolean = false 28 | fun apply(resource: T, ruleDefinition: RuleDefinition? = null): Result 29 | fun name(): String = this.javaClass.simpleName 30 | } 31 | 32 | data class Result( 33 | val summary: Summary? 34 | ) 35 | 36 | data class Summary( 37 | val description: String, 38 | val ruleName: String 39 | ) 40 | -------------------------------------------------------------------------------- /gradle/kotlin.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: "kotlin" 18 | apply plugin: "io.gitlab.arturbosch.detekt" 19 | 20 | compileKotlin { 21 | kotlinOptions { 22 | languageVersion = "1.6" 23 | jvmTarget = "17" 24 | } 25 | } 26 | 27 | compileTestKotlin { 28 | kotlinOptions { 29 | languageVersion = "1.6" 30 | jvmTarget = "17" 31 | } 32 | } 33 | 34 | configurations.all { 35 | resolutionStrategy { 36 | eachDependency { details -> 37 | if (details.requested.group == "org.jetbrains.kotlin") { 38 | details.useVersion kotlinVersion 39 | } 40 | } 41 | } 42 | } 43 | 44 | detekt { 45 | parallel = false 46 | config = files("$rootDir/.detekt.yml") 47 | buildUponDefaultConfig = true 48 | ignoreFailures = true 49 | reports { 50 | xml { 51 | enabled = false 52 | } 53 | txt { 54 | enabled = false 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exclusions/NaiveExclusionPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exclusions 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | import com.netflix.spinnaker.config.ExclusionType 21 | import com.netflix.spinnaker.swabbie.model.NAIVE_EXCLUSION 22 | import com.netflix.spinnaker.swabbie.model.Resource 23 | import org.springframework.stereotype.Component 24 | 25 | @Component 26 | class NaiveExclusionPolicy : ResourceExclusionPolicy { 27 | override fun getType(): ExclusionType = ExclusionType.Naive 28 | override fun apply(excludable: Excludable, exclusions: List): String? { 29 | if (excludable is Resource) { 30 | (excludable.details[NAIVE_EXCLUSION] as? Boolean)?.let { 31 | if (it) { 32 | return "Naive exclusion of ${excludable.name}" 33 | } 34 | } 35 | } 36 | 37 | return null 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /swabbie-clouddriver/src/main/kotlin/com/netflix/spinnaker/swabbie/clouddriver/ClouddriverAccountProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.clouddriver 18 | 19 | import com.netflix.spinnaker.security.AuthenticatedRequest 20 | import com.netflix.spinnaker.swabbie.AccountProvider 21 | import com.netflix.spinnaker.swabbie.InMemoryCache 22 | import com.netflix.spinnaker.swabbie.model.Account 23 | import com.netflix.spinnaker.swabbie.model.SpinnakerAccount 24 | import org.springframework.stereotype.Component 25 | 26 | @Component 27 | class ClouddriverAccountProvider( 28 | private val accountCache: InMemoryCache 29 | ) : AccountProvider { 30 | override fun getAccounts(): Set = accountCache.get() 31 | } 32 | 33 | @Component 34 | class ClouddriverAccountCache( 35 | cloudDriverService: CloudDriverService 36 | ) : InMemoryCache({ AuthenticatedRequest.allowAnonymous(cloudDriverService::getAccounts) }) 37 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/repository/ResourceTrackingRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.repository 20 | 21 | import com.netflix.spinnaker.swabbie.model.MarkedResource 22 | 23 | interface ResourceTrackingRepository { 24 | fun upsert(markedResource: MarkedResource, deleteScore: Long = markedResource.projectedDeletionStamp) 25 | fun remove(markedResource: MarkedResource) 26 | 27 | fun getIdsOfMarkedResourcesToDelete(): Set 28 | fun getMarkedResourcesToDelete(): List 29 | fun getMarkedResources(): List 30 | 31 | fun getNumMarkedResources(): Long 32 | 33 | fun getDeleted(): List 34 | 35 | fun find(resourceId: String, namespace: String): MarkedResource? 36 | } 37 | 38 | data class DeleteInfo( 39 | val name: String, 40 | val resourceId: String, 41 | val namespace: String 42 | ) 43 | -------------------------------------------------------------------------------- /swabbie-echo/src/main/kotlin/com/netflix/spinnaker/swabbie/echo/EchoService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.echo 18 | 19 | import com.fasterxml.jackson.annotation.JsonInclude 20 | import com.netflix.spinnaker.swabbie.notifications.Notifier 21 | import retrofit.client.Response 22 | import retrofit.http.Body 23 | import retrofit.http.POST 24 | 25 | interface EchoService { 26 | @POST("/notifications") 27 | fun create(@Body notification: Notification): Response 28 | 29 | @JsonInclude(JsonInclude.Include.NON_NULL) 30 | data class Notification( 31 | val notificationType: Notifier.NotificationType, 32 | val to: Collection, 33 | val cc: Collection = listOf(), 34 | val severity: Notifier.NotificationSeverity, 35 | val source: Source, 36 | val templateGroup: String, 37 | val additionalContext: Map = mapOf() 38 | ) { 39 | 40 | data class Source(val application: String) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /swabbie-test/src/main/kotlin/com/netflix/spinnaker/swabbie/test/InMemoryNotificationQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.test 18 | 19 | import com.netflix.spinnaker.swabbie.notifications.NotificationQueue 20 | import com.netflix.spinnaker.swabbie.notifications.NotificationTask 21 | import java.util.concurrent.LinkedBlockingDeque 22 | 23 | // For local testing. Implemented as a mere list 24 | class InMemoryNotificationQueue : NotificationQueue { 25 | private val _q = LinkedBlockingDeque() 26 | override fun popAll(): List { 27 | val list = mutableListOf() 28 | while (!_q.isEmpty()) { 29 | list += _q.pop() 30 | } 31 | 32 | return list 33 | } 34 | 35 | override fun isEmpty(): Boolean { 36 | return _q.isEmpty() 37 | } 38 | 39 | override fun add(notificationTask: NotificationTask) { 40 | _q.add(notificationTask) 41 | } 42 | 43 | override fun size(): Int { 44 | return _q.size 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swabbie-core/swabbie-core.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation "org.slf4j:jcl-over-slf4j" 19 | implementation "io.spinnaker.kork:kork-core" 20 | implementation "io.spinnaker.kork:kork-web" 21 | implementation "io.spinnaker.kork:kork-moniker" 22 | implementation "net.logstash.logback:logstash-logback-encoder" 23 | implementation "com.squareup.retrofit:retrofit" 24 | implementation "com.fasterxml.jackson.core:jackson-annotations" 25 | implementation "com.fasterxml.jackson.core:jackson-core" 26 | implementation "com.fasterxml.jackson.core:jackson-databind" 27 | implementation "com.squareup.retrofit:converter-jackson" 28 | implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" 29 | implementation "com.fasterxml.jackson.module:jackson-module-kotlin" 30 | implementation "com.google.guava:guava" 31 | 32 | testImplementation project(":swabbie-test") 33 | testImplementation "io.spinnaker.kork:kork-test" 34 | testImplementation "io.strikt:strikt-core" 35 | } 36 | -------------------------------------------------------------------------------- /swabbie-web/src/main/kotlin/com/netflix/spinnaker/swabbie/controllers/ControllerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie.controllers 2 | 3 | import com.netflix.spinnaker.kork.web.exceptions.NotFoundException 4 | import com.netflix.spinnaker.swabbie.ResourceTypeHandler 5 | import com.netflix.spinnaker.swabbie.model.SwabbieNamespace 6 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 7 | import org.slf4j.LoggerFactory 8 | import org.springframework.stereotype.Component 9 | 10 | @Component 11 | class ControllerUtils( 12 | private val resourceTypeHandlers: List>, 13 | private val workConfigurations: List 14 | ) { 15 | 16 | private val log = LoggerFactory.getLogger(javaClass) 17 | 18 | init { 19 | log.info("Available resource type handlers are: $resourceTypeHandlers") 20 | } 21 | 22 | fun findWorkConfiguration(namespace: SwabbieNamespace): WorkConfiguration { 23 | return workConfigurations.find { workConfiguration -> 24 | workConfiguration.account.name == namespace.accountName && 25 | workConfiguration.cloudProvider == namespace.cloudProvider && 26 | workConfiguration.resourceType.equals(namespace.resourceType, true) && 27 | workConfiguration.location == namespace.region 28 | } ?: throw NotFoundException("No configuration found for $namespace") 29 | } 30 | 31 | fun findHandler(workConfiguration: WorkConfiguration): ResourceTypeHandler<*> { 32 | return resourceTypeHandlers.find { handler -> 33 | handler.handles(workConfiguration) 34 | } ?: throw NotFoundException("No handlers for ${workConfiguration.namespace}") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /swabbie-web/src/main/kotlin/com/netflix/spinnaker/swabbie/controllers/AdminController.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie.controllers 2 | 3 | import com.netflix.spinnaker.swabbie.model.SwabbieNamespace 4 | import org.slf4j.LoggerFactory 5 | import org.springframework.web.bind.annotation.PathVariable 6 | import org.springframework.web.bind.annotation.RequestMapping 7 | import org.springframework.web.bind.annotation.RequestMethod 8 | import org.springframework.web.bind.annotation.RequestParam 9 | import org.springframework.web.bind.annotation.RestController 10 | 11 | @RestController 12 | @RequestMapping("/admin") 13 | class AdminController( 14 | private val controllerUtils: ControllerUtils 15 | ) { 16 | 17 | private val log = LoggerFactory.getLogger(javaClass) 18 | 19 | /** 20 | * Recalculate deletion timestamp to [retentionSeconds] seconds in the future for the 21 | * oldest [numResources] that are marked 22 | */ 23 | @RequestMapping(value = ["/resources/recalculate/{namespace}/"], method = [RequestMethod.PUT]) 24 | fun recalculate( 25 | @PathVariable namespace: String, 26 | @RequestParam(required = true) retentionSeconds: Long, 27 | @RequestParam(required = true) numResources: Int 28 | ) { 29 | log.info( 30 | "Recalculating deletion timestamp for oldest $numResources resources in $namespace. " + 31 | "Setting to ${retentionSeconds}s from now." 32 | ) 33 | val workConfiguration = controllerUtils.findWorkConfiguration(SwabbieNamespace.namespaceParser(namespace)) 34 | val handler = controllerUtils.findHandler(workConfiguration) 35 | handler.recalculateDeletionTimestamp(namespace, retentionSeconds, numResources) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/exclusions/TemporalTagExclusionSupplier.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.exclusions 18 | 19 | import com.netflix.spinnaker.config.Attribute 20 | import com.netflix.spinnaker.config.Exclusion 21 | import com.netflix.spinnaker.config.ExclusionType 22 | import com.netflix.spinnaker.swabbie.exclusions.ExclusionsSupplier 23 | import com.netflix.spinnaker.swabbie.tagging.TemporalTags 24 | import org.springframework.stereotype.Component 25 | 26 | @Component 27 | class TemporalTagExclusionSupplier : ExclusionsSupplier { 28 | override fun get(): List { 29 | return listOf( 30 | Exclusion() 31 | .withType(ExclusionType.Tag.toString()) 32 | .withAttributes( 33 | TemporalTags.temporalTags.map { key -> 34 | Attribute() 35 | .withKey(key) 36 | .withValue( 37 | TemporalTags.supportedTemporalTagValues 38 | ) 39 | }.toSet() 40 | ) 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/loadbalancers/AmazonElasticLoadBalancer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.loadbalancers 18 | 19 | import com.fasterxml.jackson.annotation.JsonTypeName 20 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 21 | import com.netflix.spinnaker.swabbie.model.AWS 22 | import com.netflix.spinnaker.swabbie.model.LOAD_BALANCER 23 | 24 | @JsonTypeName("amazonElasticLoadBalancer") 25 | data class AmazonElasticLoadBalancer( 26 | private val loadBalancerName: String?, // TODO: stupid hack, fix this 27 | override val resourceType: String = LOAD_BALANCER, 28 | override val name: String = loadBalancerName!!, 29 | override val resourceId: String = loadBalancerName!!, 30 | override val cloudProvider: String = AWS, 31 | private val creationDate: String? 32 | ) : AmazonResource(creationDate) { 33 | override fun equals(other: Any?): Boolean { 34 | return super.equals(other) 35 | } 36 | 37 | override fun hashCode(): Int { 38 | return super.hashCode() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/AmazonTagOwnerResolutionStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws 18 | 19 | import com.netflix.spinnaker.swabbie.ResourceOwnerResolutionStrategy 20 | import com.netflix.spinnaker.swabbie.model.Resource 21 | import org.springframework.stereotype.Component 22 | 23 | @Component 24 | class AmazonTagOwnerResolutionStrategy : ResourceOwnerResolutionStrategy { 25 | 26 | override fun primaryFor(): Set = emptySet() 27 | 28 | override fun resolve(resource: Resource): String? { 29 | if ("tags" in resource.details) { 30 | (resource.details["tags"] as? List>)?.let { tags -> 31 | return getOwner(tags) 32 | } 33 | } 34 | 35 | return null 36 | } 37 | 38 | private fun getOwner(tags: List>): String? { 39 | return tags.find { it["key"] == "creator" || it["key"] == "owner" }?.get("value") as? String 40 | ?: return tags.find { "owner" in it || "creator" in it }?.map { it.value }?.first() as? String 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/tagging/TemporalTags.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.tagging 18 | 19 | import com.netflix.spinnaker.swabbie.model.BasicTag 20 | import java.time.temporal.ChronoUnit 21 | 22 | class TemporalTags { 23 | companion object { 24 | val supportedTemporalTagValues = listOf("^\\d+(d|m|y|w)$", "never") 25 | val temporalTags = listOf("expiration_time", "expires", "ttl") 26 | 27 | private val supportedTemporalUnits = mapOf( 28 | "d" to ChronoUnit.DAYS, 29 | "m" to ChronoUnit.MONTHS, 30 | "y" to ChronoUnit.YEARS, 31 | "w" to ChronoUnit.WEEKS 32 | ) 33 | 34 | fun toTemporalPair(tag: BasicTag): Pair { 35 | val tagValue = tag.value.toString() 36 | val unit = tagValue.last().toString() 37 | val amount = tagValue.replace(unit, "").toLong() 38 | if (unit !in supportedTemporalUnits) { 39 | return Pair(amount, null) 40 | } 41 | 42 | return Pair(amount, supportedTemporalUnits[unit]) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/securitygroups/AmazonSecurityGroup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.securitygroups 18 | 19 | import com.fasterxml.jackson.annotation.JsonTypeName 20 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 21 | import com.netflix.spinnaker.swabbie.model.AWS 22 | import com.netflix.spinnaker.swabbie.model.SECURITY_GROUP 23 | 24 | @JsonTypeName("amazonSecurityGroup") 25 | data class AmazonSecurityGroup( 26 | val groupId: String, 27 | val groupName: String, 28 | val description: String?, 29 | val vpcId: String?, 30 | val ownerId: String, 31 | override val resourceId: String = groupId, 32 | override val name: String = groupName, 33 | override val resourceType: String = SECURITY_GROUP, 34 | override val cloudProvider: String = AWS, 35 | private val creationDate: String? 36 | ) : AmazonResource(creationDate) { 37 | override fun equals(other: Any?): Boolean { 38 | return super.equals(other) 39 | } 40 | 41 | override fun hashCode(): Int { 42 | return super.hashCode() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /swabbie-redis/src/main/kotlin/com/netflix/spinnaker/swabbie/redis/RedisClientDelegateSupport.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.redis 18 | 19 | import com.netflix.spinnaker.kork.jedis.RedisClientDelegate 20 | 21 | open class RedisClientDelegateSupport 22 | constructor( 23 | private val mainRedisClientDelegate: RedisClientDelegate, 24 | private val previousRedisClientDelegate: RedisClientDelegate? 25 | ) { 26 | fun getClientForId(id: String?): RedisClientDelegate { 27 | if (id == null) { 28 | return mainRedisClientDelegate 29 | } 30 | 31 | var client: RedisClientDelegate? = null 32 | mainRedisClientDelegate.withCommandsClient { 33 | if (it.exists(id)) { 34 | client = mainRedisClientDelegate 35 | } 36 | } 37 | 38 | if (client == null && previousRedisClientDelegate != null) { 39 | previousRedisClientDelegate.withCommandsClient { 40 | if (it.exists(id)) { 41 | client = previousRedisClientDelegate 42 | } 43 | } 44 | } 45 | 46 | return client ?: mainRedisClientDelegate 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /swabbie-aws/src/test/java/com/netflix/spinnaker/swabbie/aws/autoscalinggroups/ZeroLoadBalancerRuleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.autoscalinggroups 18 | 19 | import java.time.Clock 20 | import java.time.Instant 21 | import java.time.ZoneOffset 22 | import org.junit.jupiter.api.Test 23 | import strikt.api.expectThat 24 | import strikt.assertions.isNotNull 25 | import strikt.assertions.isNull 26 | 27 | object ZeroLoadBalancerRuleTest { 28 | private val clock = Clock.fixed(Instant.now(), ZoneOffset.UTC) 29 | 30 | @Test 31 | fun `should apply if server group has no load balancer`() { 32 | val rule = ZeroLoadBalancerRule() 33 | val asg = AmazonAutoScalingGroup( 34 | autoScalingGroupName = "testapp-v001", 35 | instances = listOf( 36 | mapOf("instanceId" to "i-01234") 37 | ), 38 | loadBalancerNames = listOf(), 39 | createdTime = clock.millis() 40 | ) 41 | 42 | expectThat(rule.apply(asg).summary).isNotNull() 43 | 44 | expectThat(rule.apply(asg.copy(loadBalancerNames = listOf("lb"))).summary).isNull() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swabbie-web/swabbie-web.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: "io.spinnaker.package" 18 | 19 | ext { 20 | springConfigLocation = System.getProperty("spring.config.location", "${System.getProperty("user.home")}/.spinnaker/".toString()) 21 | } 22 | 23 | run { 24 | systemProperty("spring.config.location", project.springConfigLocation) 25 | } 26 | 27 | mainClassName = "com.netflix.spinnaker.swabbie.MainKt" 28 | 29 | dependencies { 30 | implementation "org.springframework.boot:spring-boot-starter-actuator" 31 | implementation "org.springframework.boot:spring-boot-starter-web" 32 | implementation "org.springframework.boot:spring-boot-starter-data-rest" 33 | implementation "io.spinnaker.kork:kork-web" 34 | 35 | implementation project(":swabbie-aws") 36 | implementation project(":swabbie-clouddriver") 37 | implementation project(":swabbie-core") 38 | implementation project(":swabbie-echo") 39 | implementation project(":swabbie-front50") 40 | implementation project(":swabbie-redis") 41 | implementation project(":swabbie-test") 42 | runtimeOnly "io.spinnaker.kork:kork-runtime" 43 | } 44 | -------------------------------------------------------------------------------- /swabbie-aws/src/test/java/com/netflix/spinnaker/swabbie/aws/AmazonTagOwnerResolutionStrategyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws 18 | 19 | import com.natpryce.hamkrest.equalTo 20 | import com.natpryce.hamkrest.should.shouldMatch 21 | import com.netflix.spinnaker.swabbie.aws.exclusions.AwsTestResource 22 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 23 | import org.junit.jupiter.api.Assertions 24 | import org.junit.jupiter.api.Test 25 | 26 | object AmazonTagOwnerResolutionStrategyTest { 27 | @Test 28 | fun `should resolve owner from aws tag if present`() { 29 | var resource = AwsTestResource("1") 30 | .withDetail(name = "tags", value = listOf(mapOf("owner" to "test@netflix.com"))) 31 | 32 | AmazonTagOwnerResolutionStrategy().resolve(resource as AmazonResource).let { owner -> 33 | owner shouldMatch equalTo("test@netflix.com") 34 | } 35 | 36 | resource = AwsTestResource("1") 37 | AmazonTagOwnerResolutionStrategy().resolve(resource as AmazonResource).let { owner -> 38 | Assertions.assertNull(owner) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /swabbie-core/src/test/kotlin/com/netflix/spinnaker/swabbie/ScheduleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie 20 | 21 | import com.natpryce.hamkrest.equalTo 22 | import com.natpryce.hamkrest.should.shouldMatch 23 | import com.netflix.spinnaker.config.Schedule 24 | import java.time.Instant 25 | import org.junit.jupiter.api.Test 26 | 27 | object ScheduleTest { 28 | private val subject = Schedule().also { it.enabled = true } 29 | 30 | val mondayTimestamp = Instant.parse("2018-05-21T12:00:00Z") 31 | val satTimestamp = Instant.parse("2018-05-26T12:00:00Z") 32 | val followingMondayTimestamp = Instant.parse("2018-05-28T12:00:00Z") 33 | 34 | @Test 35 | fun `should return timestamp when day is in the schedule`() { 36 | subject.getNextTimeInWindow(mondayTimestamp.toEpochMilli()) shouldMatch equalTo(mondayTimestamp.toEpochMilli()) 37 | } 38 | 39 | @Test 40 | fun `should return monday when saturday is the proposed timestamp`() { 41 | subject.getNextTimeInWindow(satTimestamp.toEpochMilli()) shouldMatch equalTo(followingMondayTimestamp.toEpochMilli()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/repository/TaskTrackingRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.repository 20 | 21 | import com.netflix.spinnaker.swabbie.events.Action 22 | import com.netflix.spinnaker.swabbie.model.MarkedResource 23 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 24 | 25 | interface TaskTrackingRepository { 26 | fun add(taskId: String, taskCompleteEventInfo: TaskCompleteEventInfo) 27 | fun isInProgress(taskId: String): Boolean 28 | fun getInProgress(): Set 29 | fun setSucceeded(taskId: String) 30 | fun setFailed(taskId: String) 31 | fun getFailed(): Set 32 | fun getSucceeded(): Set 33 | fun getTaskDetail(taskId: String): TaskCompleteEventInfo? 34 | fun cleanUpFinishedTasks(daysToLeave: Int = 2) 35 | } 36 | 37 | data class TaskCompleteEventInfo( 38 | val action: Action, 39 | val markedResources: List, 40 | val workConfiguration: WorkConfiguration, 41 | var submittedTimeMillis: Long? 42 | ) 43 | 44 | enum class TaskState { 45 | IN_PROGRESS, SUCCEEDED, FAILED 46 | } 47 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/utils/ApplicationUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.utils 18 | 19 | import com.netflix.spinnaker.swabbie.InMemoryCache 20 | import com.netflix.spinnaker.swabbie.model.Application 21 | import com.netflix.spinnaker.swabbie.model.Grouping 22 | import com.netflix.spinnaker.swabbie.model.GroupingType 23 | import com.netflix.spinnaker.swabbie.model.Resource 24 | import org.springframework.stereotype.Component 25 | 26 | @Component 27 | open class ApplicationUtils( 28 | private val applicationsCaches: List> 29 | ) { 30 | 31 | /** 32 | * If the resource has an app grouping and that app exists in the cache of applicaitons we know about, 33 | * return that app. 34 | * Otherwise, return "swabbie". 35 | */ 36 | fun determineApp(resource: Resource): String { 37 | val grouping: Grouping = resource.grouping ?: return "swabbie" 38 | if (grouping.type == GroupingType.APPLICATION) { 39 | if (applicationsCaches.any { it.contains(grouping.value) }) { 40 | return grouping.value 41 | } 42 | } 43 | return "swabbie" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/release_info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | NEW_TAG=${GITHUB_REF/refs\/tags\//} 4 | export NEW_TAG 5 | echo "NEW_TAG=$NEW_TAG" 6 | # Glob match previous tags which should be format v1.2.3. Avoids Deck's npm tagging. 7 | PREVIOUS_TAG=$(git describe --abbrev=0 --tags "${NEW_TAG}"^ --match 'v[0-9]*') 8 | export PREVIOUS_TAG 9 | echo "PREVIOUS_TAG=$PREVIOUS_TAG" 10 | CHANGELOG=$(git log "$NEW_TAG"..."$PREVIOUS_TAG" --oneline) 11 | export CHANGELOG 12 | echo "CHANGELOG=$CHANGELOG" 13 | 14 | # Format the changelog so it's markdown compatible 15 | CHANGELOG="${CHANGELOG//$'%'/%25}" 16 | CHANGELOG="${CHANGELOG//$'\n'/%0A}" 17 | CHANGELOG="${CHANGELOG//$'\r'/%0D}" 18 | 19 | # If the previous release tag is the same as this tag the user likely cut a release (and in the process created a tag), which means we can skip the need to create a release 20 | SKIP_RELEASE=$([[ "$PREVIOUS_TAG" = "$NEW_TAG" ]] && echo "true" || echo "false") 21 | export SKIP_RELEASE 22 | 23 | # https://github.com/fsaintjacques/semver-tool/blob/master/src/semver#L5-L14 24 | NAT='0|[1-9][0-9]*' 25 | ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*' 26 | IDENT="$NAT|$ALPHANUM" 27 | FIELD='[0-9A-Za-z-]+' 28 | SEMVER_REGEX="\ 29 | ^[vV]?\ 30 | ($NAT)\\.($NAT)\\.($NAT)\ 31 | (\\-(${IDENT})(\\.(${IDENT}))*)?\ 32 | (\\+${FIELD}(\\.${FIELD})*)?$" 33 | 34 | # Used in downstream steps to determine if the release should be marked as a "prerelease" and if the build should build candidate release artifacts 35 | IS_CANDIDATE=$([[ $NEW_TAG =~ $SEMVER_REGEX && -n ${BASH_REMATCH[4]} ]] && echo "true" || echo "false") 36 | export IS_CANDIDATE 37 | 38 | # This is the version string we will pass to the build, trim off leading 'v' if present 39 | RELEASE_VERSION=$([[ $NEW_TAG =~ $SEMVER_REGEX ]] && echo "${NEW_TAG:1}" || echo "${NEW_TAG}") 40 | export RELEASE_VERSION 41 | echo "RELEASE_VERSION=$RELEASE_VERSION" 42 | -------------------------------------------------------------------------------- /swabbie-test/src/main/kotlin/com/netflix/spinnaker/swabbie/test/InMemoryWorkQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.test 18 | 19 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 20 | import com.netflix.spinnaker.swabbie.model.WorkItem 21 | import com.netflix.spinnaker.swabbie.work.WorkQueue 22 | import java.util.concurrent.LinkedBlockingQueue 23 | import org.slf4j.LoggerFactory 24 | 25 | // For testing purpose 26 | class InMemoryWorkQueue( 27 | private val _seed: List = emptyList() 28 | ) : WorkQueue { 29 | 30 | private val log = LoggerFactory.getLogger(javaClass) 31 | private val _q = LinkedBlockingQueue() 32 | 33 | init { 34 | log.info("Using ${javaClass.simpleName}") 35 | } 36 | 37 | override fun seed() { 38 | _seed.map { it.toWorkItems() }.flatten().forEach { 39 | _q.put(it) 40 | } 41 | } 42 | 43 | override fun pop(): WorkItem? { 44 | return _q.poll() 45 | } 46 | 47 | override fun push(workItem: WorkItem) { 48 | _q.put(workItem) 49 | } 50 | 51 | override fun isEmpty(): Boolean { 52 | return _q.isEmpty() 53 | } 54 | 55 | override fun size(): Int { 56 | return _q.size 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /swabbie-aws/src/test/java/com/netflix/spinnaker/swabbie/aws/autoscalinggroups/ZeroInstanceRuleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.autoscalinggroups 18 | 19 | import java.time.Clock 20 | import java.time.Instant 21 | import java.time.ZoneOffset 22 | import org.junit.jupiter.api.Test 23 | import strikt.api.expectThat 24 | import strikt.assertions.isNotNull 25 | import strikt.assertions.isNull 26 | 27 | object ZeroInstanceRuleTest { 28 | private val clock = Clock.fixed(Instant.now(), ZoneOffset.UTC) 29 | 30 | @Test 31 | fun `should apply if server group has no instances`() { 32 | val rule = ZeroInstanceRule() 33 | val asg = AmazonAutoScalingGroup( 34 | autoScalingGroupName = "testapp-v001", 35 | instances = listOf( 36 | mapOf("instanceId" to "i-01234") 37 | ), 38 | loadBalancerNames = listOf(), 39 | createdTime = clock.millis() 40 | ) 41 | 42 | // doesn't apply because the server group has an instance 43 | expectThat(rule.apply(asg).summary).isNull() 44 | 45 | // applies because the server group has no instances 46 | expectThat( 47 | rule.apply(asg.copy(instances = emptyList())).summary 48 | ).isNotNull() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /swabbie-test/src/main/kotlin/com/netflix/spinnaker/swabbie/test/TestResource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.test 18 | 19 | import com.fasterxml.jackson.annotation.JsonTypeName 20 | import com.netflix.spinnaker.swabbie.model.Grouping 21 | import com.netflix.spinnaker.swabbie.model.GroupingType 22 | import com.netflix.spinnaker.swabbie.model.Resource 23 | import java.time.Instant 24 | import java.time.temporal.ChronoUnit 25 | 26 | @JsonTypeName("testResource") 27 | data class TestResource( 28 | override val resourceId: String, 29 | override val resourceType: String = TEST_RESOURCE_TYPE, 30 | override val cloudProvider: String = TEST_RESOURCE_PROVIDER_TYPE, 31 | override val name: String = resourceId, 32 | override val createTs: Long = Instant.now().minus(10, ChronoUnit.DAYS).toEpochMilli(), 33 | override val grouping: Grouping? = Grouping("group", GroupingType.APPLICATION) 34 | ) : Resource() { 35 | override fun equals(other: Any?): Boolean { 36 | return super.equals(other) 37 | } 38 | 39 | override fun hashCode(): Int { 40 | return super.hashCode() 41 | } 42 | } 43 | 44 | const val TEST_RESOURCE_PROVIDER_TYPE = "testProvider" 45 | const val TEST_RESOURCE_TYPE = "testResourceType" 46 | -------------------------------------------------------------------------------- /swabbie-aws/swabbie-aws.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencies { 18 | implementation project(":swabbie-core") 19 | implementation project(":swabbie-orca") 20 | implementation project(":swabbie-retrofit") 21 | 22 | implementation "com.netflix.eureka:eureka-client" 23 | implementation "io.spinnaker.kork:kork-aws:$korkVersion" 24 | implementation "com.netflix.awsobjectmapper:awsobjectmapper" 25 | implementation "com.amazonaws:aws-java-sdk-ec2" 26 | implementation "com.amazonaws:aws-java-sdk-autoscaling" 27 | implementation "com.amazonaws:aws-java-sdk-sts" 28 | implementation "com.amazonaws:aws-java-sdk-elasticloadbalancing" 29 | implementation "com.netflix.frigga:frigga" 30 | implementation "io.spinnaker.kork:kork-moniker" 31 | implementation "net.logstash.logback:logstash-logback-encoder" 32 | implementation "com.fasterxml.jackson.module:jackson-module-kotlin" 33 | implementation "io.spinnaker.kork:kork-core" 34 | implementation "io.spinnaker.kork:kork-web" 35 | 36 | implementation "com.squareup.retrofit:retrofit" 37 | implementation "com.squareup.retrofit:converter-jackson" 38 | 39 | testImplementation project(":swabbie-test") 40 | testImplementation "io.spinnaker.kork:kork-test" 41 | testImplementation "io.strikt:strikt-core" 42 | } 43 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | conditions: 4 | - status-success=build 5 | 6 | pull_request_rules: 7 | - name: Automatically merge on CI success and review 8 | conditions: 9 | - base=master 10 | - status-success=build 11 | - "label=ready to merge" 12 | - "approved-reviews-by=@oss-approvers" 13 | actions: 14 | queue: 15 | method: squash 16 | name: default 17 | label: 18 | add: ["auto merged"] 19 | - name: Automatically merge release branch changes on CI success and release manager review 20 | conditions: 21 | - base~=^release- 22 | - status-success=build 23 | - "label=ready to merge" 24 | - "approved-reviews-by=@release-managers" 25 | actions: 26 | queue: 27 | method: squash 28 | name: default 29 | label: 30 | add: ["auto merged"] 31 | - name: Automatically merge PRs from maintainers on CI success and review 32 | conditions: 33 | - base=master 34 | - status-success=build 35 | - "label=ready to merge" 36 | - "author=@oss-approvers" 37 | actions: 38 | queue: 39 | method: squash 40 | name: default 41 | label: 42 | add: ["auto merged"] 43 | - name: Automatically merge autobump PRs on CI success 44 | conditions: 45 | - base~=^(master|release-) 46 | - status-success=build 47 | - "label~=autobump-*" 48 | - "author:spinnakerbot" 49 | actions: 50 | queue: 51 | method: squash 52 | name: default 53 | label: 54 | add: ["auto merged"] 55 | - name: Request reviews for autobump PRs on CI failure 56 | conditions: 57 | - base~=^(master|release-) 58 | - status-failure=build 59 | - "label~=autobump-*" 60 | - base=master 61 | actions: 62 | request_reviews: 63 | teams: ["oss-approvers"] 64 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/AlwaysCleanRule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.model 20 | 21 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 22 | import com.netflix.spinnaker.config.SwabbieProperties 23 | import org.slf4j.LoggerFactory 24 | 25 | /** 26 | * Testing rule that is invalid on everything. 27 | */ 28 | class AlwaysCleanRule( 29 | swabbieProperties: SwabbieProperties 30 | ) : Rule { 31 | private val config = swabbieProperties.testing.alwaysCleanRuleConfig 32 | private val log = LoggerFactory.getLogger(javaClass) 33 | 34 | init { 35 | log.info("Using ${javaClass.simpleName} for resources ${config.resourceIds}") 36 | } 37 | 38 | override fun applicableForType(clazz: Class): Boolean = true 39 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 40 | return if (config.resourceIds.contains(resource.resourceId)) { 41 | Result( 42 | Summary( 43 | description = "This resource should always be cleaned", 44 | ruleName = name() 45 | ) 46 | ) 47 | } else { 48 | Result(null) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/repository/ResourceUseTrackingRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.repository 20 | 21 | interface ResourceUseTrackingRepository { 22 | /** 23 | * record that resource is used by another resource at this exact time 24 | */ 25 | fun recordUse(resourceIdentifier: String, usedByResourceIdentifier: String) 26 | 27 | /** 28 | * gets resources that haven't been seen in use for X days 29 | */ 30 | fun getUnused(): List 31 | 32 | /** 33 | * gets all resources that we have seen in use within the threshold 34 | */ 35 | fun getUsed(): Set 36 | 37 | fun isUnused(resourceIdentifier: String): Boolean 38 | 39 | fun getLastSeenInfo(resourceIdentifier: String): LastSeenInfo? 40 | 41 | /** 42 | * Returns true if data is present for the whole outOfUseThreshold period. 43 | * Returns false if data is only present for part of the period, 44 | * i.e. if the outOfUseThreshold is 10 days but we only have data for 5. 45 | */ 46 | fun hasCompleteData(): Boolean 47 | } 48 | 49 | data class LastSeenInfo( 50 | val resourceId: String, 51 | val usedByResourceId: String, 52 | val timeSeen: Long 53 | ) 54 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/instances/AmazonInstance.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.instances 18 | 19 | import com.fasterxml.jackson.annotation.JsonTypeName 20 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 21 | import com.netflix.spinnaker.swabbie.model.AWS 22 | import com.netflix.spinnaker.swabbie.model.INSTANCE 23 | import java.time.Instant 24 | import java.time.LocalDateTime 25 | import java.time.ZoneId 26 | 27 | @JsonTypeName("amazonInstance") 28 | data class AmazonInstance( 29 | val instanceId: String, 30 | val imageId: String, 31 | private val launchTime: Long, 32 | override val resourceId: String = instanceId, 33 | override val resourceType: String = INSTANCE, 34 | override val cloudProvider: String = AWS, 35 | override val name: String = instanceId, 36 | private val creationDate: String? = 37 | LocalDateTime.ofInstant(Instant.ofEpochMilli(launchTime), ZoneId.systemDefault()).toString() 38 | ) : AmazonResource(creationDate) { 39 | fun getAutoscalingGroup(): String? { 40 | return tags()?.find { it.key == "aws:autoscaling:groupName" }?.value as String 41 | } 42 | 43 | override fun equals(other: Any?): Boolean { 44 | return super.equals(other) 45 | } 46 | 47 | override fun hashCode(): Int { 48 | return super.hashCode() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/edda/caches/EddaEndpointCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.edda.caches 18 | 19 | import com.netflix.spinnaker.security.AuthenticatedRequest.allowAnonymous 20 | import com.netflix.spinnaker.swabbie.InMemoryCache 21 | import com.netflix.spinnaker.swabbie.aws.edda.EddaEndpointsService 22 | import com.netflix.spinnaker.swabbie.model.EddaEndpoint 23 | 24 | class EddaEndpointCache( 25 | private val eddaEndpointsService: EddaEndpointsService 26 | ) : InMemoryCache({ 27 | allowAnonymous { load(eddaEndpointsService) } 28 | }) { 29 | companion object { 30 | fun load(eddaEndpointsService: EddaEndpointsService): Set { 31 | return eddaEndpointsService.getEddaEndpoints().get() 32 | .asSequence() 33 | .mapNotNull { buildEndpoint(it) } 34 | .toSet() 35 | } 36 | 37 | // i.e. "http://edda-account.region.foo.bar.com", 38 | private fun buildEndpoint(endpoint: String): EddaEndpoint? { 39 | val regex = 40 | """^https?://edda-([\w\-]+)\.([\w\-]+)\.([\w\-]+)\..*$""".toRegex() 41 | val match = regex.matchEntire(endpoint) ?: return null 42 | val (account, region, env) = match.destructured 43 | 44 | return EddaEndpoint(region, account, env, endpoint, "$region-$account-$env") 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/launchtemplates/AmazonLaunchTemplate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.launchtemplates 18 | 19 | import com.fasterxml.jackson.annotation.JsonTypeName 20 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 21 | import com.netflix.spinnaker.swabbie.model.AWS 22 | import com.netflix.spinnaker.swabbie.model.LAUNCH_TEMPLATE 23 | import java.time.Instant 24 | import java.time.LocalDateTime 25 | import java.time.ZoneId 26 | 27 | @JsonTypeName("amazonLaunchTemplate") 28 | data class AmazonLaunchTemplate( 29 | private val launchTemplateId: String? = "", 30 | private val launchTemplateName: String? = "", 31 | private val createdTime: Long, 32 | override val resourceId: String = launchTemplateId!!, 33 | override val resourceType: String = LAUNCH_TEMPLATE, 34 | override val cloudProvider: String = AWS, 35 | override val name: String = launchTemplateName!!, 36 | private val creationDate: String? = LocalDateTime.ofInstant(Instant.ofEpochMilli(createdTime), ZoneId.systemDefault()).toString() 37 | ) : AmazonResource(creationDate) { 38 | fun getAutoscalingGroupName() = 39 | name.substringBeforeLast("-") 40 | override fun equals(other: Any?): Boolean { 41 | return super.equals(other) 42 | } 43 | 44 | override fun hashCode(): Int { 45 | return super.hashCode() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/images/AmazonImage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.images 18 | 19 | import com.amazonaws.services.ec2.model.EbsBlockDevice 20 | import com.fasterxml.jackson.annotation.JsonTypeName 21 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 22 | import com.netflix.spinnaker.swabbie.model.AWS 23 | import com.netflix.spinnaker.swabbie.model.IMAGE 24 | 25 | @JsonTypeName("amazonImage") 26 | data class AmazonImage( 27 | val imageId: String, 28 | val ownerId: String?, 29 | val description: String?, 30 | val state: String, 31 | val blockDeviceMappings: List?, 32 | override val resourceId: String = imageId, 33 | override val resourceType: String = IMAGE, 34 | override val cloudProvider: String = AWS, 35 | override val name: String?, 36 | private val creationDate: String? 37 | ) : AmazonResource(creationDate) { 38 | override fun equals(other: Any?): Boolean { 39 | return super.equals(other) 40 | } 41 | 42 | override fun hashCode(): Int { 43 | return super.hashCode() 44 | } 45 | } 46 | 47 | data class AmazonBlockDevice( 48 | val ebs: EbsBlockDevice?, 49 | val noDevice: String?, 50 | val deviceName: String?, 51 | val virtualName: String? 52 | ) 53 | 54 | data class AmazonEbs( 55 | val snapshotId: String?, 56 | val virtualName: String? 57 | ) 58 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/tagging/EntityTag.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.tagging 18 | 19 | const val SWABBIE_ENTITY_TAG_NAME = "spinnaker_ui_alert:swabbie_deletion_candidate" 20 | 21 | data class EntityTag( 22 | 23 | val namespace: String, 24 | val value: TagValue?, 25 | val valueType: String = "object", 26 | val category: String = "Clean Up", 27 | val name: String = SWABBIE_ENTITY_TAG_NAME 28 | ) : Tag 29 | 30 | data class TagValue( 31 | val message: String, 32 | val type: String = "alert" 33 | ) 34 | 35 | data class EntityRef( 36 | val entityType: String, 37 | val cloudProvider: String, 38 | val entityId: String, 39 | val region: String, 40 | val account: String 41 | ) 42 | 43 | data class UpsertEntityTagsRequest( 44 | val entityRef: EntityRef?, 45 | val tags: List?, 46 | override val application: String, 47 | override val description: String 48 | ) : EntityTagRequest("upsertEntityTags", application, description) 49 | 50 | data class DeleteEntityTagsRequest( 51 | val id: String, 52 | override val application: String, 53 | override val description: String 54 | ) : EntityTagRequest("deleteEntityTags", application, description) 55 | 56 | open class EntityTagRequest( 57 | open val type: String, 58 | open val application: String, 59 | open val description: String 60 | ) : TagRequest 61 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/launchconfigurations/LaunchConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.launchconfigurations 18 | 19 | import com.fasterxml.jackson.annotation.JsonTypeName 20 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 21 | import com.netflix.spinnaker.swabbie.model.AWS 22 | import com.netflix.spinnaker.swabbie.model.LAUNCH_CONFIGURATION 23 | import java.time.Instant 24 | import java.time.LocalDateTime 25 | import java.time.ZoneId 26 | 27 | @JsonTypeName("amazonLaunchConfiguration") 28 | data class AmazonLaunchConfiguration( 29 | val imageId: String, 30 | private val launchConfigurationName: String? = "", 31 | private val createdTime: Long, 32 | override val resourceId: String = launchConfigurationName ?: "", 33 | override val resourceType: String = LAUNCH_CONFIGURATION, 34 | override val cloudProvider: String = AWS, 35 | override val name: String = resourceId, 36 | private val creationDate: String? = LocalDateTime.ofInstant(Instant.ofEpochMilli(createdTime), ZoneId.systemDefault()).toString() 37 | ) : AmazonResource(creationDate) { 38 | fun getAutoscalingGroupName() = 39 | name.substringBeforeLast("-") 40 | override fun equals(other: Any?): Boolean { 41 | return super.equals(other) 42 | } 43 | 44 | override fun hashCode(): Int { 45 | return super.hashCode() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/exclusions/AllowListExclusionPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exclusions 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | import com.netflix.spinnaker.config.ExclusionType 21 | import org.springframework.stereotype.Component 22 | 23 | @Component 24 | class AllowListExclusionPolicy : ResourceExclusionPolicy { 25 | override fun getType(): ExclusionType = ExclusionType.Allowlist 26 | 27 | /** 28 | * Takes a resource that can be excluded (an excludable), and a list of the configured exclusions, and 29 | * determines if the resource can be excluded based on those rules. 30 | */ 31 | override fun apply(excludable: Excludable, exclusions: List): String? { 32 | keysAndValues(exclusions, ExclusionType.Allowlist).let { kv -> 33 | if (kv.isEmpty()) { 34 | return null // no allowlist defined 35 | } 36 | 37 | kv.keys.forEach { key -> 38 | if (excludable.matchResourceAttributes(key, kv.getValue(key))) { 39 | // since a matching value is returned for the key, we know it is in the allowlist 40 | return null 41 | } 42 | } 43 | 44 | // if none of the keys have a qualifying value, the resource is not in the allowlist 45 | return notAllowlistedMessage(excludable.name, kv.values.flatten().toSet()) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/events/ResourceTrackingManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.events 20 | 21 | import com.netflix.spinnaker.swabbie.repository.ResourceTrackingRepository 22 | import org.slf4j.Logger 23 | import org.slf4j.LoggerFactory 24 | import org.springframework.context.event.EventListener 25 | import org.springframework.stereotype.Component 26 | 27 | /** 28 | * Handles any further change in state after a resource is updated 29 | * for resources that are changed via an orca task. 30 | */ 31 | // TODO: (jeyrs) - Review me. Don't think this class is needed since the resource removal happens on site 32 | @Component 33 | class ResourceTrackingManager( 34 | private val resourceTrackingRepository: ResourceTrackingRepository 35 | ) { 36 | 37 | private val log: Logger = LoggerFactory.getLogger(javaClass) 38 | 39 | /** 40 | * Other event types are handled in [AbstractResourceTypeHandler]. 41 | * These events are emitted after an orca task is completed in 42 | * [OrcaTaskMonitoringAgent] 43 | */ 44 | @EventListener 45 | fun handleEvents(event: Event) { 46 | when (event) { 47 | is DeleteResourceEvent -> { 48 | log.debug("Deleted {}. Configuration: {}", event.markedResource.uniqueId(), event.workConfiguration) 49 | resourceTrackingRepository.remove(event.markedResource) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/caches/AmazonImagesUsedByInstancesInMemoryCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.aws.caches 20 | 21 | import com.netflix.spinnaker.security.AuthenticatedRequest 22 | import com.netflix.spinnaker.swabbie.Cacheable 23 | import com.netflix.spinnaker.swabbie.CachedViewProvider 24 | import com.netflix.spinnaker.swabbie.InMemorySingletonCache 25 | import com.netflix.spinnaker.swabbie.aws.Parameters 26 | 27 | open class AmazonImagesUsedByInstancesInMemoryCache( 28 | provider: CachedViewProvider 29 | ) : InMemorySingletonCache({ AuthenticatedRequest.allowAnonymous(provider::load) }) 30 | 31 | data class AmazonImagesUsedByInstancesCache( 32 | private val refdAmisByRegion: Map>, 33 | private val lastUpdated: Long, 34 | override val name: String? 35 | ) : Cacheable { 36 | /** 37 | * @param params.region: return a Set of Amazon imageIds (i.e. "ami-abc123") 38 | * currently referenced by running EC2 instances in the region 39 | */ 40 | fun getAll(params: Parameters): Set { 41 | if (params.region != "") { 42 | return refdAmisByRegion[params.region] ?: emptySet() 43 | } else { 44 | throw IllegalArgumentException("Missing required region parameter") 45 | } 46 | } 47 | 48 | fun getLastUpdated(): Long { 49 | return lastUpdated 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /swabbie-front50/src/test/kotlin/com/netflix/spinnaker/swabbie/front50/ApplicationResourceOwnerResolutionStrategyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.front50 18 | 19 | import com.natpryce.hamkrest.equalTo 20 | import com.natpryce.hamkrest.should.shouldMatch 21 | import com.netflix.spectator.api.NoopRegistry 22 | import com.netflix.spinnaker.swabbie.InMemoryCache 23 | import com.netflix.spinnaker.swabbie.model.Application 24 | import com.netflix.spinnaker.swabbie.model.Grouping 25 | import com.netflix.spinnaker.swabbie.model.GroupingType 26 | import com.netflix.spinnaker.swabbie.test.TestResource 27 | import org.junit.jupiter.api.Test 28 | import org.mockito.kotlin.doReturn 29 | import org.mockito.kotlin.mock 30 | import org.mockito.kotlin.whenever 31 | 32 | object ApplicationResourceOwnerResolutionStrategyTest { 33 | private val front50ApplicationCache: InMemoryCache = mock() 34 | private val subject = ApplicationResourceOwnerResolutionStrategy(front50ApplicationCache, NoopRegistry()) 35 | 36 | @Test 37 | fun `should resolve resource owner from spinnaker application`() { 38 | whenever(front50ApplicationCache.get()) doReturn 39 | setOf( 40 | Application(name = "name", email = "name@netflix.com"), 41 | Application(name = "test", email = "test@netflix.com") 42 | ) 43 | 44 | subject.resolve( 45 | TestResource( 46 | resourceId = "id", 47 | name = "name", 48 | grouping = Grouping("name", GroupingType.APPLICATION) 49 | ) 50 | ) shouldMatch equalTo("name@netflix.com") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/loadbalancers/Rules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.loadbalancers 18 | 19 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 20 | import com.netflix.spinnaker.swabbie.model.Resource 21 | import com.netflix.spinnaker.swabbie.model.Result 22 | import com.netflix.spinnaker.swabbie.model.Rule 23 | import com.netflix.spinnaker.swabbie.model.Summary 24 | import org.slf4j.Logger 25 | import org.slf4j.LoggerFactory 26 | import org.springframework.stereotype.Component 27 | 28 | /** 29 | * An Elastic Load Balancer is invalid if it's not referenced by any server groups and has no instances 30 | */ 31 | @Component 32 | class OrphanedELBRule : Rule { 33 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 34 | if (resource !is AmazonElasticLoadBalancer || resource.details["instances"] != null && (resource.details["instances"] as List<*>).size > 0) { 35 | return Result(null) 36 | } 37 | 38 | if (resource.details["serverGroups"] != null && (resource.details["serverGroups"] as List<*>).size > 0) { 39 | return Result(null) 40 | } 41 | 42 | log.debug("Load Balancer {} has no instances and is not referenced by any Server Group", resource) 43 | return Result( 44 | Summary( 45 | description = "Load Balancer has no instances and is not referenced by any Server Group", 46 | ruleName = javaClass.simpleName 47 | ) 48 | ) 49 | } 50 | 51 | private val log: Logger = LoggerFactory.getLogger(javaClass) 52 | } 53 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/ExpiredResourceRule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws 18 | 19 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 20 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 21 | import com.netflix.spinnaker.swabbie.model.Resource 22 | import com.netflix.spinnaker.swabbie.model.Result 23 | import com.netflix.spinnaker.swabbie.model.Rule 24 | import com.netflix.spinnaker.swabbie.model.Summary 25 | import java.time.Clock 26 | import org.springframework.stereotype.Component 27 | 28 | /** 29 | * This rule applies if this amazon resource has expired. 30 | * A resource is expired if it's tagged with the following keys: ("expiration_time", "expires", "ttl") 31 | * Acceptable tag value: a number followed by a suffix such as d (days), w (weeks), m (month), y (year) 32 | * @see com.netflix.spinnaker.swabbie.tagging.TemporalTags.supportedTemporalTagValues 33 | */ 34 | 35 | @Component 36 | class ExpiredResourceRule( 37 | private val clock: Clock 38 | ) : Rule { 39 | override fun applicableForType(clazz: Class): Boolean = AmazonResource::class.java.isAssignableFrom(clazz) 40 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 41 | if (resource is AmazonResource && resource.expired(clock)) { 42 | return Result( 43 | Summary( 44 | description = "${resource.resourceId} has expired.", 45 | ruleName = javaClass.simpleName 46 | ) 47 | ) 48 | } 49 | 50 | return Result(null) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/snapshots/SnapshotRules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.aws.snapshots 20 | 21 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 22 | import com.netflix.spinnaker.swabbie.model.Resource 23 | import com.netflix.spinnaker.swabbie.model.Result 24 | import com.netflix.spinnaker.swabbie.model.Rule 25 | import com.netflix.spinnaker.swabbie.model.Summary 26 | import org.springframework.stereotype.Component 27 | 28 | /** 29 | * Snapshots with a specific naming pattern are created automatically by the bakery when 30 | * the image is created. Once an image is deleted we can safely clean up the snapshot. 31 | */ 32 | @Component 33 | class OrphanedSnapshotRule : Rule { 34 | override fun applicableForType(clazz: Class): Boolean = AmazonSnapshot::class.java.isAssignableFrom(clazz) 35 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 36 | if (resource !is AmazonSnapshot || resource.matchesAnyRule( 37 | IMAGE_EXISTS 38 | ) 39 | ) { 40 | return Result(null) 41 | } 42 | 43 | return Result( 44 | Summary( 45 | description = "This snapshot was auto-generated at bake time and the image no longer exists", 46 | ruleName = name() 47 | ) 48 | ) 49 | } 50 | 51 | private fun AmazonSnapshot.matchesAnyRule(vararg ruleName: String): Boolean { 52 | return ruleName.any { details.containsKey(it) && details[it] as Boolean } 53 | } 54 | } 55 | 56 | const val IMAGE_EXISTS = "imageExists" 57 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/launchtemplates/AmazonLaunchTemplateVersion.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.launchtemplates 18 | 19 | import com.amazonaws.services.ec2.model.ResponseLaunchTemplateData 20 | import com.fasterxml.jackson.annotation.JsonTypeName 21 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 22 | import com.netflix.spinnaker.swabbie.model.AWS 23 | import com.netflix.spinnaker.swabbie.model.LAUNCH_TEMPLATE_VERSION 24 | import java.time.Instant 25 | import java.time.LocalDateTime 26 | import java.time.ZoneId 27 | 28 | @JsonTypeName("amazonLaunchTemplateVersion") 29 | data class AmazonLaunchTemplateVersion( 30 | private val versionNumber: Long = 0, 31 | private val launchTemplateId: String? = "", 32 | private val launchTemplateName: String? = "", 33 | val launchTemplateData: ResponseLaunchTemplateData, 34 | private val createdTime: Long, 35 | override val resourceId: String = launchTemplateId!! + ":" + versionNumber, 36 | override val resourceType: String = LAUNCH_TEMPLATE_VERSION, 37 | override val cloudProvider: String = AWS, 38 | override val name: String = launchTemplateName!! + ":" + versionNumber, 39 | private val creationDate: String? = LocalDateTime.ofInstant(Instant.ofEpochMilli(createdTime), ZoneId.systemDefault()).toString() 40 | ) : AmazonResource(creationDate) { 41 | fun getAutoscalingGroupName() = 42 | name.substringBeforeLast("-") 43 | override fun equals(other: Any?): Boolean { 44 | return super.equals(other) 45 | } 46 | 47 | override fun hashCode(): Int { 48 | return super.hashCode() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /swabbie-core/src/test/kotlin/com/netflix/spinnaker/swabbie/rules/AgeRuleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.rules 18 | 19 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 20 | import com.netflix.spinnaker.swabbie.test.TestResource 21 | import java.time.Clock 22 | import java.time.Instant 23 | import java.time.ZoneOffset 24 | import java.time.temporal.ChronoUnit 25 | import org.junit.jupiter.api.Test 26 | import strikt.api.expectThat 27 | import strikt.assertions.isNotNull 28 | import strikt.assertions.isNull 29 | 30 | object AgeRuleTest { 31 | private val clock = Clock.fixed(Instant.now(), ZoneOffset.UTC) 32 | 33 | @Test 34 | fun `should apply if resource is older than moreThanDays`() { 35 | val createdAt = clock.instant().minus(5, ChronoUnit.DAYS) 36 | val resource = TestResource(resourceId = "1", createTs = createdAt.toEpochMilli()) 37 | 38 | val rule = AgeRule(clock) 39 | // doesn't apply because the parameter was not provided 40 | expectThat(rule.apply(resource).summary).isNull() 41 | 42 | var ruleDefinition = RuleDefinition() 43 | .apply { 44 | name = rule.name() 45 | parameters = mapOf("moreThanDays" to 6) 46 | } 47 | 48 | // doesn't apply because even though the parameter was provided, the resource isn't older than 6 days 49 | expectThat(rule.apply(resource, ruleDefinition).summary).isNull() 50 | 51 | // applies because the parameter was provided and the resource is older than 4 days 52 | ruleDefinition = RuleDefinition() 53 | .apply { 54 | name = rule.name() 55 | parameters = mapOf("moreThanDays" to 4) 56 | } 57 | 58 | expectThat(rule.apply(resource, ruleDefinition).summary).isNotNull() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /swabbie-redis/src/main/kotlin/com/netflix/spinnaker/swabbie/redis/RedisNotificationQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.redis 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.fasterxml.jackson.module.kotlin.readValue 21 | import com.netflix.spinnaker.kork.jedis.RedisClientDelegate 22 | import com.netflix.spinnaker.kork.jedis.RedisClientSelector 23 | import com.netflix.spinnaker.swabbie.notifications.NotificationQueue 24 | import com.netflix.spinnaker.swabbie.notifications.NotificationTask 25 | import org.springframework.stereotype.Component 26 | 27 | /** 28 | * Stores notification tasks in a set 29 | */ 30 | @Component 31 | class RedisNotificationQueue( 32 | private val objectMapper: ObjectMapper, 33 | redisClientSelector: RedisClientSelector 34 | ) : NotificationQueue { 35 | private val NOTIFICATION_KEY = "{swabbie:notifications}" 36 | private val redisClientDelegate: RedisClientDelegate = redisClientSelector.primary("default") 37 | 38 | override fun add(notificationTask: NotificationTask) { 39 | redisClientDelegate.withCommandsClient { client -> 40 | client.sadd(NOTIFICATION_KEY, objectMapper.writeValueAsString(notificationTask)) 41 | } 42 | } 43 | 44 | override fun size(): Int { 45 | return redisClientDelegate.withCommandsClient> { client -> 46 | client.smembers(NOTIFICATION_KEY) 47 | }.size 48 | } 49 | 50 | override fun popAll(): List { 51 | val json = redisClientDelegate.withCommandsClient> { client -> 52 | client.spop(NOTIFICATION_KEY, size().toLong()) 53 | } ?: return emptyList() 54 | 55 | return objectMapper.readValue(json.toString()) 56 | } 57 | 58 | override fun isEmpty(): Boolean = size() == 0 59 | } 60 | -------------------------------------------------------------------------------- /swabbie-redis/src/main/kotlin/com/netflix/spinnaker/swabbie/redis/RedisUsedResourceRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.redis 20 | 21 | import com.netflix.spinnaker.kork.jedis.RedisClientDelegate 22 | import com.netflix.spinnaker.kork.jedis.RedisClientSelector 23 | import com.netflix.spinnaker.swabbie.repository.UsedResourceRepository 24 | import java.util.concurrent.TimeUnit 25 | import org.slf4j.LoggerFactory 26 | import org.springframework.beans.factory.annotation.Value 27 | import org.springframework.stereotype.Component 28 | 29 | @Component 30 | class RedisUsedResourceRepository( 31 | redisClientSelector: RedisClientSelector 32 | ) : UsedResourceRepository { 33 | 34 | private val redisClientDelegate: RedisClientDelegate = redisClientSelector.primary("default") 35 | private val log = LoggerFactory.getLogger(javaClass) 36 | 37 | @Value("\${swabbie.repository.used-resource-repository.retention-ttl:172800}") 38 | private var expTime = TimeUnit.DAYS.toSeconds(2).toInt() 39 | 40 | init { 41 | log.info("Using ${javaClass.simpleName}") 42 | } 43 | 44 | override fun recordUse(resourceType: String, id: String, namespace: String) { 45 | redisClientDelegate.withCommandsClient { client -> 46 | client.setex(makeKey(resourceType, id), expTime, namespace) 47 | } 48 | } 49 | 50 | override fun isUsed(resourceType: String, id: String, namespace: String): Boolean { 51 | return redisClientDelegate.withCommandsClient { client -> 52 | val ns = client.get(makeKey(resourceType, id)) 53 | ns != null && ns == namespace 54 | } 55 | } 56 | 57 | private fun makeKey(resourceType: String, id: String): String { 58 | return "{swabbie:used:$resourceType}:$id" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /swabbie-front50/src/main/kotlin/com/netflix/spinnaker/config/Front50Configuration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.config 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.netflix.spinnaker.okhttp.SpinnakerRequestInterceptor 21 | import com.netflix.spinnaker.swabbie.front50.Front50Service 22 | import com.netflix.spinnaker.swabbie.retrofit.SwabbieRetrofitConfiguration 23 | import org.springframework.beans.factory.annotation.Value 24 | import org.springframework.context.annotation.Bean 25 | import org.springframework.context.annotation.ComponentScan 26 | import org.springframework.context.annotation.Configuration 27 | import org.springframework.context.annotation.Import 28 | import retrofit.Endpoint 29 | import retrofit.Endpoints 30 | import retrofit.RestAdapter 31 | import retrofit.client.Client 32 | import retrofit.converter.JacksonConverter 33 | 34 | @Configuration 35 | @Import(SwabbieRetrofitConfiguration::class) 36 | @ComponentScan("com.netflix.spinnaker.swabbie.front50") 37 | open class Front50Configuration { 38 | @Bean 39 | open fun front50Endpoint(@Value("\${front50.base-url}") front50Url: String): Endpoint = Endpoints.newFixedEndpoint(front50Url) 40 | 41 | @Bean 42 | open fun front50Service( 43 | front50Endpoint: Endpoint, 44 | objectMapper: ObjectMapper, 45 | retrofitClient: Client, 46 | spinnakerRequestInterceptor: SpinnakerRequestInterceptor, 47 | retrofitLogLevel: RestAdapter.LogLevel 48 | ): Front50Service = RestAdapter.Builder() 49 | .setRequestInterceptor(spinnakerRequestInterceptor) 50 | .setEndpoint(front50Endpoint) 51 | .setClient(retrofitClient) 52 | .setLogLevel(retrofitLogLevel) 53 | .setConverter(JacksonConverter(objectMapper)) 54 | .build() 55 | .create(Front50Service::class.java) 56 | } 57 | -------------------------------------------------------------------------------- /swabbie-web/src/main/kotlin/com/netflix/spinnaker/swabbie/Main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie 18 | 19 | import com.netflix.spinnaker.kork.PlatformComponents 20 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration 21 | import org.springframework.boot.builder.SpringApplicationBuilder 22 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer 23 | import org.springframework.context.annotation.ComponentScan 24 | import org.springframework.context.annotation.Configuration 25 | import org.springframework.context.annotation.Import 26 | import org.springframework.scheduling.annotation.EnableAsync 27 | import org.springframework.scheduling.annotation.EnableScheduling 28 | 29 | object MainDefaults { 30 | val PROPS = mapOf( 31 | "netflix.environment" to "test", 32 | "netflix.account" to "\${netflix.environment}", 33 | "netflix.stack" to "test", 34 | "spring.config.location" to "\${user.home}/.spinnaker/", 35 | "spring.application.name" to "swabbie", 36 | "spring.config.name" to "spinnaker,\${spring.application.name}", 37 | "spring.profiles.active" to "\${netflix.environment},local" 38 | ) 39 | } 40 | 41 | @Configuration 42 | @EnableAsync 43 | @EnableAutoConfiguration 44 | @EnableScheduling 45 | @Import(PlatformComponents::class) 46 | @ComponentScan(basePackages = arrayOf("com.netflix.spinnaker.config", "com.netflix.spinnaker.swabbie.web.config")) 47 | open class Main : SpringBootServletInitializer() { 48 | override fun configure(builder: SpringApplicationBuilder): SpringApplicationBuilder = builder.properties(MainDefaults.PROPS).sources(Main::class.java) 49 | } 50 | 51 | fun main(args: Array) { 52 | SpringApplicationBuilder().properties(MainDefaults.PROPS).sources(Main::class.java).run(*args) 53 | } 54 | -------------------------------------------------------------------------------- /swabbie-clouddriver/src/main/kotlin/com/netflix/spinnaker/config/ClouddriverConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.config 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.jakewharton.retrofit.Ok3Client 21 | import com.netflix.spinnaker.config.okhttp3.OkHttpClientProvider 22 | import com.netflix.spinnaker.okhttp.SpinnakerRequestInterceptor 23 | import com.netflix.spinnaker.swabbie.clouddriver.CloudDriverService 24 | import com.netflix.spinnaker.swabbie.retrofit.SwabbieRetrofitConfiguration 25 | import org.springframework.beans.factory.annotation.Value 26 | import org.springframework.context.annotation.Bean 27 | import org.springframework.context.annotation.Configuration 28 | import org.springframework.context.annotation.Import 29 | import retrofit.Endpoint 30 | import retrofit.Endpoints 31 | import retrofit.RestAdapter 32 | import retrofit.client.Client 33 | import retrofit.converter.JacksonConverter 34 | 35 | @Configuration 36 | @Import(SwabbieRetrofitConfiguration::class) 37 | open class ClouddriverConfiguration { 38 | @Bean 39 | open fun clouddriverEndpoint(@Value("\${clouddriver.base-url}") clouddriverBaseUrl: String) = Endpoints.newFixedEndpoint(clouddriverBaseUrl) 40 | 41 | @Bean 42 | open fun clouddriverService( 43 | clouddriverEndpoint: Endpoint, 44 | objectMapper: ObjectMapper, 45 | retrofitClient: Client, 46 | spinnakerRequestInterceptor: SpinnakerRequestInterceptor, 47 | retrofitLogLevel: RestAdapter.LogLevel 48 | ) = RestAdapter.Builder() 49 | .setRequestInterceptor(spinnakerRequestInterceptor) 50 | .setEndpoint(clouddriverEndpoint) 51 | .setClient(retrofitClient) 52 | .setLogLevel(retrofitLogLevel) 53 | .setConverter(JacksonConverter(objectMapper)) 54 | .build() 55 | .create(CloudDriverService::class.java) 56 | } 57 | -------------------------------------------------------------------------------- /swabbie-test/src/main/kotlin/com/netflix/spinnaker/swabbie/test/WorkConfigurationTestHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.test 20 | 21 | import com.netflix.spinnaker.config.Exclusion 22 | import com.netflix.spinnaker.config.NotificationConfiguration 23 | import com.netflix.spinnaker.swabbie.model.EmptyNotificationConfiguration 24 | import com.netflix.spinnaker.swabbie.model.SpinnakerAccount 25 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 26 | 27 | object WorkConfigurationTestHelper { 28 | fun generateWorkConfiguration( 29 | exclusions: List = emptyList(), 30 | dryRun: Boolean = false, 31 | itemsProcessedBatchSize: Int = 1, 32 | maxItemsProcessedPerCycle: Int = 10, 33 | retention: Int = 14, 34 | notificationConfiguration: NotificationConfiguration = EmptyNotificationConfiguration(), 35 | resourceType: String = TEST_RESOURCE_TYPE, 36 | cloudProvider: String = TEST_RESOURCE_PROVIDER_TYPE, 37 | namespace: String = "$cloudProvider:test:us-east-1:$resourceType" 38 | ): WorkConfiguration = WorkConfiguration( 39 | namespace = namespace, 40 | account = testAccount, 41 | location = "us-east-1", 42 | cloudProvider = cloudProvider, 43 | resourceType = resourceType, 44 | retention = retention, 45 | exclusions = exclusions.toSet(), 46 | dryRun = dryRun, 47 | maxAge = 0, 48 | itemsProcessedBatchSize = itemsProcessedBatchSize, 49 | maxItemsProcessedPerCycle = maxItemsProcessedPerCycle, 50 | notificationConfiguration = notificationConfiguration 51 | ) 52 | 53 | private val testAccount = SpinnakerAccount( 54 | name = "test", 55 | accountId = "id", 56 | type = "type", 57 | edda = "", 58 | regions = emptyList(), 59 | eddaEnabled = false, 60 | environment = "test" 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /swabbie-aws/src/test/java/com/netflix/spinnaker/swabbie/aws/ExpiredResourceRuleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws 18 | 19 | import com.netflix.spinnaker.kork.test.time.MutableClock 20 | import com.netflix.spinnaker.swabbie.aws.autoscalinggroups.AmazonAutoScalingGroup 21 | import java.time.Duration 22 | import java.time.Instant 23 | import org.junit.jupiter.api.BeforeEach 24 | import org.junit.jupiter.api.Test 25 | import strikt.api.expectThat 26 | import strikt.assertions.isNotNull 27 | import strikt.assertions.isNull 28 | 29 | object ExpiredResourceRuleTest { 30 | private val clock = MutableClock() 31 | private val subject = ExpiredResourceRule(clock) 32 | private val now = Instant.now(clock).toEpochMilli() 33 | private val asg = AmazonAutoScalingGroup( 34 | autoScalingGroupName = "testapp-v001", 35 | instances = listOf( 36 | mapOf("instanceId" to "i-01234") 37 | ), 38 | loadBalancerNames = listOf(), 39 | createdTime = now 40 | ) 41 | 42 | @BeforeEach 43 | fun setup() { 44 | asg.set("tags", null) 45 | } 46 | 47 | @Test 48 | fun `should not apply if resource is not tagged with a ttl`() { 49 | expectThat( 50 | subject.apply(asg).summary 51 | ).isNull() 52 | } 53 | 54 | @Test 55 | fun `should not apply if resource is not expired`() { 56 | val tags = listOf( 57 | mapOf("ttl" to "4d") 58 | ) 59 | 60 | asg.set("tags", tags) 61 | expectThat( 62 | subject.apply(asg).summary 63 | ).isNull() 64 | } 65 | 66 | @Test 67 | fun `should apply if resource is expired`() { 68 | val tags = listOf( 69 | mapOf("ttl" to "2d") 70 | ) 71 | 72 | asg.set("tags", tags) 73 | 74 | clock.incrementBy(Duration.ofDays(3)) 75 | expectThat( 76 | subject.apply(asg).summary 77 | ).isNotNull() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/model/AmazonResource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.model 18 | 19 | import com.netflix.frigga.ami.AppVersion 20 | import com.netflix.spinnaker.moniker.frigga.FriggaReflectiveNamer 21 | import com.netflix.spinnaker.swabbie.Dates 22 | import com.netflix.spinnaker.swabbie.model.Grouping 23 | import com.netflix.spinnaker.swabbie.model.GroupingType 24 | import com.netflix.spinnaker.swabbie.model.Resource 25 | import java.time.LocalDateTime 26 | import java.time.ZoneId 27 | 28 | abstract class AmazonResource( 29 | creationDate: String? // ISO_LOCAL_DATE_TIME format 30 | ) : Resource() { 31 | 32 | override val grouping: Grouping? 33 | get() { 34 | if (resourceType.contains("image", ignoreCase = true) || resourceType.contains("snapshot", ignoreCase = true)) { 35 | // Images and snapshots have only packageName, not app, to group by 36 | getTagValue("appversion")?.let { AppVersion.parseName(it as String)?.packageName }?.let { packageName -> 37 | return Grouping(packageName, GroupingType.PACKAGE_NAME) 38 | } 39 | return null 40 | } else { 41 | FriggaReflectiveNamer().deriveMoniker(this).app?.let { app -> 42 | return Grouping(app, GroupingType.APPLICATION) 43 | } 44 | return null 45 | } 46 | } 47 | 48 | override val createTs: Long = 49 | if (!creationDate.isNullOrBlank()) 50 | Dates.toLocalDateTime(creationDate!!) 51 | .atZone(ZoneId.systemDefault()) 52 | .toInstant() 53 | .toEpochMilli() 54 | else 55 | LocalDateTime.now() 56 | .minusYears(3) // falling back to 3 years prior if creationDate is nil to support legacy resources 57 | .atZone(ZoneId.systemDefault()) 58 | .toInstant() 59 | .toEpochMilli() 60 | } 61 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/exclusions/AmazonTagExclusionPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.exclusions 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | import com.netflix.spinnaker.config.ExclusionType 21 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 22 | import com.netflix.spinnaker.swabbie.exclusions.Excludable 23 | import com.netflix.spinnaker.swabbie.exclusions.ResourceExclusionPolicy 24 | import com.netflix.spinnaker.swabbie.model.BasicTag 25 | import java.time.Clock 26 | import org.springframework.stereotype.Component 27 | 28 | @Component 29 | class AmazonTagExclusionPolicy( 30 | val clock: Clock 31 | ) : ResourceExclusionPolicy { 32 | override fun getType(): ExclusionType = ExclusionType.Tag 33 | override fun apply(excludable: Excludable, exclusions: List): String? { 34 | if (excludable !is AmazonResource || excludable.tags().isNullOrEmpty()) { 35 | return null 36 | } 37 | 38 | val tags = excludable.tags()!! 39 | // Exclude this resource if it's tagged with a ttl but has not yet expired 40 | val temporalTags = tags.filter(BasicTag::isTemporal) 41 | if (temporalTags.isNotEmpty() && !excludable.expired(clock)) { 42 | val keysAsString = temporalTags.map { it.key }.joinToString { "," } 43 | val valuesAsString = temporalTags.map { it.value }.toString() 44 | return patternMatchMessage(keysAsString, setOf(valuesAsString)) 45 | } 46 | 47 | val configuredKeysAndTargetValues = keysAndValues(exclusions, ExclusionType.Tag) 48 | tags.forEach { tag -> 49 | val target = configuredKeysAndTargetValues[tag.key] ?: emptyList() 50 | if (tag.value in target) { 51 | return patternMatchMessage(tag.key, setOf(tag.value.toString())) 52 | } 53 | } 54 | 55 | return null 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/caches/AmazonLaunchConfigurationCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.aws.caches 20 | 21 | import com.netflix.spinnaker.security.AuthenticatedRequest 22 | import com.netflix.spinnaker.swabbie.Cacheable 23 | import com.netflix.spinnaker.swabbie.CachedViewProvider 24 | import com.netflix.spinnaker.swabbie.InMemorySingletonCache 25 | import com.netflix.spinnaker.swabbie.aws.Parameters 26 | import com.netflix.spinnaker.swabbie.aws.launchconfigurations.AmazonLaunchConfiguration 27 | 28 | open class AmazonLaunchConfigurationInMemoryCache( 29 | private val provider: CachedViewProvider 30 | ) : InMemorySingletonCache( 31 | { AuthenticatedRequest.allowAnonymous(provider::load) } 32 | ) 33 | 34 | data class AmazonLaunchConfigurationCache( 35 | private val refdAmisByRegion: Map>>, 36 | private val lastUpdated: Long, 37 | override val name: String? 38 | ) : Cacheable { 39 | fun getLaunchConfigsByRegionForImage(params: Parameters): Set { 40 | if (params.region != "" && params.id != "") { 41 | return getRefdAmisForRegion(params.region).getOrDefault(params.id, emptySet()) 42 | } else { 43 | throw IllegalArgumentException("Missing required region and id parameters") 44 | } 45 | } 46 | 47 | /** 48 | * @param region: AWS region 49 | * 50 | * Returns a map of 51 | */ 52 | fun getRefdAmisForRegion(region: String): Map> { 53 | return refdAmisByRegion[region].orEmpty() 54 | } 55 | 56 | fun getLastUpdated(): Long { 57 | return lastUpdated 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /swabbie-echo/src/test/kotlin/com/netflix/spinnaker/swabbie/echo/EchoNotifierTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.echo 18 | 19 | import com.netflix.spinnaker.config.NotificationConfiguration 20 | import com.netflix.spinnaker.swabbie.model.MarkedResource 21 | import com.netflix.spinnaker.swabbie.model.Summary 22 | import com.netflix.spinnaker.swabbie.notifications.Notifier 23 | import com.netflix.spinnaker.swabbie.test.TestResource 24 | import com.netflix.spinnaker.swabbie.test.WorkConfigurationTestHelper 25 | import org.junit.jupiter.api.Test 26 | import org.mockito.kotlin.any 27 | import org.mockito.kotlin.mock 28 | import org.mockito.kotlin.verify 29 | import strikt.api.expectThat 30 | import strikt.assertions.isEqualTo 31 | 32 | object EchoNotifierTest { 33 | private val echoService = mock() 34 | private val subject = EchoNotifier(echoService) 35 | 36 | @Test 37 | fun `should notify`() { 38 | val now = System.currentTimeMillis() 39 | val workConfiguration = WorkConfigurationTestHelper.generateWorkConfiguration( 40 | notificationConfiguration = NotificationConfiguration(types = listOf(Notifier.NotificationType.EMAIL.name)) 41 | ) 42 | 43 | val markedResource = MarkedResource( 44 | resource = TestResource(resourceId = "1"), 45 | summaries = listOf(Summary("invalid resource", "rule x")), 46 | namespace = workConfiguration.namespace, 47 | markTs = now, 48 | projectedDeletionStamp = now, 49 | resourceOwner = "test@netflix.com" 50 | ) 51 | 52 | val result = subject.notify( 53 | recipient = markedResource.resourceOwner, 54 | notificationContext = mapOf(), 55 | notificationConfiguration = workConfiguration.notificationConfiguration 56 | ) 57 | 58 | verify(echoService).create(any()) 59 | expectThat(result.success).isEqualTo(true) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/CacheStatus.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.swabbie 2 | 3 | import java.util.concurrent.Executors 4 | import java.util.concurrent.TimeUnit 5 | import java.util.concurrent.atomic.AtomicReference 6 | import javax.annotation.PreDestroy 7 | import org.slf4j.Logger 8 | import org.slf4j.LoggerFactory 9 | import org.springframework.stereotype.Component 10 | 11 | interface CacheStatus { 12 | fun cachesLoaded(): Boolean 13 | } 14 | 15 | @Component 16 | open class InMemoryCacheStatus( 17 | private val caches: List>, 18 | private val singletonCaches: List> 19 | ) : CacheStatus { 20 | private var allLoaded = AtomicReference(false) 21 | private val monitorExecutor = Executors.newSingleThreadScheduledExecutor() 22 | private val cacheLoaderExecutor = Executors.newSingleThreadScheduledExecutor() 23 | private val log: Logger = LoggerFactory.getLogger(javaClass) 24 | 25 | init { 26 | refreshCaches() 27 | 28 | // monitor the status of caches. Sets allLoaded=true when all caches are filled. 29 | monitorCaches() 30 | } 31 | 32 | private fun monitorCaches() { 33 | monitorExecutor.scheduleWithFixedDelay( 34 | { 35 | try { 36 | if (!allLoaded.get()) { 37 | log.debug("All caches not loaded, checking cache status.") 38 | updateStatus() 39 | } 40 | } catch (e: Exception) { 41 | log.error("Failed while checking the cache status", e) 42 | } 43 | }, 44 | 0, 1, TimeUnit.MINUTES 45 | ) 46 | } 47 | 48 | private fun refreshCaches() { 49 | cacheLoaderExecutor.scheduleWithFixedDelay( 50 | { 51 | try { 52 | caches.forEach(Cache<*>::refresh) 53 | singletonCaches.forEach(SingletonCache<*>::refresh) 54 | } catch (e: Exception) { 55 | log.error("Failed while refreshing caches.", e) 56 | } 57 | }, 58 | 0, 15, TimeUnit.MINUTES 59 | ) 60 | } 61 | 62 | private fun updateStatus() { 63 | caches.forEach { cache -> 64 | if (!cache.loadingComplete()) return 65 | } 66 | 67 | singletonCaches.forEach { cache -> 68 | if (!cache.loadingComplete()) return 69 | } 70 | 71 | allLoaded.set(true) 72 | } 73 | 74 | @PreDestroy 75 | private fun shutdown() { 76 | monitorExecutor.shutdownNow() 77 | cacheLoaderExecutor.shutdownNow() 78 | } 79 | 80 | override fun cachesLoaded(): Boolean { 81 | return allLoaded.get() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/caches/AmazonLaunchTemplateVersionCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2020 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.aws.caches 20 | 21 | import com.netflix.spinnaker.security.AuthenticatedRequest 22 | import com.netflix.spinnaker.swabbie.Cacheable 23 | import com.netflix.spinnaker.swabbie.CachedViewProvider 24 | import com.netflix.spinnaker.swabbie.InMemorySingletonCache 25 | import com.netflix.spinnaker.swabbie.aws.Parameters 26 | import com.netflix.spinnaker.swabbie.aws.launchtemplates.AmazonLaunchTemplateVersion 27 | 28 | open class AmazonLaunchTemplateVersionInMemoryCache( 29 | private val provider: CachedViewProvider 30 | ) : InMemorySingletonCache( 31 | { AuthenticatedRequest.allowAnonymous(provider::load) } 32 | ) 33 | 34 | data class AmazonLaunchTemplateVersionCache( 35 | private val refdAmisByRegion: Map>>, 36 | private val lastUpdated: Long, 37 | override val name: String? 38 | ) : Cacheable { 39 | fun getLaunchTemplateVersionsByRegionForImage(params: Parameters): Set { 40 | if (params.region != "" && params.id != "") { 41 | return getRefdAmisForRegion(params.region).getOrDefault(params.id, emptySet()) 42 | } else { 43 | throw IllegalArgumentException("Missing required region and id parameters") 44 | } 45 | } 46 | 47 | /** 48 | * @param region: AWS region 49 | * 50 | * Returns a map of 51 | */ 52 | fun getRefdAmisForRegion(region: String): Map> { 53 | return refdAmisByRegion[region].orEmpty() 54 | } 55 | 56 | fun getLastUpdated(): Long { 57 | return lastUpdated 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /swabbie-echo/src/main/kotlin/com/netflix/spinnaker/config/EchoConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.config 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.jakewharton.retrofit.Ok3Client 21 | import com.netflix.spinnaker.config.okhttp3.OkHttpClientProvider 22 | import com.netflix.spinnaker.swabbie.echo.EchoService 23 | import com.netflix.spinnaker.swabbie.retrofit.SwabbieRetrofitConfiguration 24 | import org.slf4j.LoggerFactory 25 | import org.springframework.beans.factory.annotation.Value 26 | import org.springframework.context.annotation.Bean 27 | import org.springframework.context.annotation.ComponentScan 28 | import org.springframework.context.annotation.Configuration 29 | import org.springframework.context.annotation.Import 30 | import retrofit.Endpoint 31 | import retrofit.Endpoints 32 | import retrofit.RequestInterceptor 33 | import retrofit.RestAdapter 34 | import retrofit.client.Client 35 | import retrofit.converter.JacksonConverter 36 | 37 | @Configuration 38 | @ComponentScan("com.netflix.spinnaker.swabbie.echo") 39 | @Import(SwabbieRetrofitConfiguration::class) 40 | open class EchoConfiguration { 41 | private val log = LoggerFactory.getLogger(javaClass) 42 | 43 | @Bean 44 | open fun echoEndpoint(@Value("\${echo.base-url}") echoBaseUrl: String) = Endpoints.newFixedEndpoint(echoBaseUrl) 45 | 46 | @Bean 47 | open fun echoService( 48 | echoEndpoint: Endpoint, 49 | objectMapper: ObjectMapper, 50 | retrofitClient: Client, 51 | spinnakerRequestInterceptor: RequestInterceptor, 52 | retrofitLogLevel: RestAdapter.LogLevel 53 | ) = RestAdapter.Builder() 54 | .setRequestInterceptor(spinnakerRequestInterceptor) 55 | .setEndpoint(echoEndpoint) 56 | .setClient(retrofitClient) 57 | .setLogLevel(retrofitLogLevel) 58 | .setConverter(JacksonConverter(objectMapper)) 59 | .build() 60 | .create(EchoService::class.java) 61 | } 62 | -------------------------------------------------------------------------------- /swabbie-core/src/test/kotlin/com/netflix/spinnaker/swabbie/events/ResourceTrackingManagerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2018 Netflix, Inc. 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License") 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.events 20 | 21 | import com.netflix.spectator.api.NoopRegistry 22 | import com.netflix.spinnaker.swabbie.model.MarkedResource 23 | import com.netflix.spinnaker.swabbie.model.Summary 24 | import com.netflix.spinnaker.swabbie.repository.ResourceTrackingRepository 25 | import com.netflix.spinnaker.swabbie.test.TestResource 26 | import com.netflix.spinnaker.swabbie.test.WorkConfigurationTestHelper 27 | import org.junit.jupiter.api.AfterEach 28 | import org.junit.jupiter.api.Test 29 | import org.mockito.kotlin.argWhere 30 | import org.mockito.kotlin.mock 31 | import org.mockito.kotlin.reset 32 | import org.mockito.kotlin.verify 33 | import java.time.Clock 34 | 35 | object ResourceTrackingManagerTest { 36 | private val resourceTrackingRepository = mock() 37 | private val clock = Clock.systemDefaultZone() 38 | private val registry = NoopRegistry() 39 | 40 | private var resource = TestResource("testResource") 41 | private var configuration = WorkConfigurationTestHelper.generateWorkConfiguration() 42 | 43 | private var subject = ResourceTrackingManager( 44 | resourceTrackingRepository = resourceTrackingRepository 45 | ) 46 | 47 | @AfterEach 48 | fun cleanup() { 49 | reset(resourceTrackingRepository) 50 | } 51 | 52 | @Test 53 | fun `should update state on delete event`() { 54 | val markedResource = MarkedResource( 55 | resource = resource, 56 | summaries = listOf(Summary("violates rule 1", "ruleName")), 57 | namespace = configuration.namespace, 58 | projectedDeletionStamp = clock.millis() 59 | ) 60 | val event = DeleteResourceEvent(markedResource, configuration) 61 | 62 | subject.handleEvents(event) 63 | 64 | verify(resourceTrackingRepository).remove( 65 | argWhere { it == markedResource } 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /swabbie-orca/src/main/kotlin/com/netflix/spinnaker/config/OrcaConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.config 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.netflix.spinnaker.swabbie.orca.OrcaService 21 | import com.netflix.spinnaker.swabbie.orca.tagging.OrcaTaggingService 22 | import com.netflix.spinnaker.swabbie.retrofit.SwabbieRetrofitConfiguration 23 | import com.netflix.spinnaker.swabbie.tagging.TaggingService 24 | import org.springframework.beans.factory.annotation.Value 25 | import org.springframework.context.annotation.Bean 26 | import org.springframework.context.annotation.ComponentScan 27 | import org.springframework.context.annotation.Configuration 28 | import org.springframework.context.annotation.Import 29 | import retrofit.Endpoint 30 | import retrofit.Endpoints 31 | import retrofit.RequestInterceptor 32 | import retrofit.RestAdapter 33 | import retrofit.client.Client 34 | import retrofit.converter.JacksonConverter 35 | 36 | @Configuration 37 | @Import(SwabbieRetrofitConfiguration::class) 38 | @ComponentScan("com.netflix.spinnaker.swabbie.orca") 39 | open class OrcaConfiguration { 40 | @Bean 41 | open fun orcaEndpoint(@Value("\${orca.base-url}") orcaBaseUrl: String): Endpoint = 42 | Endpoints.newFixedEndpoint(orcaBaseUrl) 43 | 44 | @Bean 45 | open fun orcaService( 46 | orcaEndpoint: Endpoint, 47 | objectMapper: ObjectMapper, 48 | retrofitClient: Client, 49 | spinnakerRequestInterceptor: RequestInterceptor, 50 | retrofitLogLevel: RestAdapter.LogLevel 51 | ): OrcaService = 52 | RestAdapter.Builder() 53 | .setRequestInterceptor(spinnakerRequestInterceptor) 54 | .setEndpoint(orcaEndpoint) 55 | .setClient(retrofitClient) 56 | .setLogLevel(retrofitLogLevel) 57 | .setConverter(JacksonConverter(objectMapper)) 58 | .build() 59 | .create(OrcaService::class.java) 60 | 61 | @Bean 62 | open fun entityTaggingService(orcaService: OrcaService): TaggingService { 63 | return OrcaTaggingService(orcaService) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /swabbie-core/src/test/kotlin/com/netflix/spinnaker/swabbie/model/WorkConfigurationTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.swabbie.model 17 | 18 | import com.netflix.spinnaker.swabbie.events.Action 19 | import org.junit.jupiter.api.Test 20 | 21 | object WorkConfigurationTest { 22 | @Test 23 | fun `Should expand configuration to work items`() { 24 | val workConfiguration = WorkConfiguration( 25 | namespace = "workConfiguration1", 26 | account = SpinnakerAccount( 27 | name = "test", 28 | accountId = "id", 29 | type = "type", 30 | edda = "", 31 | regions = emptyList(), 32 | eddaEnabled = false, 33 | environment = "test" 34 | ), 35 | location = "us-east-1", 36 | cloudProvider = AWS, 37 | resourceType = "testResourceType", 38 | retention = 14, 39 | exclusions = emptySet(), 40 | maxAge = 1 41 | ) 42 | 43 | val workItems = workConfiguration.toWorkItems() 44 | assert(workItems.size == 3) 45 | 46 | with(workItems) { 47 | assert(listOf(Action.MARK, Action.NOTIFY, Action.DELETE) == map { it.action }) 48 | assert(map { it.workConfiguration }.toSet().size == 1) 49 | assert(setOf(workConfiguration) == map { it.workConfiguration }.toSet()) 50 | } 51 | } 52 | 53 | @Test 54 | fun `should only use enabled actions`() { 55 | val workConfiguration = WorkConfiguration( 56 | namespace = "workConfiguration1", 57 | account = SpinnakerAccount( 58 | name = "test", 59 | accountId = "id", 60 | type = "type", 61 | edda = "", 62 | regions = emptyList(), 63 | eddaEnabled = false, 64 | environment = "test" 65 | ), 66 | location = "us-east-1", 67 | cloudProvider = AWS, 68 | resourceType = "testResourceType", 69 | retention = 14, 70 | exclusions = emptySet(), 71 | maxAge = 1, 72 | enabledActions = listOf(Action.MARK) 73 | ) 74 | 75 | val workItems = workConfiguration.toWorkItems() 76 | assert(workItems.size == 1) 77 | assert(workItems[0].action == Action.MARK) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /swabbie-front50/src/main/kotlin/com/netflix/spinnaker/swabbie/front50/Front50ApplicationExclusionPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.front50 18 | 19 | import com.netflix.spinnaker.config.Exclusion 20 | import com.netflix.spinnaker.config.ExclusionType 21 | import com.netflix.spinnaker.swabbie.InMemoryCache 22 | import com.netflix.spinnaker.swabbie.exclusions.Excludable 23 | import com.netflix.spinnaker.swabbie.exclusions.ResourceExclusionPolicy 24 | import com.netflix.spinnaker.swabbie.model.Application 25 | import com.netflix.spinnaker.swabbie.model.Grouping 26 | import com.netflix.spinnaker.swabbie.model.GroupingType 27 | import org.springframework.stereotype.Component 28 | 29 | /** 30 | * todo eb: for images, we could find applications they belong to by seeing what images are running in each: 31 | * find the package name of the image by using Frigga AppVersion, 32 | * then build a cache of packageVersion to listOf(appName). 33 | * Also build a cache of appName to owner. 34 | * This would let us accurately determine which packages are in use by each app by querying both caches 35 | */ 36 | @Component 37 | class Front50ApplicationExclusionPolicy( 38 | private val front50ApplicationCache: InMemoryCache 39 | ) : ResourceExclusionPolicy { 40 | 41 | private fun findApplication(excludable: Excludable): Excludable? { 42 | val grouping: Grouping = excludable.grouping ?: return null 43 | if (grouping.type != GroupingType.APPLICATION) { 44 | return null 45 | } 46 | 47 | return front50ApplicationCache 48 | .get() 49 | .find { 50 | it.isNamed(grouping.value) 51 | } 52 | } 53 | 54 | private fun Application.isNamed(name: String): Boolean { 55 | return this.name.equals(name, ignoreCase = true) || this.name.matchPattern(name) 56 | } 57 | 58 | override fun getType(): ExclusionType = ExclusionType.Application 59 | 60 | override fun apply(excludable: Excludable, exclusions: List): String? { 61 | val application = findApplication(excludable) ?: return null 62 | return byPropertyMatchingResult(exclusions, application) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/model/Account.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.model 18 | 19 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize 20 | import com.netflix.spinnaker.swabbie.Cacheable 21 | import com.netflix.spinnaker.swabbie.exclusions.Excludable 22 | 23 | @JsonDeserialize(`as` = SpinnakerAccount::class) 24 | interface Account : Excludable { 25 | val accountId: String? 26 | val eddaEnabled: Boolean 27 | val edda: String? 28 | val type: String 29 | val regions: List? 30 | val environment: String 31 | } 32 | 33 | data class Region( 34 | val deprecated: Boolean = false, 35 | val name: String 36 | ) 37 | 38 | /** 39 | * An account managed by Spinnaker 40 | */ 41 | data class SpinnakerAccount( 42 | override val eddaEnabled: Boolean, 43 | override val accountId: String?, 44 | override val type: String, 45 | override val name: String, 46 | override val edda: String?, 47 | override val regions: List?, 48 | override val environment: String, 49 | override val grouping: Grouping? = null, 50 | val assumeRole: String = "role/spinnaker", 51 | val sessionName: String = "Spinnaker" 52 | ) : Account, Cacheable, HasDetails() { 53 | override val resourceId: String 54 | get() = accountId!! 55 | override val resourceType: String 56 | get() = "account" 57 | override val cloudProvider: String 58 | get() = type 59 | } 60 | 61 | /** 62 | * A placeholder account for things with no concept of an account 63 | */ 64 | data class EmptyAccount( 65 | override val accountId: String? = none, 66 | override val type: String = none, 67 | override val name: String = none, 68 | override val eddaEnabled: Boolean = false, 69 | override val edda: String? = none, 70 | override val regions: List = listOf(Region(false, none)), 71 | override val environment: String = none, 72 | override val grouping: Grouping? = null 73 | ) : Account, HasDetails() { 74 | override val resourceId: String 75 | get() = accountId!! 76 | override val resourceType: String 77 | get() = type 78 | override val cloudProvider: String 79 | get() = type 80 | } 81 | 82 | const val none = "none" 83 | -------------------------------------------------------------------------------- /swabbie-redis/src/main/kotlin/com/netflix/spinnaker/swabbie/redis/LockingConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.redis 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.netflix.spectator.api.Registry 21 | import com.netflix.spinnaker.config.LockingConfigurationProperties 22 | import com.netflix.spinnaker.kork.jedis.RedisClientSelector 23 | import com.netflix.spinnaker.kork.jedis.lock.RedisLockManager 24 | import com.netflix.spinnaker.kork.lock.LockManager 25 | import com.netflix.spinnaker.swabbie.LockingService 26 | import java.time.Clock 27 | import java.util.Optional 28 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean 29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 30 | import org.springframework.boot.context.properties.EnableConfigurationProperties 31 | import org.springframework.context.annotation.Bean 32 | import org.springframework.context.annotation.Configuration 33 | 34 | @Configuration 35 | @EnableConfigurationProperties(LockingConfigurationProperties::class) 36 | open class LockingConfiguration( 37 | private val lockingConfigurationProperties: LockingConfigurationProperties 38 | ) { 39 | 40 | @Bean 41 | @ConditionalOnProperty("locking.enabled") 42 | open fun lockingService(lockManager: LockManager): LockingService { 43 | return LockingService(lockManager, lockingConfigurationProperties) 44 | } 45 | 46 | @Bean 47 | @ConditionalOnMissingBean(LockManager::class) 48 | open fun noopLockingService(): LockingService { 49 | return LockingService.NOOP() 50 | } 51 | 52 | @Bean 53 | @ConditionalOnProperty("locking.enabled") 54 | open fun lockManager( 55 | clock: Clock, 56 | registry: Registry, 57 | objectMapper: ObjectMapper, 58 | redisClientSelector: RedisClientSelector 59 | ): RedisLockManager { 60 | return RedisLockManager( 61 | null, // Will default to node name 62 | clock, 63 | registry, 64 | objectMapper, 65 | redisClientSelector.primary("default"), 66 | Optional.of(lockingConfigurationProperties.heartbeatRateMillis), 67 | Optional.of(lockingConfigurationProperties.leaseDurationMillis) 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/caches/ImagesUsedByInstancesProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.caches 18 | 19 | import com.netflix.spinnaker.swabbie.AccountProvider 20 | import com.netflix.spinnaker.swabbie.CachedViewProvider 21 | import com.netflix.spinnaker.swabbie.aws.AWS 22 | import com.netflix.spinnaker.swabbie.aws.Parameters 23 | import com.netflix.spinnaker.swabbie.aws.instances.AmazonInstance 24 | import java.time.Clock 25 | import org.slf4j.Logger 26 | import org.slf4j.LoggerFactory 27 | 28 | class ImagesUsedByInstancesProvider( 29 | private val clock: Clock, 30 | private val accountProvider: AccountProvider, 31 | private val aws: AWS 32 | ) : CachedViewProvider, AWS by aws { 33 | private val log: Logger = LoggerFactory.getLogger(javaClass) 34 | override fun load(): AmazonImagesUsedByInstancesCache { 35 | log.info("Loading cache for ${javaClass.simpleName}") 36 | val refdAmisByRegion = mutableMapOf>() 37 | accountProvider.getAccounts() 38 | .filter { it.cloudProvider == "aws" && !it.regions.isNullOrEmpty() } 39 | .forEach { account -> 40 | account.regions!!.forEach { region -> 41 | // we need to read all the instances in every region, no matter if it's deprecated or not, 42 | // because we need to see all the AMIs that are in use. 43 | log.info("Reading instances in {}/{}/{}", account.accountId, region.name, account.environment) 44 | val instances: List = getInstances( 45 | Parameters( 46 | region = region.name, 47 | account = account.accountId!!, 48 | environment = account.environment 49 | ) 50 | ) 51 | 52 | val refdAmis: Set = instances 53 | .map { it.imageId } 54 | .toSet() 55 | 56 | val currentAmis = refdAmisByRegion.getOrDefault(region.name, mutableSetOf()) 57 | currentAmis.addAll(refdAmis) 58 | refdAmisByRegion[region.name] = currentAmis 59 | } 60 | } 61 | 62 | return AmazonImagesUsedByInstancesCache(refdAmisByRegion, clock.millis(), "default") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/snapshots/AmazonSnapshot.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License") 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.spinnaker.swabbie.aws.snapshots 20 | 21 | import com.fasterxml.jackson.annotation.JsonTypeName 22 | import com.netflix.spinnaker.swabbie.Dates 23 | import com.netflix.spinnaker.swabbie.aws.model.AmazonResource 24 | import com.netflix.spinnaker.swabbie.model.AWS 25 | import com.netflix.spinnaker.swabbie.model.SNAPSHOT 26 | import java.time.Instant 27 | import java.time.ZoneId 28 | 29 | @JsonTypeName("amazonSnapshot") 30 | class AmazonSnapshot( 31 | val volumeId: String, 32 | val state: String, 33 | val progress: String, 34 | val volumeSize: Int, 35 | startTime: Any, 36 | val description: String?, 37 | val snapshotId: String, 38 | val ownerId: String, 39 | val encrypted: Boolean, 40 | val ownerAlias: String?, 41 | val stateMessage: String?, 42 | override val resourceId: String = snapshotId, 43 | override val resourceType: String = SNAPSHOT, 44 | override val cloudProvider: String = AWS, 45 | override val name: String = snapshotId, 46 | creationDate: String? = getCreationDate(startTime) 47 | ) : AmazonResource(creationDate) { 48 | val startTime: Long = convertStartTime(startTime) 49 | 50 | /** 51 | * Amazon returns [startTime] as a String (date), whereas edda returns it as a Long. 52 | * This allows either of those formats to be accepted and used successfully. 53 | */ 54 | companion object { 55 | private fun convertStartTime(value: Any): Long { 56 | return when (value) { 57 | is String -> 58 | Dates 59 | .toLocalDateTime(value) 60 | .toInstant(ZoneId.systemDefault().rules.getOffset(Instant.now())) 61 | .toEpochMilli() 62 | is Long -> value 63 | else -> throw IllegalArgumentException("Start time must be String (date) or Long, but given ${value.javaClass}") 64 | } 65 | } 66 | 67 | private fun getCreationDate(value: Any): String? { 68 | return when (value) { 69 | is String -> value 70 | is Long -> Dates.toCreationDate(value) 71 | else -> throw IllegalArgumentException("Start time must be String (date) or Long, but given ${value.javaClass}") 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /swabbie-redis/src/main/kotlin/com/netflix/spinnaker/swabbie/redis/RedisWorkQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.redis 18 | 19 | import com.netflix.spinnaker.kork.jedis.RedisClientDelegate 20 | import com.netflix.spinnaker.kork.jedis.RedisClientSelector 21 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 22 | import com.netflix.spinnaker.swabbie.model.WorkItem 23 | import com.netflix.spinnaker.swabbie.work.WorkQueue 24 | import org.slf4j.LoggerFactory 25 | import org.springframework.stereotype.Component 26 | 27 | /** 28 | * Stores an list of [com.netflix.spinnaker.swabbie.model.WorkItem] 29 | * @property _seed work configurations defined in the application config from which to derive work items 30 | * @see [com.netflix.spinnaker.swabbie.work.WorkQueueManager] 31 | */ 32 | @Component 33 | class RedisWorkQueue( 34 | redisClientSelector: RedisClientSelector, 35 | private val _seed: List 36 | ) : WorkQueue { 37 | private val log = LoggerFactory.getLogger(javaClass) 38 | private val workItems: List = _seed.map { it.toWorkItems() }.flatten() 39 | private val redisClientDelegate: RedisClientDelegate = redisClientSelector.primary("default") 40 | 41 | init { 42 | log.info("Using ${javaClass.simpleName}") 43 | } 44 | 45 | override fun seed() { 46 | log.debug("Seeding work queue with ${workItems.map { "${it.action} in ${it.id}" }}") 47 | workItems.forEach { 48 | push(it) 49 | } 50 | } 51 | 52 | /** 53 | * Pops a [WorkItem] off of the [WorkQueue] 54 | */ 55 | override fun pop(): WorkItem? { 56 | redisClientDelegate.withCommandsClient { client -> 57 | client.spop(WORK_KEY) 58 | }?.let { id -> 59 | return workItems.find { id == it.id } 60 | } 61 | 62 | return null 63 | } 64 | 65 | override fun push(workItem: WorkItem) { 66 | redisClientDelegate.withCommandsClient { client -> 67 | client.sadd(WORK_KEY, workItem.id) 68 | } 69 | } 70 | 71 | override fun isEmpty(): Boolean = size() == 0 72 | 73 | override fun size(): Int { 74 | return redisClientDelegate.withCommandsClient> { client -> 75 | client.smembers(WORK_KEY) 76 | }.size 77 | } 78 | } 79 | 80 | private const val WORK_KEY = "{swabbie:work}" 81 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/events/Event.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.events 18 | 19 | import com.netflix.spinnaker.swabbie.model.MarkedResource 20 | import com.netflix.spinnaker.swabbie.model.WorkConfiguration 21 | 22 | abstract class Event( 23 | open val action: Action, 24 | open val markedResource: MarkedResource, 25 | open val workConfiguration: WorkConfiguration 26 | ) { 27 | override fun equals(other: Any?): Boolean { 28 | if (other is Event) { 29 | return action == other && other.markedResource == markedResource && 30 | workConfiguration == other.workConfiguration 31 | } 32 | return super.equals(other) 33 | } 34 | 35 | override fun hashCode(): Int { 36 | var result = action.name.hashCode() 37 | result = 31 * result + markedResource.hashCode() 38 | result = 31 * result + workConfiguration.hashCode() 39 | return result 40 | } 41 | } 42 | 43 | enum class Action { 44 | MARK, UNMARK, DELETE, NOTIFY, OPTOUT 45 | } 46 | 47 | class OwnerNotifiedEvent( 48 | override val markedResource: MarkedResource, 49 | override val workConfiguration: WorkConfiguration 50 | ) : Event(Action.NOTIFY, markedResource, workConfiguration) 51 | 52 | class UnMarkResourceEvent( 53 | override val markedResource: MarkedResource, 54 | override val workConfiguration: WorkConfiguration 55 | ) : Event(Action.UNMARK, markedResource, workConfiguration) 56 | 57 | class MarkResourceEvent( 58 | override val markedResource: MarkedResource, 59 | override val workConfiguration: WorkConfiguration 60 | ) : Event(Action.MARK, markedResource, workConfiguration) 61 | 62 | class DeleteResourceEvent( 63 | override val markedResource: MarkedResource, 64 | override val workConfiguration: WorkConfiguration 65 | ) : Event(Action.DELETE, markedResource, workConfiguration) 66 | 67 | class OptOutResourceEvent( 68 | override val markedResource: MarkedResource, 69 | override val workConfiguration: WorkConfiguration 70 | ) : Event(Action.OPTOUT, markedResource, workConfiguration) 71 | 72 | class OrcaTaskFailureEvent( 73 | override val action: Action, 74 | override val markedResource: MarkedResource, 75 | override val workConfiguration: WorkConfiguration 76 | ) : Event(action, markedResource, workConfiguration) 77 | -------------------------------------------------------------------------------- /swabbie-aws/src/test/java/com/netflix/spinnaker/swabbie/aws/autoscalinggroups/DisabledLoadBalancerRuleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.autoscalinggroups 18 | 19 | import com.amazonaws.services.autoscaling.model.SuspendedProcess 20 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 21 | import com.netflix.spinnaker.kork.test.time.MutableClock 22 | import java.time.Duration 23 | import java.time.LocalDateTime 24 | import org.junit.jupiter.api.Test 25 | import strikt.api.expectThat 26 | import strikt.assertions.isNotNull 27 | import strikt.assertions.isNull 28 | 29 | object DisabledLoadBalancerRuleTest { 30 | private val clock = MutableClock() 31 | 32 | @Test 33 | fun `should apply when server group's load balancer has been suspended`() { 34 | val disabledTime = LocalDateTime.now(clock) 35 | val suspendedProcess = SuspendedProcess() 36 | .withProcessName("AddToLoadBalancer") 37 | .withSuspensionReason("User suspended at $disabledTime") 38 | 39 | val asg = AmazonAutoScalingGroup( 40 | autoScalingGroupName = "testapp-v001", 41 | instances = listOf(), 42 | loadBalancerNames = listOf(), 43 | suspendedProcesses = listOf( 44 | suspendedProcess 45 | ), 46 | createdTime = clock.millis() 47 | ) 48 | 49 | clock.incrementBy(Duration.ofDays(2)) 50 | val rule = DisabledLoadBalancerRule(clock) 51 | 52 | expectThat(rule.apply(asg).summary).isNotNull() 53 | 54 | var ruleDefinition = RuleDefinition() 55 | .apply { 56 | name = rule.name() 57 | parameters = mapOf("moreThanDays" to 3) 58 | } 59 | 60 | expectThat(rule.apply(asg, ruleDefinition).summary).isNull() 61 | 62 | ruleDefinition = RuleDefinition() 63 | .apply { 64 | name = rule.name() 65 | parameters = mapOf("moreThanDays" to 1) 66 | } 67 | 68 | expectThat(rule.apply(asg, ruleDefinition).summary).isNotNull() 69 | 70 | ruleDefinition = RuleDefinition() 71 | .apply { 72 | name = rule.name() 73 | } 74 | 75 | // When no parameter passed, rule should still assert that resource is disabled 76 | expectThat(rule.apply(asg, ruleDefinition).summary).isNotNull() 77 | expectThat(rule.apply(asg.copy(suspendedProcesses = emptyList()), ruleDefinition).summary).isNull() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/Cacheable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie 18 | 19 | import com.netflix.spinnaker.swabbie.model.Named 20 | import java.util.concurrent.atomic.AtomicReference 21 | import org.slf4j.Logger 22 | import org.slf4j.LoggerFactory 23 | 24 | interface Cacheable : Named 25 | 26 | interface Cache { 27 | fun get(): Set 28 | fun contains(key: String?): Boolean 29 | fun loadingComplete(): Boolean 30 | fun refresh() 31 | } 32 | 33 | interface SingletonCache { 34 | fun get(): T 35 | fun loadingComplete(): Boolean 36 | fun refresh() 37 | } 38 | 39 | open class InMemoryCache( 40 | private val sourceProvider: () -> Set 41 | ) : Cache { 42 | private val cache = AtomicReference>() 43 | val log: Logger = LoggerFactory.getLogger(javaClass) 44 | 45 | override fun contains(key: String?): Boolean { 46 | if (key == null) return false 47 | return get().find { it.name == key } != null 48 | } 49 | 50 | override fun refresh() { 51 | try { 52 | log.info("Refreshing cache ${javaClass.simpleName}") 53 | cache.set(sourceProvider.invoke()) 54 | } catch (e: Exception) { 55 | log.error("Error refreshing cache ${javaClass.name}", e) 56 | } 57 | } 58 | 59 | override fun get(): Set { 60 | if (cache.get() == null) { 61 | cache.set(sourceProvider.invoke()) 62 | } 63 | 64 | return cache.get() 65 | } 66 | 67 | override fun loadingComplete(): Boolean { 68 | return cache.get() != null 69 | } 70 | } 71 | 72 | open class InMemorySingletonCache( 73 | private val sourceProvider: () -> T 74 | ) : SingletonCache { 75 | private val cache = AtomicReference() 76 | val log: Logger = LoggerFactory.getLogger(javaClass) 77 | 78 | override fun refresh() { 79 | try { 80 | log.info("Refreshing cache ${javaClass.name}") 81 | cache.set(sourceProvider.invoke()) 82 | } catch (e: Exception) { 83 | log.error("Error refreshing cache ${javaClass.name}", e) 84 | } 85 | } 86 | 87 | override fun get(): T { 88 | if (cache.get() == null) { 89 | cache.set(sourceProvider.invoke()) 90 | } 91 | return cache.get() 92 | } 93 | 94 | override fun loadingComplete(): Boolean { 95 | return cache.get() != null 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /swabbie-aws/src/test/java/com/netflix/spinnaker/swabbie/aws/autoscalinggroups/ZeroInstanceDisabledServerGroupRuleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.autoscalinggroups 18 | 19 | import com.amazonaws.services.autoscaling.model.SuspendedProcess 20 | import java.time.Clock 21 | import java.time.Instant 22 | import java.time.ZoneOffset 23 | import java.time.temporal.ChronoUnit 24 | import org.junit.jupiter.api.Assertions 25 | import org.junit.jupiter.api.Test 26 | 27 | object ZeroInstanceDisabledServerGroupRuleTest { 28 | 29 | private val clock = Clock.fixed(Instant.now(), ZoneOffset.UTC) 30 | 31 | @Test 32 | fun `should not apply to non disabled server groups`() { 33 | val asg = AmazonAutoScalingGroup( 34 | autoScalingGroupName = "testapp-v001", 35 | instances = listOf( 36 | mapOf("instanceId" to "i-01234") 37 | ), 38 | loadBalancerNames = listOf("lb1"), 39 | createdTime = clock.millis() 40 | ) 41 | 42 | val result = ZeroInstanceDisabledServerGroupRule(clock).apply(asg) 43 | Assertions.assertNull(result.summary) 44 | } 45 | 46 | @Test 47 | fun `should apply when server group is disabled with no instances`() { 48 | val suspendedProcess = SuspendedProcess() 49 | suspendedProcess.withProcessName("AddToLoadBalancer") 50 | suspendedProcess.withSuspensionReason("User suspended at 2019-09-03T17:29:07Z") 51 | val asg = AmazonAutoScalingGroup( 52 | autoScalingGroupName = "testapp-v001", 53 | instances = listOf(), 54 | loadBalancerNames = listOf(), 55 | suspendedProcesses = listOf( 56 | suspendedProcess 57 | ), 58 | createdTime = Instant.now(clock).minus(35, ChronoUnit.DAYS).toEpochMilli() 59 | ) 60 | 61 | val result = ZeroInstanceDisabledServerGroupRule(clock).apply(asg) 62 | Assertions.assertNotNull(result.summary) 63 | } 64 | 65 | @Test 66 | fun `should not apply when server group has been disabled for less than maxage `() { 67 | val asg = AmazonAutoScalingGroup( 68 | autoScalingGroupName = "testapp-v001", 69 | instances = listOf(), 70 | loadBalancerNames = listOf(), 71 | createdTime = Instant.now(clock).minus(35, ChronoUnit.DAYS).toEpochMilli() 72 | ) 73 | 74 | val result = ZeroInstanceDisabledServerGroupRule(clock).apply(asg) 75 | Assertions.assertNull(result.summary) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /swabbie-core/src/test/kotlin/com/netflix/spinnaker/swabbie/exclusions/LiteralExclusionPolicyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.exclusions 18 | 19 | import com.natpryce.hamkrest.equalTo 20 | import com.natpryce.hamkrest.should.shouldMatch 21 | import com.netflix.spinnaker.config.Attribute 22 | import com.netflix.spinnaker.config.Exclusion 23 | import com.netflix.spinnaker.config.ExclusionType 24 | import com.netflix.spinnaker.swabbie.test.TestResource 25 | import org.junit.jupiter.api.Test 26 | 27 | object LiteralExclusionPolicyTest { 28 | @Test 29 | fun `should exclude by name`() { 30 | val exclusions = listOf( 31 | Exclusion() 32 | .withType(ExclusionType.Literal.toString()) 33 | .withAttributes( 34 | setOf( 35 | Attribute() 36 | .withKey("name") 37 | .withValue( 38 | listOf("test", "pattern:^ti") 39 | ), 40 | Attribute() 41 | .withKey("resourceId") 42 | .withValue( 43 | listOf("test", "pattern:^ti") 44 | ) 45 | ) 46 | ) 47 | ) 48 | 49 | listOf( 50 | TestResource("test", name = "test") 51 | ).filter { 52 | LiteralExclusionPolicy().apply(it, exclusions) == null 53 | }.let { filteredList -> 54 | filteredList.size shouldMatch equalTo(0) 55 | } 56 | } 57 | 58 | @Test 59 | fun `should exclude by arbitrary field`() { 60 | val exclusions = listOf( 61 | Exclusion() 62 | .withType(ExclusionType.Literal.toString()) 63 | .withAttributes( 64 | setOf( 65 | Attribute() 66 | .withKey("resourceId") 67 | .withValue( 68 | listOf("test", "pattern:^.*?base_ami_id=", "pattern:(?!^.*?ancestor_id=)") 69 | ) 70 | ) 71 | ) 72 | ) 73 | 74 | listOf( 75 | TestResource( 76 | resourceId = "name=derivedsource, arch=x86_64, ancestor_name=xenialbase-x86_64-201806121200-ebs, " + 77 | "ancestor_id=ami-0c44ea1fc39e22303, ancestor_version=nflx-base-5.202.0-h721.5589019", 78 | name = "random 1" 79 | ) 80 | ).filter { 81 | LiteralExclusionPolicy().apply(it, exclusions) == null 82 | }.let { filteredList -> 83 | filteredList.size shouldMatch equalTo(0) 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /swabbie-aws/src/main/kotlin/com/netflix/spinnaker/swabbie/aws/images/Rules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.aws.images 18 | 19 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 20 | import com.netflix.spinnaker.swabbie.model.Resource 21 | import com.netflix.spinnaker.swabbie.model.Result 22 | import com.netflix.spinnaker.swabbie.model.Rule 23 | import com.netflix.spinnaker.swabbie.model.Summary 24 | import org.springframework.stereotype.Component 25 | 26 | /** 27 | * Images are marked when they are orphaned. 28 | * 29 | * The `outOfUseThresholdDays` property controls the amount of time 30 | * we let a resource be unseen (out of use) before it is marked and deleted. 31 | * For example, if `outOfUseThresholdDays = 10`, then an image is allowed to sit in 32 | * orphaned state (defined by the rules below) for 10 days before it will be marked. 33 | */ 34 | @Component 35 | class OrphanedImageRule : Rule { 36 | override fun applicableForType(clazz: Class): Boolean = AmazonImage::class.java.isAssignableFrom(clazz) 37 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 38 | if (resource !is AmazonImage) { 39 | return Result(null) 40 | } 41 | 42 | if (resource.matchesAnyRule( 43 | USED_BY_INSTANCES, 44 | USED_BY_LAUNCH_CONFIGURATIONS, 45 | HAS_SIBLINGS_IN_OTHER_ACCOUNTS, 46 | SEEN_IN_USE_RECENTLY, 47 | USED_BY_LAUNCH_TEMPLATES 48 | ) 49 | ) { 50 | return Result(null) 51 | } 52 | 53 | return Result( 54 | Summary( 55 | description = "Image is not referenced by an Instance, Launch Configuration/Template, " + 56 | "and has no siblings in other accounts, " + 57 | "and has been that way for over the outOfUseThreshold days", 58 | ruleName = name() 59 | ) 60 | ) 61 | } 62 | 63 | private fun AmazonImage.matchesAnyRule(vararg ruleName: String): Boolean { 64 | return ruleName.any { details.containsKey(it) && details[it] as Boolean } 65 | } 66 | } 67 | 68 | const val USED_BY_INSTANCES = "usedByInstances" 69 | const val USED_BY_LAUNCH_CONFIGURATIONS = "usedByLaunchConfigurations" 70 | const val USED_BY_LAUNCH_TEMPLATES = "usedByLaunchTemplates" 71 | const val HAS_SIBLINGS_IN_OTHER_ACCOUNTS = "hasSiblingsInOtherAccounts" 72 | const val SEEN_IN_USE_RECENTLY = "seenInUseRecently" 73 | -------------------------------------------------------------------------------- /swabbie-core/src/main/kotlin/com/netflix/spinnaker/swabbie/rules/Rules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.rules 18 | 19 | import com.netflix.spinnaker.config.ResourceTypeConfiguration.RuleDefinition 20 | import com.netflix.spinnaker.swabbie.model.Resource 21 | import com.netflix.spinnaker.swabbie.model.Result 22 | import com.netflix.spinnaker.swabbie.model.Rule 23 | import com.netflix.spinnaker.swabbie.model.Summary 24 | import java.time.Clock 25 | import org.springframework.stereotype.Component 26 | 27 | @Component 28 | class AgeRule( 29 | private val clock: Clock 30 | ) : Rule { 31 | override fun applicableForType(clazz: Class): Boolean = true 32 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 33 | val age = resource.age(clock).toDays() 34 | val moreThanDays = ruleDefinition?.parameters?.get("moreThanDays") as? Int 35 | if (moreThanDays == null || age <= moreThanDays) { 36 | return Result(null) 37 | } 38 | 39 | return Result( 40 | Summary( 41 | description = "Resource ${resource.resourceId} is older than $moreThanDays days.", 42 | ruleName = name() 43 | ) 44 | ) 45 | } 46 | } 47 | 48 | /** 49 | description: "A rule that applies when parameters match a resource's attributes" 50 | rules: 51 | - name: AttributeRule 52 | parameters: 53 | name: pattern:some 54 | desc: blah 55 | */ 56 | @Component 57 | class AttributeRule : Rule { 58 | override fun applicableForType(clazz: Class): Boolean = true 59 | 60 | /** 61 | * @param ruleDefinition parameters map represents a resource's attributes to match against the given values in the map. 62 | */ 63 | override fun apply(resource: T, ruleDefinition: RuleDefinition?): Result { 64 | if (!resource.matchAttributes(ruleDefinition)) { 65 | return Result(null) 66 | } 67 | 68 | return Result( 69 | Summary(ruleDefinition?.description ?: "(${resource.resourceId}): matched by rule attributes.", name()) 70 | ) 71 | } 72 | 73 | private fun T.matchAttributes(ruleDefinition: RuleDefinition?): Boolean { 74 | // params contains key-value pairs to match against the resource's attributes 75 | val params = ruleDefinition?.parameters ?: return false 76 | return params.any { (key, value) -> matchResourceAttributes(key, listOf(value)) } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /swabbie-echo/src/main/kotlin/com/netflix/spinnaker/swabbie/echo/EchoNotifier.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.swabbie.echo 18 | 19 | import com.netflix.spinnaker.config.NotificationConfiguration 20 | import com.netflix.spinnaker.swabbie.notifications.Notifier 21 | import com.netflix.spinnaker.swabbie.notifications.Notifier.NotificationResult 22 | import com.netflix.spinnaker.swabbie.notifications.Notifier.NotificationSeverity 23 | import com.netflix.spinnaker.swabbie.notifications.Notifier.NotificationType 24 | import java.lang.UnsupportedOperationException 25 | import org.slf4j.Logger 26 | import org.slf4j.LoggerFactory 27 | import org.springframework.stereotype.Component 28 | 29 | @Component 30 | class EchoNotifier( 31 | private val echoService: EchoService 32 | ) : Notifier { 33 | private val log: Logger = LoggerFactory.getLogger(javaClass) 34 | override fun notify( 35 | recipient: String, 36 | notificationContext: Map, 37 | notificationConfiguration: NotificationConfiguration 38 | ): NotificationResult { 39 | notificationConfiguration 40 | .types 41 | .forEach { notificationType -> 42 | when { 43 | notificationType.equals(NotificationType.EMAIL.name, true) -> { 44 | return try { 45 | log.info("Sending notification to $recipient. context: $notificationContext") 46 | sendEmail(recipient, notificationContext) 47 | NotificationResult(recipient, NotificationType.EMAIL, success = true) 48 | } catch (e: Exception) { 49 | log.error("Failed to send notification to $recipient. context: $notificationContext", e) 50 | NotificationResult(recipient, NotificationType.EMAIL, success = false) 51 | } 52 | } 53 | } 54 | } 55 | 56 | throw UnsupportedOperationException("Notification Type not supported in $notificationConfiguration") 57 | } 58 | 59 | private fun sendEmail(recipient: String, notificationContext: Map) { 60 | echoService.create( 61 | EchoService.Notification( 62 | notificationType = NotificationType.EMAIL, 63 | to = recipient.split(","), 64 | severity = NotificationSeverity.HIGH, 65 | source = EchoService.Notification.Source("swabbie"), 66 | templateGroup = "swabbie", 67 | additionalContext = notificationContext 68 | ) 69 | ) 70 | } 71 | } 72 | --------------------------------------------------------------------------------