Skip to content

Commit

Permalink
Merge pull request #73 from ajordens/front50-on-s3
Browse files Browse the repository at this point in the history
Initial port of Front50 to S3
  • Loading branch information
ajordens committed Mar 18, 2016
2 parents 1099fd0 + cfb6b54 commit 67abdaa
Show file tree
Hide file tree
Showing 48 changed files with 2,575 additions and 592 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ buildscript {
}
dependencies {
classpath 'com.netflix.spinnaker.gradle:spinnaker-gradle-project:3.5.0'
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}

Expand All @@ -35,7 +35,7 @@ allprojects { project ->
group = "com.netflix.spinnaker.front50"

spinnaker {
dependenciesVersion = "0.25.0"
dependenciesVersion = "0.30.0"
}
test {
testLogging {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment

@Configuration
@ConditionalOnExpression('${cassandra.enabled:true}')
class CassandraConfig {
@Value('${spinnaker.cassandra.name:global}')
String name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import javax.annotation.PostConstruct

@Slf4j
@Component
@ConditionalOnExpression('${spinnaker.cassandra.enabled:false}')
@ConditionalOnExpression('${cassandra.enabled:true}')
class CassandraApplicationDAO implements ApplicationDAO {
private static final MapSerializer<String, String> mapSerializer = new MapSerializer<String, String>(UTF8Type.instance, UTF8Type.instance)
private static final ListSerializer<String> listSerializer = new ListSerializer(UTF8Type.instance)
Expand Down Expand Up @@ -85,18 +85,6 @@ class CassandraApplicationDAO implements ApplicationDAO {
) with compression={};''').execute()
}

try {
// TODO: migrate schema on the fly, remove any time after 2/1/16.
runQuery("select details_json from application")
} catch (BadRequestException ignored) {
runQuery("ALTER TABLE application ADD details_json varchar")
runQuery("ALTER TABLE application ADD version int")
all().each { Application application ->
application.platformHealthOnly = new Boolean(application.details().platformHealthOnly)
application.platformHealthOnlyShowOverride = new Boolean(application.details().platformHealthOnlyShowOverride)
update(application.name, application)
}
}
}

@Override
Expand All @@ -110,6 +98,11 @@ class CassandraApplicationDAO implements ApplicationDAO {
return applications[0]
}

@Override
Application findById(String id) throws NotFoundException {
return findByName(id)
}

@Override
Set<Application> all() {
def applications = unmarshallResults(runQuery('SELECT * FROM application;'))
Expand Down Expand Up @@ -156,44 +149,12 @@ class CassandraApplicationDAO implements ApplicationDAO {

@Override
Set<Application> search(Map<String, String> attributes) {
def searchableApplications = all()
attributes = attributes.collect { k,v -> [k.toLowerCase(), v] }.collectEntries()

if (attributes["accounts"]) {
// CQL 3.0/Cassandra 1.2 doesn't support the filtering of collections (3.1/2.0+ do)
def accounts = attributes["accounts"].split(",").collect { it.trim().toLowerCase() }
searchableApplications = searchableApplications.findAll {
def applicationAccounts = (it.accounts ?: "").split(",").collect { it.trim().toLowerCase() }
return applicationAccounts.containsAll(accounts)
}

// remove the 'accounts' search attribute so it's not picked up again in the field-level filtering below
attributes.remove("accounts")
}

// filtering vs. querying to achieve case-insensitivity without using an additional column (small data set)
def items = searchableApplications.findAll { app ->
def result = true
attributes.each { k, v ->
if (!v) {
return
}
if (!app.hasProperty(k) && !app.details().containsKey(k)) {
result = false
}
def appVal = app.hasProperty(k) ? app[k] : app.details()[k] ?: ""
if (!appVal.toString().toLowerCase().contains(v.toLowerCase())) {
result = false
}
}
return result
} as Set

if (!items) {
throw new NotFoundException("No Application found for search criteria $attributes")
}
return ApplicationDAO.Searcher.search(all(), attributes);
}

return items
@Override
void bulkImport(Collection<Application> applications) {
throw new UnsupportedOperationException()
}

void truncate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ import com.netflix.spinnaker.front50.exception.NotFoundException
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
import org.springframework.stereotype.Component

import javax.annotation.PostConstruct

@Slf4j
@Component
@ConditionalOnExpression('${cassandra.enabled:true}')
class CassandraProjectDAO implements ProjectDAO {
private static final String CF_NAME = 'project'
private static final String TEST_QUERY = '''select * from project;'''
Expand Down Expand Up @@ -91,24 +93,43 @@ class CassandraProjectDAO implements ProjectDAO {
return project
}

@Override
Project findByName(String name) throws NotFoundException {
return findBy("name", name)
}

@Override
Project findById(String id) throws NotFoundException {
return findBy("id", id)
}

Set<Project> all() {
return unmarshallResults(runQuery('SELECT * FROM project;'))
}

Project create(Project project) {
Project create(String id, Project project) {
runQuery(buildInsertQuery(objectMapper, project))
return findBy("name", project.name)
}

Project update(String id, Project project) {
void update(String id, Project project) {
runQuery(buildInsertQuery(objectMapper, project))
return findBy("id", id)
}

void delete(String id) {
runQuery("DELETE FROM project WHERE id = ${id};")
}

@Override
void bulkImport(Collection<Project> projects) {
throw new UnsupportedOperationException()
}

@Override
boolean isHealthy() {
return true
}

void truncate() {
keyspace.truncateColumnFamily(CF_NAME)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package com.netflix.spinnaker.front50.model

import com.netflix.spinnaker.front50.exception.NotFoundException

interface ItemDAO<T> {
T findById(String id) throws NotFoundException
Collection<T> all()

T create(String id, T item)
void update(String id, T item)
void delete(String id)

void bulkImport(Collection<T> items)

boolean isHealthy()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.front50.model;

public interface Timestamped {
String getId();

Long getLastModified();

void setLastModified(Long lastModified);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import com.netflix.spinnaker.front50.events.ApplicationEventListener
import com.netflix.spinnaker.front50.exception.ApplicationAlreadyExistsException
import com.netflix.spinnaker.front50.exception.NotFoundException
import com.netflix.spinnaker.front50.model.Timestamped
import com.netflix.spinnaker.front50.validator.ApplicationValidationErrors
import com.netflix.spinnaker.front50.validator.ApplicationValidator
import groovy.transform.Canonical
Expand All @@ -34,7 +35,7 @@ import org.springframework.validation.Errors

@ToString
@Slf4j
class Application {
class Application implements Timestamped {
String name
String description
String email
Expand Down Expand Up @@ -248,9 +249,9 @@ class Application {
updatedApplication = it.call(copy(copyOfOriginalApplication), copy(updatedApplication)) as Application
invokedEventListeners << it
}
onSuccess.call(copy(copyOfOriginalApplication), copy(updatedApplication))
updatedApplication = onSuccess.call(copy(copyOfOriginalApplication), copy(updatedApplication))
postApplicationEventListeners.each {
it.call(copy(copyOfOriginalApplication), copy(updatedApplication))
updatedApplication = it.call(copy(copyOfOriginalApplication), copy(updatedApplication))
invokedEventListeners << it
}

Expand All @@ -274,6 +275,23 @@ class Application {
}
}

@Override
@JsonIgnore()
String getId() {
return name.toLowerCase()
}

@Override
@JsonIgnore
Long getLastModified() {
return updateTs ? Long.valueOf(updateTs) : null
}

@Override
void setLastModified(Long lastModified) {
this.updateTs = lastModified.toString()
}

private static Application copy(Application source) {
return source ? new Application(source.getPersistedProperties()) : null
}
Expand All @@ -282,4 +300,4 @@ class Application {
static class ValidationException extends RuntimeException {
Errors errors
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,51 @@
package com.netflix.spinnaker.front50.model.application

import com.netflix.spinnaker.front50.exception.NotFoundException
import com.netflix.spinnaker.front50.model.ItemDAO

public interface ApplicationDAO {
public interface ApplicationDAO extends ItemDAO<Application> {
Application findByName(String name) throws NotFoundException

Set<Application> all() throws NotFoundException
Collection<Application> search(Map<String, String> attributes)

Application create(String id, Application application)
static class Searcher {
static Collection<Application> search(Collection<Application> searchableApplications, Map<String, String> attributes) {
attributes = attributes.collect { k,v -> [k.toLowerCase(), v] }.collectEntries()

void update(String id, Application application)
if (attributes["accounts"]) {
def accounts = attributes["accounts"].split(",").collect { it.trim().toLowerCase() }
searchableApplications = searchableApplications.findAll {
def applicationAccounts = (it.accounts ?: "").split(",").collect { it.trim().toLowerCase() }
return applicationAccounts.containsAll(accounts)
}

void delete(String id)
// remove the 'accounts' search attribute so it's not picked up again in the field-level filtering below
attributes.remove("accounts")
}

boolean isHealthy()
// filtering vs. querying to achieve case-insensitivity without using an additional column (small data set)
def items = searchableApplications.findAll { app ->
def result = true
attributes.each { k, v ->
if (!v) {
return
}
if (!app.hasProperty(k) && !app.details().containsKey(k)) {
result = false
}
def appVal = app.hasProperty(k) ? app[k] : app.details()[k] ?: ""
if (!appVal.toString().toLowerCase().contains(v.toLowerCase())) {
result = false
}
}
return result
} as Set

Set<Application> search(Map<String, String> attributes)
if (!items) {
throw new NotFoundException("No Application found for search criteria $attributes")
}

}
return items
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@
* limitations under the License.
*/

package com.netflix.spinnaker.front50.notifications
package com.netflix.spinnaker.front50.model.notification

public enum HierarchicalLevel {
STAGE,
PIPELINE,
APPLICATION,
PROJECT,
ORGANIZATION,
GLOBAL

static HierarchicalLevel fromString(String level) {
Expand Down
Loading

0 comments on commit 67abdaa

Please sign in to comment.