Skip to content
This repository was archived by the owner on Dec 19, 2025. It is now read-only.

Commit 80182c0

Browse files
committed
Merge pull request #625 from ajordens/multiple-applicationservice-impls
Added Spinnaker Support
2 parents 8ea8d34 + 85cf902 commit 80182c0

File tree

13 files changed

+858
-202
lines changed

13 files changed

+858
-202
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ root = true
99

1010
# Change these settings to your own preference
1111
indent_style = space
12-
indent_size = 2
12+
indent_size = 4
1313

1414
# We recommend you to keep these unchanged
1515
end_of_line = lf

grails-app/conf/BuildConfig.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ grails.project.dependency.resolution = {
5252
grailsHome()
5353
grailsCentral()
5454
mavenCentral()
55+
mavenRepo "http://dl.bintray.com/spinnaker/spinnaker"
5556

5657
// Optional custom repository for dependencies.
5758
Closure internalRepo = {
@@ -140,7 +141,10 @@ grails.project.dependency.resolution = {
140141
// Used for JSON parsing of AWS Simple Workflow Service metadata.
141142
// Previously this was an indirect depencency through Grails itself, but this caused errors in some
142143
// Grails environments.
143-
'com.googlecode.json-simple:json-simple:1.1'
144+
'com.googlecode.json-simple:json-simple:1.1',
145+
146+
// Spinnaker client is used to retrieve application metadata
147+
'com.netflix.spinnaker.client:spinnaker-client:0.6'
144148
) { // Exclude superfluous and dangerous transitive dependencies
145149
excludes(
146150
// Some libraries bring older versions of JUnit as a transitive dependency and that can interfere

grails-app/conf/spring/resources.groovy

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import com.google.common.base.CaseFormat
1919
import com.netflix.asgard.CachedMapBuilder
2020
import com.netflix.asgard.Caches
2121
import com.netflix.asgard.CsiAsgAnalyzer
22+
import com.netflix.asgard.applications.SimpleDBApplicationService
23+
import com.netflix.asgard.applications.SpinnakerApplicationService
2224
import com.netflix.asgard.NoOpAsgAnalyzer
2325
import com.netflix.asgard.Region
2426
import com.netflix.asgard.ServiceInitLoggingBeanPostProcessor
@@ -113,6 +115,19 @@ beans = {
113115

114116
restrictBrowserAuthorizationProvider(RestrictBrowserAuthorizationProvider)
115117

118+
if (application.config.spinnaker?.gateUrl) {
119+
applicationService(
120+
SpinnakerApplicationService, application.config.spinnaker.gateUrl, application.config.cloud.accountName
121+
) { bean ->
122+
bean.lazyInit = true
123+
}
124+
} else {
125+
applicationService(SimpleDBApplicationService) { bean ->
126+
bean.lazyInit = true
127+
}
128+
}
129+
130+
116131
//**** Plugin behavior
117132

118133
xmlns lang:'http://www.springframework.org/schema/lang'

grails-app/controllers/com/netflix/asgard/ApplicationController.groovy

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ class ApplicationController {
179179
String monitorBucketTypeString = params.monitorBucketType
180180
String tags = normalizeTagDelimiter(params.tags)
181181
MonitorBucketType bucketType = Enum.valueOf(MonitorBucketType, monitorBucketTypeString)
182-
CreateApplicationResult result = applicationService.createRegisteredApplication(userContext, name, group,
183-
type, desc, owner, email, bucketType, tags)
182+
def result = applicationService.createRegisteredApplication(
183+
userContext, name, group, type, desc, owner, email, bucketType, tags
184+
)
184185
flash.message = result.toString()
185186
if (result.succeeded()) {
186187
redirect(action: 'show', params: [id: name])
@@ -210,9 +211,10 @@ class ApplicationController {
210211
String monitorBucketTypeString = params.monitorBucketType
211212
try {
212213
MonitorBucketType bucketType = Enum.valueOf(MonitorBucketType, monitorBucketTypeString)
213-
applicationService.updateRegisteredApplication(userContext, name, group, type, desc, owner, email, tags,
214-
bucketType)
215-
flash.message = "Application '${name}' has been updated."
214+
def result = applicationService.updateRegisteredApplication(
215+
userContext, name, group, type, desc, owner, email, tags, bucketType
216+
)
217+
flash.message = result.toString()
216218
} catch (Exception e) {
217219
flash.message = "Could not update Application: ${e}"
218220
}
Lines changed: 25 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 Netflix, Inc.
2+
* Copyright 2014 Netflix, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,172 +15,33 @@
1515
*/
1616
package com.netflix.asgard
1717

18-
import com.amazonaws.AmazonServiceException
19-
import com.amazonaws.services.simpledb.model.Attribute
20-
import com.amazonaws.services.simpledb.model.Item
21-
import com.amazonaws.services.simpledb.model.ReplaceableAttribute
22-
import com.netflix.asgard.cache.CacheInitializer
2318
import com.netflix.asgard.collections.GroupedAppRegistrationSet
2419
import com.netflix.asgard.model.MonitorBucketType
25-
import org.joda.time.DateTime
26-
import org.springframework.beans.factory.InitializingBean
27-
28-
class ApplicationService implements CacheInitializer, InitializingBean {
29-
30-
static transactional = false
31-
32-
/** The name of SimpleDB domain that stores the cloud application registry. */
33-
String domainName
34-
35-
def grailsApplication // injected after construction
36-
def awsAutoScalingService
37-
def awsClientService
38-
def awsEc2Service
39-
def awsLoadBalancerService
40-
def awsSimpleDbService
41-
Caches caches
42-
def configService
43-
def fastPropertyService
44-
def mergedInstanceGroupingService
45-
def taskService
46-
47-
void afterPropertiesSet() {
48-
domainName = configService.applicationsDomain
49-
}
5020

51-
void initializeCaches() {
52-
caches.allApplications.ensureSetUp({ retrieveApplications() })
53-
}
21+
interface ApplicationService {
22+
List<AppRegistration> getRegisteredApplications(UserContext userContext)
5423

55-
private Collection<AppRegistration> retrieveApplications() {
56-
List<Item> items = awsSimpleDbService.selectAll(domainName).sort { it.name.toLowerCase() }
57-
items.collect { AppRegistration.from(it) }
58-
}
59-
60-
List<AppRegistration> getRegisteredApplications(UserContext userContext) {
61-
caches.allApplications.list().sort { it.name }
62-
}
63-
64-
List<AppRegistration> getRegisteredApplicationsForLoadBalancer(UserContext userContext) {
65-
new ArrayList<AppRegistration>(getRegisteredApplications(userContext).findAll {
66-
Relationships.checkAppNameForLoadBalancer(it.name)
67-
})
68-
}
69-
70-
GroupedAppRegistrationSet getGroupedRegisteredApplications(UserContext ctx) {
71-
new GroupedAppRegistrationSet(getRegisteredApplications(ctx))
72-
}
24+
List<AppRegistration> getRegisteredApplicationsForLoadBalancer(UserContext userContext)
7325

74-
AppRegistration getRegisteredApplication(UserContext userContext, String nameInput, From from = From.AWS) {
75-
if (!nameInput) { return null }
76-
String name = nameInput.toLowerCase()
77-
if (from == From.CACHE) {
78-
return caches.allApplications.get(name)
79-
}
80-
Item item = awsSimpleDbService.selectOne(domainName, name.toUpperCase())
81-
AppRegistration appRegistration = AppRegistration.from(item)
82-
caches.allApplications.put(name, appRegistration)
83-
appRegistration
84-
}
26+
GroupedAppRegistrationSet getGroupedRegisteredApplications(UserContext ctx)
8527

86-
AppRegistration getRegisteredApplicationForLoadBalancer(UserContext userContext, String name) {
87-
Relationships.checkAppNameForLoadBalancer(name) ? getRegisteredApplication(userContext, name) : null
88-
}
28+
AppRegistration getRegisteredApplication(UserContext userContext, String nameInput)
8929

90-
CreateApplicationResult createRegisteredApplication(UserContext userContext, String nameInput, String group,
91-
String type, String description, String owner, String email, MonitorBucketType monitorBucketType,
92-
String tags) {
93-
String name = nameInput.toLowerCase()
94-
CreateApplicationResult result = new CreateApplicationResult()
95-
result.appName = name
96-
if (getRegisteredApplication(userContext, name)) {
97-
result.appCreateException = new IllegalStateException("Can't add Application ${name}. It already exists.")
98-
return result
99-
}
100-
String nowEpoch = new DateTime().millis as String
101-
Collection<ReplaceableAttribute> attributes = buildAttributesList(group, type, description, owner, email,
102-
monitorBucketType, tags, false)
103-
attributes << new ReplaceableAttribute('createTs', nowEpoch, false)
104-
String creationLogMessage = "Create registered app ${name}, type ${type}, owner ${owner}, email ${email}"
105-
taskService.runTask(userContext, creationLogMessage, { task ->
106-
try {
107-
awsSimpleDbService.save(domainName, name.toUpperCase(), attributes)
108-
result.appCreated = true
109-
} catch (AmazonServiceException e) {
110-
result.appCreateException = e
111-
}
112-
}, Link.to(EntityType.application, name))
113-
getRegisteredApplication(userContext, name)
114-
result
115-
}
30+
AppRegistration getRegisteredApplication(UserContext userContext, String nameInput, From from)
11631

117-
private static Collection<ReplaceableAttribute> buildAttributesList(String group, String type, String description,
118-
String owner, String email, MonitorBucketType monitorBucketType, String tags,
119-
Boolean replaceExistingValues) {
120-
121-
Check.notNull(monitorBucketType, MonitorBucketType, 'monitorBucketType')
122-
String nowEpoch = new DateTime().millis as String
123-
Collection<ReplaceableAttribute> attributes = []
124-
attributes << new ReplaceableAttribute('group', group ?: '', replaceExistingValues)
125-
attributes << new ReplaceableAttribute('type', Check.notEmpty(type), replaceExistingValues)
126-
attributes << new ReplaceableAttribute('description', Check.notEmpty(description), replaceExistingValues)
127-
attributes << new ReplaceableAttribute('owner', Check.notEmpty(owner), replaceExistingValues)
128-
attributes << new ReplaceableAttribute('email', Check.notEmpty(email), replaceExistingValues)
129-
attributes << new ReplaceableAttribute('monitorBucketType', monitorBucketType.name(), replaceExistingValues)
130-
attributes << new ReplaceableAttribute('updateTs', nowEpoch, replaceExistingValues)
131-
if (tags) {
132-
attributes << new ReplaceableAttribute('tags', tags, replaceExistingValues)
133-
}
134-
return attributes
135-
}
32+
AppRegistration getRegisteredApplicationForLoadBalancer(UserContext userContext, String name)
13633

137-
void updateRegisteredApplication(UserContext userContext, String name, String group, String type, String desc,
138-
String owner, String email, String tags, MonitorBucketType bucketType) {
139-
Collection<ReplaceableAttribute> attributes = buildAttributesList(group, type, desc, owner, email,
140-
bucketType, tags, true)
141-
taskService.runTask(userContext,
142-
"Update registered app ${name}, type ${type}, owner ${owner}, email ${email}", { task ->
143-
awsSimpleDbService.save(domainName, name.toUpperCase(), attributes)
144-
if (!tags) {
145-
awsSimpleDbService.delete(domainName, name.toUpperCase(), [new Attribute().withName('tags')])
146-
}
147-
}, Link.to(EntityType.application, name))
148-
getRegisteredApplication(userContext, name)
149-
}
34+
ApplicationModificationResult createRegisteredApplication(UserContext userContext, String name, String group,
35+
String type, String description, String owner,
36+
String email, MonitorBucketType monitorBucketType,
37+
String tags)
15038

151-
void deleteRegisteredApplication(UserContext userContext, String name) {
152-
Check.notEmpty(name, "name")
153-
validateDelete(userContext, name)
154-
taskService.runTask(userContext, "Delete registered app ${name}", { task ->
155-
awsSimpleDbService.delete(domainName, name.toUpperCase())
156-
}, Link.to(EntityType.application, name))
157-
getRegisteredApplication(userContext, name)
158-
}
39+
ApplicationModificationResult updateRegisteredApplication(UserContext userContext, String name, String group,
40+
String type, String desc, String owner,
41+
String email, MonitorBucketType bucketType,
42+
String tags)
15943

160-
private void validateDelete(UserContext userContext, String name) {
161-
List<String> objectsWithEntities = []
162-
if (awsAutoScalingService.getAutoScalingGroupsForApp(userContext, name)) {
163-
objectsWithEntities.add('Auto Scaling Groups')
164-
}
165-
if (awsLoadBalancerService.getLoadBalancersForApp(userContext, name)) {
166-
objectsWithEntities.add('Load Balancers')
167-
}
168-
if (awsEc2Service.getSecurityGroupsForApp(userContext, name)) {
169-
objectsWithEntities.add('Security Groups')
170-
}
171-
if (mergedInstanceGroupingService.getMergedInstances(userContext, name)) {
172-
objectsWithEntities.add('Instances')
173-
}
174-
if (fastPropertyService.getFastPropertiesByAppName(userContext, name)) {
175-
objectsWithEntities.add('Fast Properties')
176-
}
177-
178-
if (objectsWithEntities) {
179-
String referencesString = objectsWithEntities.join(', ')
180-
String message = "${name} ineligible for delete because it still references ${referencesString}"
181-
throw new ValidationException(message)
182-
}
183-
}
44+
void deleteRegisteredApplication(UserContext userContext, String name)
18445

18546
/**
18647
* Get the email address of the relevant app, or empty string if no email address can be found for the specified
@@ -189,9 +50,7 @@ class ApplicationService implements CacheInitializer, InitializingBean {
18950
* @param appName the name of the app that has the email address
19051
* @return the email address associated with the app, or empty string if no email address can be found
19152
*/
192-
String getEmailFromApp(UserContext userContext, String appName) {
193-
getRegisteredApplication(userContext, appName)?.email ?: ''
194-
}
53+
String getEmailFromApp(UserContext userContext, String appName)
19554

19655
/**
19756
* Provides a string to use for monitoring bucket, either provided an empty string, cluster name or app name based
@@ -202,33 +61,22 @@ class ApplicationService implements CacheInitializer, InitializingBean {
20261
* @param clusterName value to return if the application's monitor bucket type is 'cluster'
20362
* @return appName or clusterName or empty string, based on the application's monitorBucketType
20463
*/
205-
String getMonitorBucket(UserContext userContext, String appName, String clusterName) {
206-
MonitorBucketType type = getRegisteredApplication(userContext, appName)?.monitorBucketType
207-
type == MonitorBucketType.application ? appName : type == MonitorBucketType.cluster ? clusterName : ''
208-
}
64+
String getMonitorBucket(UserContext userContext, String appName, String clusterName)
20965
}
21066

21167
/**
212-
* Records the results of trying to create an Application.
68+
* Records the results of trying to modify an Application.
21369
*/
214-
class CreateApplicationResult {
215-
String appName
216-
Boolean appCreated
217-
Exception appCreateException
70+
class ApplicationModificationResult {
71+
boolean successful
72+
String message
21873

21974
String toString() {
220-
StringBuilder output = new StringBuilder()
221-
if (appCreated) {
222-
output.append("Application '${appName}' has been created. ")
223-
}
224-
if (appCreateException) {
225-
output.append("Could not create Application '${appName}': ${appCreateException}. ")
226-
}
227-
output.toString()
75+
return message
22876
}
22977

23078
Boolean succeeded() {
231-
appCreated && !appCreateException
79+
return successful
23280
}
23381
}
23482

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2014 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.asgard.applications
17+
18+
import com.netflix.asgard.AppRegistration
19+
import com.netflix.asgard.ApplicationService
20+
import com.netflix.asgard.Relationships
21+
import com.netflix.asgard.UserContext
22+
import com.netflix.asgard.collections.GroupedAppRegistrationSet
23+
import com.netflix.asgard.model.MonitorBucketType
24+
25+
abstract class AbstractApplicationService implements ApplicationService {
26+
@Override
27+
final List<AppRegistration> getRegisteredApplicationsForLoadBalancer(UserContext userContext) {
28+
new ArrayList<AppRegistration>(getRegisteredApplications(userContext).findAll {
29+
Relationships.checkAppNameForLoadBalancer(it.name)
30+
})
31+
}
32+
33+
@Override
34+
final GroupedAppRegistrationSet getGroupedRegisteredApplications(UserContext ctx) {
35+
new GroupedAppRegistrationSet(getRegisteredApplications(ctx))
36+
}
37+
38+
@Override
39+
final AppRegistration getRegisteredApplicationForLoadBalancer(UserContext userContext, String name) {
40+
Relationships.checkAppNameForLoadBalancer(name) ? getRegisteredApplication(userContext, name) : null
41+
}
42+
43+
@Override
44+
final String getEmailFromApp(UserContext userContext, String appName) {
45+
getRegisteredApplication(userContext, appName)?.email ?: ''
46+
}
47+
48+
@Override
49+
final String getMonitorBucket(UserContext userContext, String appName, String clusterName) {
50+
MonitorBucketType type = getRegisteredApplication(userContext, appName)?.monitorBucketType
51+
type == MonitorBucketType.application ? appName : type == MonitorBucketType.cluster ? clusterName : ''
52+
}
53+
}

0 commit comments

Comments
 (0)