├── OWNERS.md ├── .clog.toml ├── front50-api ├── README.md ├── front50-api.gradle └── src │ ├── sample │ └── java │ │ └── validator │ │ └── PipelineValidator.java │ └── main │ └── java │ └── com │ └── netflix │ └── spinnaker │ └── front50 │ └── api │ ├── model │ ├── Timestamped.java │ └── pipeline │ │ └── ForwardingMap.java │ └── validator │ ├── PipelineValidator.java │ └── ValidatorErrors.java ├── front50-web ├── pkg_scripts │ ├── postUninstall.sh │ └── postInstall.sh ├── etc │ ├── logrotate.d │ │ └── front50 │ └── init │ │ └── front50.conf ├── lib │ └── systemd │ │ └── system │ │ └── front50.service ├── src │ ├── test │ │ ├── resources │ │ │ └── logback.xml │ │ └── groovy │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── front50 │ │ │ ├── controllers │ │ │ ├── DeliveryControllerSpec.groovy │ │ │ ├── SimpleExceptionHandlerExceptionResolver.groovy │ │ │ ├── AdminControllerSpec.groovy │ │ │ └── AuthorizationSupportSpec.groovy │ │ │ └── ItemDAOHealthIndicatorSpec.groovy │ └── main │ │ ├── java │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── front50 │ │ │ ├── exceptions │ │ │ ├── InvalidRequestException.java │ │ │ ├── DuplicateEntityException.java │ │ │ └── InvalidEntityException.java │ │ │ ├── controllers │ │ │ ├── exception │ │ │ │ └── InvalidApplicationRequestException.java │ │ │ ├── PermissionsController.java │ │ │ └── PluginVersionController.java │ │ │ ├── config │ │ │ └── controllers │ │ │ │ └── PipelineControllerConfig.java │ │ │ └── filters │ │ │ └── SimpleCORSFilter.java │ │ └── resources │ │ └── graphql │ │ └── pipelineConfig.graphqls └── config │ └── front50.yml ├── front50-api-tck ├── README.md ├── src │ ├── main │ │ ├── resources │ │ │ └── front50-test-app.yml │ │ └── kotlin │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── front50 │ │ │ └── api │ │ │ └── test │ │ │ └── Front50Fixture.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ └── api │ │ └── test │ │ └── Front50FixtureTest.kt └── front50-api-tck.gradle ├── front50-sql-mysql └── front50-sql-mysql.gradle ├── .idea ├── copyright │ ├── profiles_settings.xml │ └── ALS2.xml ├── vcs.xml ├── google-java-format.xml ├── compiler.xml ├── README.md └── gradle.xml ├── front50-sql-postgres └── front50-sql-postgres.gradle ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── kotlin.gradle ├── halconfig ├── front50.yml └── README.md ├── front50-redis ├── src │ ├── test │ │ ├── resources │ │ │ └── application.yml │ │ └── groovy │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── front50 │ │ │ └── redis │ │ │ └── config │ │ │ ├── UnhealthyRedisConfig.java │ │ │ └── EmbeddedRedisConfig.java │ └── main │ │ └── groovy │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ └── redis │ │ └── RedisConfigurationProperties.groovy └── front50-redis.gradle ├── front50-sql ├── src │ ├── main │ │ ├── resources │ │ │ └── db │ │ │ │ ├── database-mysql.sql │ │ │ │ ├── my.cnf │ │ │ │ ├── changelog │ │ │ │ ├── 20200113-rename-plugin-artifacts-to-plugin-info.yml │ │ │ │ ├── 20190415-initial-snapshots-schema.yml │ │ │ │ ├── 20190415-initial-entity-tags-schema.yml │ │ │ │ ├── 20191213-initial-plugin-artifacts-schema.yml │ │ │ │ └── 20200428-initial-plugin-versions-schema.yml │ │ │ │ └── changelog-master.yml │ │ └── kotlin │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ ├── config │ │ │ └── Front50SqlProperties.kt │ │ │ └── front50 │ │ │ └── model │ │ │ └── sql │ │ │ └── Jooq.kt │ └── test │ │ └── resources │ │ └── application.yml ├── README.md └── front50-sql.gradle ├── front50-gcs ├── src │ ├── test │ │ ├── resources │ │ │ └── minimal-gcs-account.yml │ │ └── kotlin │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── front50 │ │ │ └── model │ │ │ └── GcsIntegrationTest.kt │ └── main │ │ └── java │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ └── config │ │ └── GcsProperties.java └── front50-gcs.gradle ├── README.md ├── .gitignore ├── .editorconfig ├── .github ├── dependabot.yml ├── workflows │ ├── bump_dependencies.yml │ └── release_info.sh └── PULL_REQUEST_TEMPLATE.md ├── front50-core └── src │ ├── main │ └── java │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ ├── model │ │ ├── tag │ │ │ └── EntityTagsDAO.java │ │ ├── snapshot │ │ │ └── SnapshotDAO.java │ │ ├── serviceaccount │ │ │ ├── ServiceAccountDAO.java │ │ │ ├── ServiceAccount.java │ │ │ └── DefaultServiceAccountDAO.java │ │ ├── notification │ │ │ ├── HierarchicalLevel.java │ │ │ ├── NotificationDAO.java │ │ │ └── Notification.java │ │ ├── project │ │ │ └── ProjectDAO.java │ │ ├── delivery │ │ │ └── DeliveryRepository.java │ │ ├── pipeline │ │ │ ├── PipelineStrategyDAO.java │ │ │ ├── PipelineDAO.java │ │ │ ├── V2TemplateConfiguration.java │ │ │ ├── PipelineTemplateDAO.java │ │ │ ├── TemplateConfiguration.java │ │ │ └── DefaultPipelineTemplateDAO.java │ │ ├── plugins │ │ │ ├── PluginEventType.java │ │ │ ├── PluginVersionPinningRepository.java │ │ │ ├── PluginInfoRepository.java │ │ │ ├── PluginInfoDelta.java │ │ │ └── ServerGroupPluginVersions.java │ │ ├── application │ │ │ └── ApplicationPermissionDAO.java │ │ ├── BulkStorageService.java │ │ ├── AdminOperations.java │ │ ├── ObjectKeyLoader.java │ │ ├── DefaultObjectKeyLoader.java │ │ └── ItemDAO.java │ │ ├── validator │ │ ├── ApplicationValidator.java │ │ ├── HasNameValidator.java │ │ ├── HasEmailValidator.java │ │ ├── PluginInfoValidator.java │ │ ├── ApplicationNameValidatorConfigurationProperties.java │ │ ├── HasCanonicalPluginIdValidator.java │ │ ├── HasOnePreferredReleaseValidator.java │ │ ├── HasValidRequiresFieldsValidator.java │ │ └── ApplicationNameValidator.java │ │ ├── exception │ │ ├── BadRequestException.java │ │ ├── ApplicationAlreadyExistsException.java │ │ └── ValidationException.java │ │ ├── echo │ │ ├── Event.java │ │ ├── EchoService.java │ │ └── Notification.java │ │ ├── jackson │ │ ├── mixins │ │ │ └── TimestampedMixins.java │ │ └── Front50ApiModule.java │ │ ├── events │ │ ├── UnmodifiableAttributesApplicationEventListener.java │ │ ├── ApplicationPermissionEventListener.java │ │ └── ApplicationEventListener.java │ │ ├── config │ │ ├── FiatConfigurationProperties.java │ │ ├── annotations │ │ │ ├── ConditionalOnAnyProviderExceptRedisIsEnabled.java │ │ │ └── AnyProviderExceptRedisCondition.java │ │ ├── PluginVersionCleanupProperties.java │ │ ├── PluginBinaryCacheConfiguration.java │ │ ├── EchoConfiguration.java │ │ ├── Front50CoreConfiguration.java │ │ └── PluginVersioningConfiguration.java │ │ └── plugins │ │ ├── PluginBinaryAlreadyExistsException.java │ │ └── PluginBinaryStorageService.java │ └── test │ ├── groovy │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ ├── model │ │ ├── plugins │ │ │ └── PluginInfoSpec.groovy │ │ └── pipeline │ │ │ └── PipelineTemplateSpec.groovy │ │ ├── UntypedUtilsSpec.groovy │ │ └── plugins │ │ └── CachingPluginBinaryStorageServiceSpec.groovy │ └── java │ └── com │ └── netflix │ └── spinnaker │ └── front50 │ └── validator │ ├── HasCanonicalPluginIdValidatorTest.java │ ├── HasNameValidatorTest.java │ └── HasEmailValidatorTest.java ├── AUTHORS ├── Dockerfile.compile ├── .gcloudignore ├── Dockerfile.slim ├── Dockerfile.ubuntu ├── front50-migrations ├── src │ ├── test │ │ └── groovy │ │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── front50 │ │ │ ├── config │ │ │ └── MigrationConfigTest.java │ │ │ └── migrations │ │ │ ├── MigrationRunnerSpec.groovy │ │ │ └── EnsureCronTriggerHasIdentifierMigrationSpec.groovy │ └── main │ │ └── java │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ ├── migrations │ │ └── Migration.java │ │ └── config │ │ └── MigrationConfig.java └── front50-migrations.gradle ├── front50-oracle ├── front50-oracle.gradle └── src │ └── main │ └── java │ └── com │ └── netflix │ └── spinnaker │ └── front50 │ ├── model │ └── LastModified.java │ └── config │ └── OracleConfig.java ├── gradle.properties ├── front50-integration ├── src │ └── test │ │ └── resources │ │ ├── front50-test-applications.sql │ │ ├── front50-test-applications-history.sql │ │ └── logback.xml └── front50-integration.gradle ├── front50-s3 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── front50 │ │ ├── config │ │ ├── S3MetadataStorageProperties.java │ │ ├── S3PluginStorageProperties.java │ │ ├── S3FailoverProperties.java │ │ └── S3EventingProperties.java │ │ └── model │ │ ├── events │ │ ├── S3EventWrapper.java │ │ └── S3Event.java │ │ └── ReadOnlyModeException.java └── front50-s3.gradle ├── front50-bom └── front50-bom.gradle ├── front50-test └── front50-test.gradle ├── front50-swift ├── front50-swift.gradle └── src │ └── main │ └── java │ └── com │ └── netflix │ └── spinnaker │ └── front50 │ └── config │ ├── SwiftConfig.java │ └── SwiftProperties.java ├── front50-azure ├── front50-azure.gradle └── src │ └── main │ └── java │ └── com │ └── netflix │ └── spinnaker │ └── front50 │ └── config │ ├── AzureStorageConfig.java │ └── AzureStorageProperties.java ├── settings.gradle └── .mergify.yml /OWNERS.md: -------------------------------------------------------------------------------- 1 | ajordens 2 | dreynaud 3 | robzienert 4 | -------------------------------------------------------------------------------- /.clog.toml: -------------------------------------------------------------------------------- 1 | [clog] 2 | 3 | [sections] 4 | "Configuration Changes" = ["config", "conf"] 5 | -------------------------------------------------------------------------------- /front50-api/README.md: -------------------------------------------------------------------------------- 1 | # Module front50-api 2 | 3 | Contains all public Java APIs for Front50. 4 | -------------------------------------------------------------------------------- /front50-web/pkg_scripts/postUninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf /var/log/spinnaker/front50 4 | -------------------------------------------------------------------------------- /front50-api-tck/README.md: -------------------------------------------------------------------------------- 1 | # front50-api-tck 2 | 3 | Test harnesses and utilities for Front50 plugin developers. 4 | -------------------------------------------------------------------------------- /front50-sql-mysql/front50-sql-mysql.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | runtimeOnly "com.mysql:mysql-connector-j" 3 | } 4 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /front50-sql-postgres/front50-sql-postgres.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | runtimeOnly "org.postgresql:postgresql" 3 | } 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/front50/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /halconfig/front50.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${services.front50.port:8080} 3 | address: ${services.front50.host:localhost} 4 | -------------------------------------------------------------------------------- /front50-redis/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org: 4 | springframework: 5 | data: DEBUG 6 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/database-mysql.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA `front50` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ; 2 | -------------------------------------------------------------------------------- /front50-gcs/src/test/resources/minimal-gcs-account.yml: -------------------------------------------------------------------------------- 1 | spinnaker: 2 | gcs: 3 | enabled: true 4 | project: my-project 5 | bucket: my-bucket 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Front50 is now maintained in the [Spinnaker monorepo](https://github.com/spinnaker/spinnaker). This repository has been archived. 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | front50.i* 4 | out/ 5 | .gradle/ 6 | .vagrant/ 7 | .vagrant 8 | vendor/ 9 | .bundle/ 10 | *.iml 11 | .idea 12 | 13 | front50-oracle-bmcs/bmcs-sdk 14 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.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 10 | -------------------------------------------------------------------------------- /front50-web/etc/logrotate.d/front50: -------------------------------------------------------------------------------- 1 | /var/log/spinnaker/front50/front50.log { 2 | copytruncate 3 | daily 4 | rotate 10 5 | compress 6 | missingok 7 | create 0644 spinnaker spinnaker 8 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.idea/google-java-format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/tag/EntityTagsDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.tag; 2 | 3 | import com.netflix.spinnaker.front50.model.ItemDAO; 4 | 5 | public interface EntityTagsDAO extends ItemDAO {} 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Names should be added to this file with this pattern: 2 | # 3 | # For individuals: 4 | # Name 5 | # 6 | # For organizations: 7 | # Organization 8 | 9 | Netflix, Inc <*@netflix.com> 10 | Google, Inc <*@google.com> 11 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/snapshot/SnapshotDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.snapshot; 2 | 3 | import com.netflix.spinnaker.front50.model.ItemDAO; 4 | 5 | public interface SnapshotDAO extends ItemDAO {} 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Dockerfile.compile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | RUN apk add --update \ 3 | openjdk17 \ 4 | && rm -rf /var/cache/apk 5 | LABEL maintainer="sig-platform@spinnaker.io" 6 | ENV GRADLE_USER_HOME /workspace/.gradle 7 | ENV GRADLE_OPTS -Xmx4g 8 | CMD ./gradlew --no-daemon front50-web:installDist -x test 9 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/serviceaccount/ServiceAccountDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.serviceaccount; 2 | 3 | import com.netflix.spinnaker.front50.model.ItemDAO; 4 | 5 | public interface ServiceAccountDAO extends ItemDAO {} 6 | -------------------------------------------------------------------------------- /front50-web/etc/init/front50.conf: -------------------------------------------------------------------------------- 1 | description "front50" 2 | 3 | start on filesystem or runlevel [2345] 4 | 5 | setuid spinnaker 6 | setgid spinnaker 7 | 8 | expect fork 9 | 10 | stop on stopping spinnaker 11 | 12 | exec /opt/front50/bin/front50 2>&1 >> /var/log/spinnaker/front50/front50.log & 13 | -------------------------------------------------------------------------------- /front50-web/lib/systemd/system/front50.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Spinnaker Front50 3 | PartOf=spinnaker.service 4 | 5 | [Service] 6 | ExecStart=/opt/front50/bin/front50 7 | WorkingDirectory=/opt/front50/bin 8 | SuccessExitStatus=143 9 | User=spinnaker 10 | Group=spinnaker 11 | Type=simple 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/ApplicationValidator.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.validator; 2 | 3 | import com.netflix.spinnaker.front50.model.application.Application; 4 | 5 | public interface ApplicationValidator { 6 | void validate(Application application, ApplicationValidationErrors validationErrors); 7 | } 8 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/my.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | default-character-set = utf8mb4 3 | 4 | [mysql] 5 | default-character-set = utf8mb4 6 | 7 | [mysqld] 8 | character-set-client-handshake = FALSE 9 | character-set-server = utf8mb4 10 | collation-server = utf8mb4_unicode_ci 11 | 12 | # Only allow connections from localhost 13 | bind-address = 127.0.0.1 14 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/notification/HierarchicalLevel.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.notification; 2 | 3 | public enum HierarchicalLevel { 4 | APPLICATION, 5 | GLOBAL; 6 | 7 | public static HierarchicalLevel fromString(final String level) { 8 | return valueOf(level.toUpperCase()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /halconfig/README.md: -------------------------------------------------------------------------------- 1 | This directory contains skeleton Front50 configs to which Halyard concatenates 2 | its generated deployment-specific config. 3 | 4 | These configs are **deprecated** and in general should not be further updated. To 5 | set a default config value, either set the value in `front50-web/config/front50.yml` 6 | or set a default in the code reading the config property. 7 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | # By default, everything inside .gitignore (as well as the .git directory and 2 | # the .gitignore file itself) is not uploaded to Google Cloud Build. But the 3 | # bintray publishing task requires the .git directory to uploaded. 4 | # 5 | # Adding this file overrides the default, so everything gets uploaded, but we 6 | # still want to exclude the large .gradle cache directory. 7 | .gradle 8 | -------------------------------------------------------------------------------- /front50-web/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%15.15t] %-40.40logger{39} %m%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /front50-web/pkg_scripts/postInstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ubuntu 4 | # check that owner group exists 5 | if [ -z `getent group spinnaker` ]; then 6 | groupadd spinnaker 7 | fi 8 | 9 | # check that user exists 10 | if [ -z `getent passwd spinnaker` ]; then 11 | useradd --gid spinnaker spinnaker -m --home-dir /home/spinnaker 12 | fi 13 | 14 | install --mode=755 --owner=spinnaker --group=spinnaker --directory /var/log/spinnaker/front50 15 | -------------------------------------------------------------------------------- /.idea/README.md: -------------------------------------------------------------------------------- 1 | # Spinnaker IntelliJ IDEA files 2 | 3 | IntelliJ IDEA will modify some of these files from their checked-in versions when the project is 4 | opened. To work around this, the Spinnaker Gradle plugin will mark these files in Git as "assume 5 | unchanged", telling Git to ignore any local changes. If you want to commit changes to these files, 6 | you will need to undo that. 7 | 8 | ```bash 9 | $ git update-index --no-assume-unchanged $FILENAME 10 | ``` 11 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/project/ProjectDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.project; 2 | 3 | import com.netflix.spinnaker.front50.model.ItemDAO; 4 | import com.netflix.spinnaker.kork.web.exceptions.NotFoundException; 5 | import java.util.Collection; 6 | 7 | public interface ProjectDAO extends ItemDAO { 8 | Project findByName(String name) throws NotFoundException; 9 | 10 | Collection all(); 11 | } 12 | -------------------------------------------------------------------------------- /front50-api-tck/src/main/resources/front50-test-app.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: front50 4 | test: 5 | database: 6 | replace: none 7 | 8 | services: 9 | fiat: 10 | enabled: false 11 | baseUrl: http://localhost:7003 12 | 13 | sql: 14 | enabled: true 15 | connectionPools: 16 | default: 17 | default: true 18 | jdbcUrl: jdbc:h2:mem:testdb 19 | user: sa 20 | migration: 21 | user: sa 22 | jdbcUrl: jdbc:h2:mem:testdb 23 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/changelog/20200113-rename-plugin-artifacts-to-plugin-info.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: rename-plugin-artifacts-to-plugin-info 4 | author: jonsie 5 | changes: 6 | - renameTable: 7 | newTableName: plugin_info 8 | oldTableName: plugin_artifacts 9 | rollback: 10 | - renameTable: 11 | newTableName: plugin_artifacts 12 | oldTableName: plugin_info 13 | -------------------------------------------------------------------------------- /.github/workflows/bump_dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Bump Dependencies 2 | 3 | on: 4 | repository_dispatch: 5 | types: [bump-dependencies] 6 | 7 | jobs: 8 | bump-dependencies: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: spinnaker/bumpdeps@master 12 | with: 13 | ref: ${{ github.event.client_payload.ref }} 14 | key: front50Version 15 | repositories: halyard 16 | env: 17 | GITHUB_OAUTH: ${{ secrets.SPINNAKER_GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/delivery/DeliveryRepository.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.delivery; 2 | 3 | import com.netflix.spinnaker.front50.model.ItemDAO; 4 | import java.util.Collection; 5 | 6 | public interface DeliveryRepository extends ItemDAO { 7 | Collection getAllConfigs(); 8 | 9 | Collection getConfigsByApplication(String application); 10 | 11 | Delivery upsertConfig(Delivery config); 12 | } 13 | -------------------------------------------------------------------------------- /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 front50-web/build/install/front50 /opt/front50 7 | RUN mkdir -p /opt/front50/plugins && chown -R spinnaker:nogroup /opt/front50/plugins 8 | USER spinnaker 9 | HEALTHCHECK CMD curl http://localhost:8080/health | grep UP || exit 1 10 | CMD ["/opt/front50/bin/front50"] 11 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/pipeline/PipelineStrategyDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.pipeline; 2 | 3 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 4 | import com.netflix.spinnaker.front50.model.ItemDAO; 5 | import java.util.Collection; 6 | 7 | public interface PipelineStrategyDAO extends ItemDAO { 8 | String getPipelineId(String application, String pipelineName); 9 | 10 | Collection getPipelinesByApplication(String application); 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile.ubuntu: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | LABEL maintainer="sig-platform@spinnaker.io" 3 | RUN rm /var/lib/dpkg/info/libc-bin.* && apt-get clean && apt-get update && apt-get -y install curl openjdk-17-jre-headless wget 4 | RUN adduser --system --uid 10111 --group spinnaker 5 | COPY front50-web/build/install/front50 /opt/front50 6 | RUN mkdir -p /opt/front50/plugins && chown -R spinnaker:nogroup /opt/front50/plugins 7 | USER spinnaker 8 | HEALTHCHECK CMD curl http://localhost:8080/health | grep UP || exit 1 9 | CMD ["/opt/front50/bin/front50"] 10 | -------------------------------------------------------------------------------- /front50-web/config/front50.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | management: 5 | health: 6 | redis: 7 | enabled: ${spinnaker.redis.enabled} 8 | 9 | spinnaker: 10 | redis: 11 | enabled: false 12 | host: localhost 13 | port: 6379 14 | 15 | swagger: 16 | enabled: true 17 | title: Spinnaker Front50 API 18 | description: 19 | contact: 20 | patterns: 21 | - /default/.* 22 | - /credentials.* 23 | - /global/.* 24 | - /notifications.* 25 | - /pipelines.* 26 | - /strategies.* 27 | - /v2/.* 28 | -------------------------------------------------------------------------------- /front50-api-tck/front50-api-tck.gradle: -------------------------------------------------------------------------------- 1 | apply from: "${project.rootDir}/gradle/kotlin.gradle" 2 | 3 | dependencies { 4 | implementation(project(":front50-web")) 5 | implementation("com.h2database:h2") 6 | 7 | api("org.springframework.boot:spring-boot-starter-test") 8 | api("dev.minutest:minutest") 9 | 10 | testImplementation(project(":front50-core")) 11 | testImplementation(project(":front50-sql")) 12 | testImplementation("io.strikt:strikt-core") 13 | 14 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 15 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") 16 | } 17 | -------------------------------------------------------------------------------- /front50-migrations/src/test/groovy/com/netflix/spinnaker/front50/config/MigrationConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Schibsted ASA. All rights reserved 3 | */ 4 | 5 | package com.netflix.spinnaker.front50.config; 6 | 7 | import com.netflix.spinnaker.front50.migrations.MigrationRunner; 8 | import org.springframework.boot.test.context.TestConfiguration; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.annotation.Bean; 11 | 12 | @TestConfiguration 13 | public class MigrationConfigTest { 14 | 15 | @Bean 16 | public MigrationRunner migrationRunner(ApplicationContext applicationContext) { 17 | return new MigrationRunner(applicationContext); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/HasNameValidator.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.validator; 2 | 3 | import com.netflix.spinnaker.front50.model.application.Application; 4 | import java.util.Optional; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class HasNameValidator implements ApplicationValidator { 9 | @Override 10 | public void validate(Application application, ApplicationValidationErrors validationErrors) { 11 | if (Optional.ofNullable(application.getName()).orElse("").trim().isEmpty()) { 12 | validationErrors.rejectValue( 13 | "name", "application.name.empty", "Application must have a non-empty name"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/HasEmailValidator.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.validator; 2 | 3 | import com.netflix.spinnaker.front50.model.application.Application; 4 | import java.util.Optional; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class HasEmailValidator implements ApplicationValidator { 9 | @Override 10 | public void validate(Application application, ApplicationValidationErrors validationErrors) { 11 | if (Optional.ofNullable(application.getEmail()).orElse("").trim().isEmpty()) { 12 | validationErrors.rejectValue( 13 | "email", "application.email.empty", "Application must have a non-empty email"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /front50-oracle/front50-oracle.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation project(":front50-core") 3 | implementation project(":front50-api") 4 | 5 | implementation "com.github.ben-manes.caffeine:guava" 6 | implementation "io.spinnaker.kork:kork-core" 7 | implementation 'com.oracle.oci.sdk:oci-java-sdk-objectstorage' 8 | implementation "com.sun.jersey:jersey-client:1.19.4" 9 | implementation "org.springframework.boot:spring-boot-autoconfigure" 10 | implementation "org.springframework.boot:spring-boot-starter-actuator" 11 | implementation "org.springframework.boot:spring-boot-starter-web" 12 | 13 | testImplementation project(":front50-test") 14 | } 15 | 16 | configurations.all { 17 | resolutionStrategy.force 'com.oracle.oci.sdk:oci-java-sdk-objectstorage:1.19.1' 18 | } 19 | -------------------------------------------------------------------------------- /front50-sql/README.md: -------------------------------------------------------------------------------- 1 | ## Configuring SQL store for front50 2 | 3 | #### MySQL: 4 | 5 | ```yaml 6 | sql: 7 | enabled: true 8 | baseUrl: jdbc:mysql://localhost:3306/front50 9 | connectionPools: 10 | default: 11 | jdbcUrl: ${sql.baseUrl}?useSSL=false&serverTimezone=UTC 12 | user: 13 | password: 14 | migration: 15 | jdbcUrl: ${sql.baseUrl}?useSSL=false&serverTimezone=UTC 16 | user: 17 | password: 18 | ``` 19 | 20 | #### PostgreSQL: 21 | ```yaml 22 | sql: 23 | enabled: true 24 | baseUrl: jdbc:postgresql://localhost:5432/front50 25 | connectionPools: 26 | default: 27 | jdbcUrl: ${sql.baseUrl} 28 | dialect: POSTGRES 29 | user: 30 | password: 31 | migration: 32 | jdbcUrl: ${sql.baseUrl} 33 | user: 34 | password: 35 | ``` 36 | -------------------------------------------------------------------------------- /.idea/copyright/ALS2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.exception; 2 | 3 | import com.netflix.spinnaker.kork.exceptions.UserException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 8 | public class BadRequestException extends UserException { 9 | public BadRequestException() {} 10 | 11 | public BadRequestException(String message) {} 12 | 13 | public BadRequestException(String message, Throwable cause) {} 14 | 15 | public BadRequestException(Throwable cause) {} 16 | 17 | protected BadRequestException( 18 | String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {} 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | fiatVersion=1.57.0 2 | includeProviders=azure,gcs,oracle,redis,s3,swift,sql 3 | korkVersion=7.254.0 4 | org.gradle.parallel=true 5 | spinnakerGradleVersion=8.32.1 6 | targetJava17=true 7 | kotlinVersion=1.6.21 8 | 9 | # To enable a composite reference to a project, set the 10 | # project property `'Composite=true'`. 11 | # 12 | # This can be done either as 13 | # * a command line flag, e.g. `-PkorkComposite=true` 14 | # * a project property via gradle.properties 15 | # * a global project property via ~/.gradle/gradle.properties 16 | # 17 | # The composite project must checked out in a sibling directory 18 | # to this project, matching the name of the project 19 | # e.g. '../kork' 20 | # 21 | #fiatComposite=true 22 | #korkComposite=true 23 | 24 | org.gradle.jvmargs=-Xmx2g -Xms2g 25 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/pipeline/PipelineDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.pipeline; 2 | 3 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 4 | import com.netflix.spinnaker.front50.model.ItemDAO; 5 | import java.util.Collection; 6 | 7 | public interface PipelineDAO extends ItemDAO { 8 | String getPipelineId(String application, String pipelineName); 9 | 10 | Collection getPipelinesByApplication(String application); 11 | 12 | Collection getPipelinesByApplication(String application, boolean refresh); 13 | 14 | Collection getPipelinesByApplication( 15 | String application, String pipelineNameFilter, boolean refresh); 16 | 17 | Pipeline getPipelineByName(String application, String pipelineName, boolean refresh); 18 | } 19 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/echo/Event.java: -------------------------------------------------------------------------------- 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 | 18 | package com.netflix.spinnaker.front50.echo; 19 | 20 | /** A marker interface for outgoing Events to Echo */ 21 | public interface Event {} 22 | -------------------------------------------------------------------------------- /front50-oracle/src/main/java/com/netflix/spinnaker/front50/model/LastModified.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Oracle Corporation and/or its affiliates. All rights reserved. 3 | * 4 | * The contents of this file are subject to the Apache License Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * If a copy of the Apache License Version 2.0 was not distributed with this file, 7 | * You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html 8 | */ 9 | 10 | package com.netflix.spinnaker.front50.model; 11 | 12 | public class LastModified { 13 | private Long lastModified = System.currentTimeMillis(); 14 | 15 | public Long getLastModified() { 16 | return lastModified; 17 | } 18 | 19 | public void setLastModified(Long lastModified) { 20 | this.lastModified = lastModified; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /front50-integration/src/test/resources/front50-test-applications.sql: -------------------------------------------------------------------------------- 1 | insert into applications (id, body, created_at, last_modified_at, last_modified_by, is_deleted) 2 | values ('app1', '{"name":"app1","description":null,"email":"test@front50.email","updateTs":"1738579806480","createTs":"1738579339278","lastModifiedBy":"anonymous","cloudProviders":"kubernetes","trafficGuards":[],"instancePort":80,"platformHealthOnlyShowOverride":false,"platformHealthOnly":true,"user":"[anonymous]","dataSources":{"disabled":[],"enabled":[]},"trafficGuards":[]}', 1738579339278, 1738579806480, 'anonymous', 0), 3 | ('dummy-application2', '{"name":"dummy-application2","description":null,"email":"test@front50.me","updateTs":"1738579396051","createTs":null,"lastModifiedBy":"anonymous","cloudProviders":"kubernetes","trafficGuards":[],"instancePort":80,"user":"[anonymous]"}', 1738579396051, 1738579396051, 'anonymous', 0); 4 | -------------------------------------------------------------------------------- /front50-migrations/src/main/java/com/netflix/spinnaker/front50/migrations/Migration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.migrations; 18 | 19 | public interface Migration { 20 | boolean isValid(); 21 | 22 | void run(); 23 | } 24 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/plugins/PluginEventType.java: -------------------------------------------------------------------------------- 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 | 18 | package com.netflix.spinnaker.front50.model.plugins; 19 | 20 | public enum PluginEventType { 21 | PUBLISHED, 22 | PREFERRED_VERSION_UPDATED 23 | } 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | We prefer small, well tested pull requests. 2 | 3 | Please refer to [Contributing to Spinnaker](https://spinnaker.io/community/contributing/). 4 | 5 | When filling out a pull request, please consider the following: 6 | 7 | * Follow the commit message conventions [found here](https://spinnaker.io/community/contributing/submitting/). 8 | * Provide a descriptive summary for your changes. 9 | * If it fixes a bug or resolves a feature request, be sure to link to that issue. 10 | * Add inline code comments to changes that might not be obvious. 11 | * Squash your commits as you keep adding changes. 12 | * Add a comment to @spinnaker/reviewers for review if your issue has been outstanding for more than 3 days. 13 | 14 | Note that we are unlikely to accept pull requests that add features without prior discussion. The best way to propose a feature is to open an issue first and discuss your ideas there before implementing them. 15 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/exception/ApplicationAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.exception; 2 | 3 | import com.netflix.spinnaker.kork.exceptions.UserException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Application already exists") 8 | public class ApplicationAlreadyExistsException extends UserException { 9 | public ApplicationAlreadyExistsException() {} 10 | 11 | public ApplicationAlreadyExistsException(String message) {} 12 | 13 | public ApplicationAlreadyExistsException(String message, Throwable cause) {} 14 | 15 | public ApplicationAlreadyExistsException(Throwable cause) {} 16 | 17 | protected ApplicationAlreadyExistsException( 18 | String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {} 19 | } 20 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/jackson/mixins/TimestampedMixins.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Armory, 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.front50.jackson.mixins; 18 | 19 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 20 | 21 | @JsonIgnoreProperties(value = {"createdAt"}) 22 | public class TimestampedMixins {} 23 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/application/ApplicationPermissionDAO.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google, 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.front50.model.application; 18 | 19 | import com.netflix.spinnaker.front50.model.ItemDAO; 20 | 21 | public interface ApplicationPermissionDAO extends ItemDAO {} 22 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/plugins/PluginVersionPinningRepository.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.model.plugins; 17 | 18 | import com.netflix.spinnaker.front50.model.ItemDAO; 19 | 20 | public interface PluginVersionPinningRepository extends ItemDAO {} 21 | -------------------------------------------------------------------------------- /front50-sql/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: default 3 | 4 | spinnaker: 5 | migration: 6 | enabled: true 7 | primaryName: secondarySqlStorageService 8 | previousName: sqlStorageService 9 | compositeStorageService: 10 | enabled: true 11 | reads: 12 | primary: true 13 | previous: false 14 | entityTags: 15 | enabled: true 16 | 17 | sql: 18 | enabled: true 19 | secondary: 20 | enabled: true 21 | poolName: secondary 22 | connectionPools: 23 | default: 24 | jdbcUrl: "jdbc:h2:mem:test" 25 | default: true 26 | user: 27 | password: 28 | secondary: 29 | enabled: true 30 | jdbcUrl: "jdbc:h2:mem:test" 31 | dialect: H2 32 | user: 33 | password: 34 | migration: 35 | jdbcUrl: "jdbc:h2:mem:test" 36 | user: 37 | password: 38 | secondaryMigration: 39 | jdbcUrl: "jdbc:h2:mem:test" 40 | user: 41 | password: 42 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/events/UnmodifiableAttributesApplicationEventListener.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.events; 2 | 3 | import java.util.Optional; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class UnmodifiableAttributesApplicationEventListener implements ApplicationEventListener { 8 | 9 | @Override 10 | public boolean supports(Type type) { 11 | return type.equals(Type.PRE_UPDATE); 12 | } 13 | 14 | @Override 15 | public void accept(ApplicationModelEvent applicationModelEvent) { 16 | Optional.ofNullable(applicationModelEvent.original) 17 | .ifPresent( 18 | original -> { 19 | applicationModelEvent.updated.setName(original.getName()); 20 | applicationModelEvent.updated.setUpdateTs(original.getUpdateTs()); 21 | applicationModelEvent.updated.setCreateTs(original.getCreateTs()); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/config/S3MetadataStorageProperties.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.config; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | @ConfigurationProperties("spinnaker.s3") 21 | public class S3MetadataStorageProperties extends S3Properties {} 22 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/config/S3PluginStorageProperties.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.config; 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; 19 | 20 | @ConfigurationProperties("spinnaker.s3.plugin-storage") 21 | public class S3PluginStorageProperties extends S3Properties {} 22 | -------------------------------------------------------------------------------- /front50-api/front50-api.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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-library' 18 | 19 | dependencies { 20 | implementation "io.spinnaker.kork:kork-exceptions" 21 | implementation platform("io.spinnaker.kork:kork-bom:$korkVersion") 22 | annotationProcessor platform("io.spinnaker.kork:kork-bom:$korkVersion") 23 | api("io.spinnaker.kork:kork-plugins-api") 24 | 25 | } 26 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/notification/NotificationDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.notification; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | 7 | public interface NotificationDAO { 8 | 9 | Collection NOTIFICATION_FORMATS = 10 | Collections.unmodifiableList( 11 | Arrays.asList( 12 | "bearychat", 13 | "email", 14 | "googlechat", 15 | "hipchat", 16 | "microsoftteams", 17 | "pubsub", 18 | "slack", 19 | "sms")); 20 | 21 | Collection all(); 22 | 23 | Notification getGlobal(); 24 | 25 | Notification get(HierarchicalLevel level, String name); 26 | 27 | void saveGlobal(Notification notification); 28 | 29 | void save(HierarchicalLevel level, String name, Notification notification); 30 | 31 | void delete(HierarchicalLevel level, String name); 32 | } 33 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/BulkStorageService.java: -------------------------------------------------------------------------------- 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.front50.model; 18 | 19 | import com.netflix.spinnaker.front50.api.model.Timestamped; 20 | import java.util.Collection; 21 | 22 | public interface BulkStorageService { 23 | void storeObjects(ObjectType objectType, Collection items); 24 | } 25 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/config/S3FailoverProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.config; 18 | 19 | public class S3FailoverProperties extends S3BucketProperties { 20 | boolean enabled = false; 21 | 22 | public boolean isEnabled() { 23 | return enabled; 24 | } 25 | 26 | public void setEnabled(boolean enabled) { 27 | this.enabled = enabled; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/PluginInfoValidator.java: -------------------------------------------------------------------------------- 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.front50.validator; 17 | 18 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo; 19 | import org.springframework.validation.Errors; 20 | 21 | /** A {@link PluginInfo} validator. */ 22 | public interface PluginInfoValidator { 23 | void validate(PluginInfo pluginInfo, Errors validationErrors); 24 | } 25 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/pipeline/V2TemplateConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google, 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 | 18 | package com.netflix.spinnaker.front50.model.pipeline; 19 | 20 | import com.netflix.spinnaker.kork.artifacts.model.Artifact; 21 | import lombok.Data; 22 | 23 | @Data 24 | public class V2TemplateConfiguration { 25 | private String schema; 26 | private String application; 27 | private String name; 28 | private Artifact template; 29 | } 30 | -------------------------------------------------------------------------------- /front50-redis/src/main/groovy/com/netflix/spinnaker/front50/redis/RedisConfigurationProperties.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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 | 18 | package com.netflix.spinnaker.front50.redis 19 | 20 | import groovy.transform.Canonical 21 | import org.springframework.boot.context.properties.ConfigurationProperties 22 | 23 | @Canonical 24 | @ConfigurationProperties("spinnaker.redis") 25 | class RedisConfigurationProperties { 26 | String host = "localhost" 27 | int port = 6379 28 | } 29 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/AdminOperations.java: -------------------------------------------------------------------------------- 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.front50.model; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | public interface AdminOperations { 24 | void recover(Recover operation); 25 | 26 | @Data 27 | @NoArgsConstructor 28 | @AllArgsConstructor 29 | class Recover { 30 | String objectType; 31 | String objectId; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front50-bom/front50-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 | api("io.spinnaker.fiat:fiat-api:$fiatVersion") 28 | api("io.spinnaker.fiat:fiat-core:$fiatVersion") 29 | 30 | rootProject 31 | .subprojects 32 | .findAll { it != project } 33 | .each { api(project(it.path)) } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/ObjectKeyLoader.java: -------------------------------------------------------------------------------- 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.front50.model; 18 | 19 | import java.util.Map; 20 | 21 | public interface ObjectKeyLoader { 22 | /** 23 | * @param objectType ObjectType 24 | * @return Key: Last Modified Timestamp for all keys of type {@code ObjectType} 25 | */ 26 | Map listObjectKeys(ObjectType objectType); 27 | 28 | default void shutdown() { 29 | // do nothing 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /front50-migrations/src/main/java/com/netflix/spinnaker/front50/config/MigrationConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.config; 18 | 19 | import org.springframework.context.annotation.ComponentScan; 20 | import org.springframework.context.annotation.Configuration; 21 | import org.springframework.scheduling.annotation.EnableScheduling; 22 | 23 | @Configuration 24 | @EnableScheduling 25 | @ComponentScan("com.netflix.spinnaker.front50.migrations") 26 | public class MigrationConfig {} 27 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/model/events/S3EventWrapper.java: -------------------------------------------------------------------------------- 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.front50.model.events; 18 | 19 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 20 | import com.fasterxml.jackson.annotation.JsonProperty; 21 | 22 | @JsonIgnoreProperties(ignoreUnknown = true) 23 | public class S3EventWrapper { 24 | @JsonProperty("Subject") 25 | public String subject; 26 | 27 | @JsonProperty("Message") 28 | public String message; 29 | } 30 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/exceptions/InvalidRequestException.java: -------------------------------------------------------------------------------- 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.front50.exceptions; 18 | 19 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 20 | 21 | import org.springframework.web.bind.annotation.ResponseStatus; 22 | 23 | @ResponseStatus(BAD_REQUEST) 24 | public class InvalidRequestException extends RuntimeException { 25 | public InvalidRequestException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/exceptions/DuplicateEntityException.java: -------------------------------------------------------------------------------- 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.front50.exceptions; 18 | 19 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 20 | 21 | import org.springframework.web.bind.annotation.ResponseStatus; 22 | 23 | @ResponseStatus(BAD_REQUEST) 24 | public class DuplicateEntityException extends RuntimeException { 25 | public DuplicateEntityException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/notification/Notification.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.notification; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.netflix.spinnaker.front50.api.model.Timestamped; 5 | import java.util.HashMap; 6 | 7 | public class Notification extends HashMap implements Timestamped { 8 | 9 | public static final String GLOBAL_ID = "__GLOBAL"; 10 | 11 | @Override 12 | @JsonIgnore 13 | public String getId() { 14 | return (String) super.getOrDefault("application", GLOBAL_ID); 15 | } 16 | 17 | @Override 18 | @JsonIgnore 19 | public Long getLastModified() { 20 | return (Long) super.get("lastModified"); 21 | } 22 | 23 | @Override 24 | public void setLastModified(Long lastModified) { 25 | super.put("lastModified", lastModified); 26 | } 27 | 28 | @Override 29 | public String getLastModifiedBy() { 30 | return (String) super.get("lastModifiedBy"); 31 | } 32 | 33 | @Override 34 | public void setLastModifiedBy(String lastModifiedBy) { 35 | super.put("lastModifiedBy", lastModifiedBy); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/exceptions/InvalidEntityException.java: -------------------------------------------------------------------------------- 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.front50.exceptions; 18 | 19 | import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; 20 | 21 | import org.springframework.web.bind.annotation.ResponseStatus; 22 | 23 | @ResponseStatus(UNPROCESSABLE_ENTITY) 24 | public class InvalidEntityException extends RuntimeException { 25 | public InvalidEntityException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/ApplicationNameValidatorConfigurationProperties.java: -------------------------------------------------------------------------------- 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.front50.validator; 18 | 19 | import lombok.Data; 20 | import org.springframework.boot.context.properties.ConfigurationProperties; 21 | 22 | @ConfigurationProperties("validation.application-name-validator") 23 | @Data 24 | public class ApplicationNameValidatorConfigurationProperties { 25 | private String validationRegex; 26 | private String validationMessage; 27 | } 28 | -------------------------------------------------------------------------------- /front50-migrations/front50-migrations.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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(":front50-core") 19 | implementation project(":front50-api") 20 | 21 | implementation "io.spinnaker.fiat:fiat-core:$fiatVersion" 22 | implementation "io.spinnaker.kork:kork-core" 23 | implementation "org.apache.commons:commons-lang3" 24 | implementation "org.springframework.boot:spring-boot-autoconfigure" 25 | 26 | testImplementation project(":front50-test") 27 | testImplementation "org.spockframework:spock-spring" 28 | } 29 | -------------------------------------------------------------------------------- /front50-test/front50-test.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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(":front50-api") 19 | implementation project(":front50-core") 20 | implementation project(":front50-s3") 21 | implementation project(":front50-sql") 22 | 23 | implementation "org.apache.groovy:groovy" 24 | implementation "org.spockframework:spock-core" 25 | implementation "com.amazonaws:aws-java-sdk-s3" 26 | implementation "io.spinnaker.kork:kork-sql-test" 27 | implementation "io.reactivex:rxjava" 28 | } 29 | 30 | tasks.compileGroovy.enabled = true 31 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/exception/ValidationException.java: -------------------------------------------------------------------------------- 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.front50.exception; 18 | 19 | import com.netflix.spinnaker.kork.exceptions.UserException; 20 | import org.springframework.validation.Errors; 21 | 22 | public class ValidationException extends UserException { 23 | 24 | private final Errors errors; 25 | 26 | public ValidationException(Errors errors) { 27 | this.errors = errors; 28 | } 29 | 30 | public Errors getErrors() { 31 | return errors; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front50-api/src/sample/java/validator/PipelineValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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 validator; 17 | 18 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 19 | import com.netflix.spinnaker.front50.api.validator.PipelineValidator; 20 | import org.pf4j.Extension; 21 | 22 | @Extension 23 | public class AppNameValidator implements PipelineValidator { 24 | @Override 25 | public void validate(Pipeline pipeline, ValidationErrors errors) { 26 | if (pipeline.getApplication() != "gadfly") { 27 | errors.reject("Invalid app name"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /front50-integration/front50-integration.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | testImplementation "com.fasterxml.jackson.core:jackson-databind" 3 | testImplementation "org.assertj:assertj-core" 4 | testImplementation "org.junit.jupiter:junit-jupiter-api" 5 | testImplementation "org.slf4j:slf4j-api" 6 | testImplementation "org.testcontainers:testcontainers" 7 | testImplementation "org.testcontainers:junit-jupiter" 8 | testImplementation "org.testcontainers:mysql" 9 | testImplementation "org.testcontainers:postgresql" 10 | testRuntimeOnly "ch.qos.logback:logback-classic" 11 | testRuntimeOnly "com.mysql:mysql-connector-j" 12 | testRuntimeOnly "org.postgresql:postgresql" 13 | } 14 | 15 | test.configure { 16 | def fullDockerImageName = System.getenv('FULL_DOCKER_IMAGE_NAME') 17 | onlyIf("there is a docker image to test") { 18 | fullDockerImageName != null && fullDockerImageName.trim() != '' 19 | } 20 | } 21 | 22 | test { 23 | // So stdout and stderr from the just-built container are available in CI 24 | testLogging.showStandardStreams = true 25 | 26 | // Run the tests when the docker image changes 27 | inputs.property 'fullDockerImageName', System.getenv('FULL_DOCKER_IMAGE_NAME') 28 | } 29 | -------------------------------------------------------------------------------- /gradle/kotlin.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 | apply plugin: "kotlin" 18 | apply plugin: "kotlin-spring" 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 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/echo/EchoService.java: -------------------------------------------------------------------------------- 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 | 18 | package com.netflix.spinnaker.front50.echo; 19 | 20 | import retrofit.client.Response; 21 | import retrofit.http.Body; 22 | import retrofit.http.Headers; 23 | import retrofit.http.POST; 24 | 25 | public interface EchoService { 26 | @Headers("Content-type: application/json") 27 | @POST("/") 28 | String postEvent(@Body Event event); 29 | 30 | @Headers("Content-type: application/json") 31 | @POST("/notifications") 32 | Response postNotification(@Body Notification notification); 33 | } 34 | -------------------------------------------------------------------------------- /front50-integration/src/test/resources/front50-test-applications-history.sql: -------------------------------------------------------------------------------- 1 | insert into applications_history (id, body, body_sig, last_modified_at, recorded_at) 2 | values ('app1', '{"name":"app1","description":null,"email":"test@front50.email","updateTs":"1738579339278","createTs":null,"lastModifiedBy":"anonymous","cloudProviders":"kubernetes","trafficGuards":[],"instancePort":80,"user":"[anonymous]"}', '6972009b4230a5af4edb44df87e2703d', 1738579339278, 1738579339311), 3 | ('app1', '{"name":"app1","description":null,"email":"test@front50.email","updateTs":"1738579806480","createTs":"1738579339278","lastModifiedBy":"anonymous","cloudProviders":"kubernetes","trafficGuards":[],"instancePort":80,"platformHealthOnlyShowOverride":false,"platformHealthOnly":true,"user":"[anonymous]","dataSources":{"disabled":[],"enabled":[]},"trafficGuards":[]}', 'ff5168aa9d988755ced0948b29f38be0', 1738579806480, 1738579806483), 4 | ('dummy-application2', '{"name":"dummy-application2","description":null,"email":"test@front50.me","updateTs":"1738579396051","createTs":null,"lastModifiedBy":"anonymous","cloudProviders":"kubernetes","trafficGuards":[],"instancePort":80,"user":"[anonymous]"}', 'ad750a02d4ce18821e09ba93618f7e84', 1738579396051, 1738579396053); 5 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/DefaultObjectKeyLoader.java: -------------------------------------------------------------------------------- 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.front50.model; 18 | 19 | import java.util.Map; 20 | 21 | public class DefaultObjectKeyLoader implements ObjectKeyLoader { 22 | private final StorageService storageService; 23 | 24 | public DefaultObjectKeyLoader(StorageService storageService) { 25 | this.storageService = storageService; 26 | } 27 | 28 | @Override 29 | public Map listObjectKeys(ObjectType objectType) { 30 | return storageService.listObjectKeys(objectType); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /front50-sql/front50-sql.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/gradle/kotlin.gradle" 2 | 3 | dependencies { 4 | implementation project(":front50-core") 5 | implementation project(":front50-api") 6 | if (!rootProject.hasProperty("excludeSqlDrivers")) { 7 | runtimeOnly project(":front50-sql-mysql") 8 | runtimeOnly project(":front50-sql-postgres") 9 | } 10 | 11 | implementation "io.spinnaker.kork:kork-sql" 12 | implementation "io.spinnaker.kork:kork-exceptions" 13 | implementation "io.spinnaker.kork:kork-web" 14 | 15 | implementation "io.strikt:strikt-core" 16 | implementation "io.github.resilience4j:resilience4j-retry" 17 | 18 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core" 19 | 20 | testImplementation "io.mockk:mockk" 21 | testImplementation "dev.minutest:minutest" 22 | testImplementation "org.junit.jupiter:junit-jupiter-api" 23 | 24 | testImplementation "com.mysql:mysql-connector-j" 25 | testImplementation "org.testcontainers:mysql" 26 | 27 | testImplementation "org.testcontainers:postgresql" 28 | testImplementation "org.postgresql:postgresql" 29 | 30 | // Only used for Initializing Datasource. For actual CRUD, test containers preferred. 31 | testImplementation "com.h2database:h2" 32 | } 33 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/model/ReadOnlyModeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.model; 17 | 18 | /** Thrown when front50 is running in read-only mode and a write operation is triggered. */ 19 | public class ReadOnlyModeException extends IllegalStateException { 20 | 21 | private static final String DEFAULT_MESSAGE = "Cannot perform write operation, in read-only mode"; 22 | 23 | public ReadOnlyModeException() { 24 | this(DEFAULT_MESSAGE); 25 | } 26 | 27 | public ReadOnlyModeException(String message) { 28 | super(message); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /front50-web/src/test/groovy/com/netflix/spinnaker/front50/controllers/DeliveryControllerSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.controllers 2 | 3 | import com.netflix.spinnaker.front50.exceptions.InvalidRequestException 4 | import com.netflix.spinnaker.front50.model.delivery.Delivery 5 | import com.netflix.spinnaker.front50.model.delivery.DeliveryRepository 6 | import spock.lang.Specification 7 | import spock.lang.Subject 8 | 9 | class DeliveryControllerSpec extends Specification { 10 | 11 | def configRepository = Mock(DeliveryRepository) 12 | 13 | @Subject 14 | def controller = new DeliveryController(configRepository) 15 | 16 | def config = new Delivery( 17 | id: "aaa", 18 | application: "ooboy", 19 | deliveryArtifacts: [], 20 | deliveryEnvironments: [] 21 | ) 22 | 23 | def "reject deletion if config id is not in the right app"() { 24 | when: 25 | configRepository.findById("aaa") >> config 26 | controller.deleteConfig("myapp", "aaa") 27 | 28 | then: 29 | thrown(InvalidRequestException) 30 | } 31 | 32 | def "reject update to config if application doesn't match request url"() { 33 | when: 34 | controller.upsertConfig("bbb", config) 35 | 36 | then: 37 | thrown(InvalidRequestException) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/FiatConfigurationProperties.java: -------------------------------------------------------------------------------- 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.front50.config; 17 | 18 | import lombok.Data; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | @Data 22 | @ConfigurationProperties("fiat") 23 | public class FiatConfigurationProperties { 24 | 25 | RoleSyncConfigurationProperties roleSync = new RoleSyncConfigurationProperties(); 26 | boolean disableRoleSyncWhenSavingServiceAccounts = false; 27 | 28 | @Data 29 | public static class RoleSyncConfigurationProperties { 30 | boolean enabled = true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/model/events/S3Event.java: -------------------------------------------------------------------------------- 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.front50.model.events; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import java.util.List; 21 | 22 | public class S3Event { 23 | @JsonProperty("Records") 24 | public List records; 25 | 26 | public static class S3EventRecord { 27 | public String eventName; 28 | public String eventTime; 29 | public S3Meta s3; 30 | } 31 | 32 | public static class S3Meta { 33 | public S3Object object; 34 | } 35 | 36 | public static class S3Object { 37 | public String key; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/plugins/PluginBinaryAlreadyExistsException.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.plugins; 17 | 18 | import static java.lang.String.format; 19 | 20 | import com.netflix.spinnaker.kork.exceptions.UserException; 21 | 22 | /** 23 | * Thrown whenever an upload attempt is made on for a plugin ID and version that already has a 24 | * plugin binary in storage. 25 | */ 26 | public class PluginBinaryAlreadyExistsException extends UserException { 27 | 28 | public PluginBinaryAlreadyExistsException(String pluginKey) { 29 | super(format("Plugin binary already exists for '%s'", pluginKey)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/pipeline/PipelineTemplateDAO.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.model.pipeline; 17 | 18 | import com.netflix.spinnaker.front50.model.ItemDAO; 19 | import java.util.Collection; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | public interface PipelineTemplateDAO extends ItemDAO { 24 | 25 | default Collection getPipelineTemplatesByScope(List scope) { 26 | if (scope == null || scope.isEmpty()) { 27 | return all(); 28 | } 29 | 30 | return all().stream().filter(pt -> pt.containsAnyScope(scope)).collect(Collectors.toList()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /front50-sql/src/main/kotlin/com/netflix/spinnaker/config/Front50SqlProperties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Salesforce, 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.config 17 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties 19 | import java.time.Duration 20 | 21 | /** 22 | * Note that kork's SqlProperties class also uses the "sql" prefix. 23 | * 24 | * @param healthIntervalMillis The period to refresh health information (e.g. in the health endpoint). 25 | */ 26 | @ConfigurationProperties("sql") 27 | class Front50SqlProperties { 28 | /** 29 | * How frequently to refresh health information (e.g. for the health endpoint). 30 | */ 31 | var healthIntervalMillis: Long = Duration.ofSeconds(30).toMillis() 32 | } 33 | -------------------------------------------------------------------------------- /front50-api/src/main/java/com/netflix/spinnaker/front50/api/model/Timestamped.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.front50.api.model; 17 | 18 | public interface Timestamped { 19 | /** 20 | * TODO(rz): Move this method into new Identifiable interface. Has nothing to do with timestamps. 21 | * 22 | * @return 23 | */ 24 | String getId(); 25 | 26 | Long getLastModified(); 27 | 28 | void setLastModified(Long lastModified); 29 | 30 | String getLastModifiedBy(); 31 | 32 | void setLastModifiedBy(String lastModifiedBy); 33 | 34 | default void setCreatedAt(Long createdAt) { 35 | // do nothing 36 | } 37 | 38 | default Long getCreatedAt() { 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /front50-swift/front50-swift.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Veritas Technologies, LLC. 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 | configurations.all { 18 | exclude group: 'org.slf4j', module: 'slf4j-log4j12' 19 | } 20 | 21 | dependencies { 22 | implementation project(":front50-core") 23 | implementation project(":front50-api") 24 | 25 | implementation "org.springframework.boot:spring-boot-starter-web" 26 | implementation "org.springframework.boot:spring-boot-starter-actuator" 27 | implementation "org.springframework.boot:spring-boot-autoconfigure" 28 | implementation "io.spinnaker.kork:kork-core" 29 | implementation "org.springframework:spring-web" 30 | implementation 'org.pacesys:openstack4j:3.2.0' 31 | 32 | testImplementation project(":front50-test") 33 | } 34 | -------------------------------------------------------------------------------- /front50-migrations/src/test/groovy/com/netflix/spinnaker/front50/migrations/MigrationRunnerSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.migrations 2 | 3 | import com.netflix.spinnaker.front50.config.MigrationConfigTest 4 | import org.spockframework.spring.SpringBean 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.test.context.ContextConfiguration 7 | import spock.lang.Specification 8 | import spock.lang.Subject 9 | 10 | @ContextConfiguration(classes = [MigrationConfigTest, MigrationRunner]) 11 | class MigrationRunnerSpec extends Specification { 12 | @SpringBean(name = "myFirstMigration") 13 | Migration myFirstMigration = Mock() 14 | 15 | @SpringBean(name = "mySecondMigration") 16 | Migration mySecondMigration = Mock() 17 | 18 | @SpringBean(name = "disabledMigration") 19 | Migration disabledMigration = Mock() 20 | 21 | @Subject 22 | @Autowired 23 | MigrationRunner migrationRunner 24 | 25 | def setup() { 26 | myFirstMigration.isValid() >> true 27 | mySecondMigration.isValid() >> true 28 | disabledMigration.isValid() >> false 29 | } 30 | 31 | def "should run all enabled migrations"() { 32 | when: 33 | migrationRunner.run() 34 | 35 | then: 36 | 1 * myFirstMigration.run() 37 | 1 * mySecondMigration.run() 38 | 0 * disabledMigration.run() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /front50-core/src/test/groovy/com/netflix/spinnaker/front50/model/plugins/PluginInfoSpec.groovy: -------------------------------------------------------------------------------- 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.front50.model.plugins 17 | 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Unroll 21 | 22 | class PluginInfoSpec extends Specification { 23 | 24 | @Unroll 25 | def "plugin info release supports service"() { 26 | given: 27 | def release = new PluginInfo.Release(requires: "clouddriver>=2.0.0,orca>7.0.0,gate>=3.0.0") 28 | 29 | expect: 30 | release.supportsService(service) == expectedResult 31 | 32 | where: 33 | service || expectedResult 34 | "gate" || true 35 | "echo" || false 36 | "clouddriver" || true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/annotations/ConditionalOnAnyProviderExceptRedisIsEnabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Schibsted ASA. 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 | * 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 | package com.netflix.spinnaker.front50.config.annotations; 19 | 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.ElementType; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.RetentionPolicy; 24 | import java.lang.annotation.Target; 25 | import org.springframework.context.annotation.Conditional; 26 | 27 | @Target({ElementType.TYPE, ElementType.METHOD}) 28 | @Retention(RetentionPolicy.RUNTIME) 29 | @Documented 30 | @Conditional(AnyProviderExceptRedisCondition.class) 31 | public @interface ConditionalOnAnyProviderExceptRedisIsEnabled {} 32 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/controllers/exception/InvalidApplicationRequestException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.controllers.exception; 17 | 18 | import com.netflix.spinnaker.kork.exceptions.UserException; 19 | import org.springframework.http.HttpStatus; 20 | import org.springframework.web.bind.annotation.ResponseStatus; 21 | 22 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 23 | public class InvalidApplicationRequestException extends UserException { 24 | public InvalidApplicationRequestException(String message) { 25 | super(message); 26 | } 27 | 28 | public InvalidApplicationRequestException(String message, Throwable cause) { 29 | super(message, cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/ItemDAO.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model; 2 | 3 | import com.netflix.spinnaker.kork.web.exceptions.NotFoundException; 4 | import java.time.Duration; 5 | import java.util.Collection; 6 | 7 | public interface ItemDAO { 8 | T findById(String id) throws NotFoundException; 9 | 10 | Collection all(); 11 | 12 | /** 13 | * It can be expensive to refresh a bucket containing a large number of objects. 14 | * 15 | * @param refresh true to refresh 16 | * @return When {@code refresh} is false, the most recently cached set of objects will be 17 | * returned. * 18 | */ 19 | Collection all(boolean refresh); 20 | 21 | Collection history(String id, int maxResults); 22 | 23 | T create(String id, T item); 24 | 25 | void update(String id, T item); 26 | 27 | void delete(String id); 28 | 29 | void bulkImport(Collection items); 30 | 31 | default void bulkDelete(Collection ids) { 32 | for (String id : ids) { 33 | delete(id); 34 | } 35 | } 36 | 37 | boolean isHealthy(); 38 | 39 | /** 40 | * How frequently to refresh health information (e.g. to call isHealthy() to provide info to the 41 | * health endpoint) 42 | */ 43 | default long getHealthIntervalMillis() { 44 | return Duration.ofSeconds(30).toMillis(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /front50-azure/front50-azure.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Microsoft, 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(":front50-core") 19 | implementation project(":front50-api") 20 | implementation 'com.microsoft.azure:azure:1.0.0-beta3' 21 | implementation 'com.microsoft.azure:azure-storage:4.4.0' 22 | implementation 'com.microsoft.azure:azure-mgmt-storage:1.0.0-beta3' 23 | 24 | implementation "org.springframework.boot:spring-boot-starter-web" 25 | implementation "org.springframework.boot:spring-boot-starter-actuator" 26 | implementation "org.springframework.boot:spring-boot-autoconfigure" 27 | implementation "io.spinnaker.kork:kork-exceptions" 28 | implementation "io.spinnaker.kork:kork-security" 29 | 30 | testImplementation project(":front50-test") 31 | } 32 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/PluginVersionCleanupProperties.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.config; 17 | 18 | import java.time.Duration; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | /** Controls configuration for the plugin version pinning metadata storage cleanup. */ 22 | @ConfigurationProperties("storage-service.plugin-version-pinning.cleanup") 23 | public class PluginVersionCleanupProperties { 24 | /** The maximum number of pinned version records to keep by cluster (and location). */ 25 | public int maxVersionsPerCluster = 10; 26 | 27 | /** The interval that the cleanup agent will run. */ 28 | public Duration interval = Duration.ofDays(1); 29 | } 30 | -------------------------------------------------------------------------------- /front50-s3/front50-s3.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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(":front50-core") 19 | implementation project(":front50-api") 20 | 21 | implementation "org.springframework.boot:spring-boot-starter-web" 22 | implementation "org.springframework.boot:spring-boot-starter-actuator" 23 | implementation "org.springframework.boot:spring-boot-autoconfigure" 24 | implementation "io.spinnaker.kork:kork-core" 25 | implementation "io.spinnaker.kork:kork-aws" 26 | implementation "io.spinnaker.kork:kork-security" 27 | implementation "com.amazonaws:aws-java-sdk-s3" 28 | implementation "com.amazonaws:aws-java-sdk-sts" 29 | implementation "com.netflix.eureka:eureka-client" 30 | 31 | testImplementation project(":front50-test") 32 | } 33 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/echo/Notification.java: -------------------------------------------------------------------------------- 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 | 18 | package com.netflix.spinnaker.front50.echo; 19 | 20 | import java.util.Collection; 21 | import java.util.Map; 22 | import lombok.Data; 23 | import lombok.NonNull; 24 | 25 | @Data 26 | public class Notification { 27 | @NonNull Type notificationType; 28 | @NonNull Collection to; 29 | @NonNull String templateGroup; 30 | @NonNull Severity severity; 31 | @NonNull Source source; 32 | @NonNull Map additionalContext; 33 | 34 | @Data 35 | public static class Source { 36 | @NonNull String application; 37 | } 38 | 39 | public enum Type { 40 | EMAIL 41 | } 42 | 43 | public enum Severity { 44 | NORMAL, 45 | HIGH 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /front50-redis/src/test/groovy/com/netflix/spinnaker/front50/redis/config/UnhealthyRedisConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, 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.front50.redis.config; 18 | 19 | import org.springframework.boot.test.context.TestConfiguration; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Primary; 22 | import org.springframework.data.redis.connection.RedisConnectionFactory; 23 | import spock.mock.DetachedMockFactory; 24 | 25 | @TestConfiguration 26 | public class UnhealthyRedisConfig { 27 | private static DetachedMockFactory mockFactory = new DetachedMockFactory(); 28 | 29 | @Bean 30 | @Primary 31 | RedisConnectionFactory jedisConnectionFactory() { 32 | return mockFactory.Mock(RedisConnectionFactory.class); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /front50-integration/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/config/controllers/PipelineControllerConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Salesforce, 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 | 18 | package com.netflix.spinnaker.front50.config.controllers; 19 | 20 | import lombok.Data; 21 | import org.springframework.boot.context.properties.ConfigurationProperties; 22 | 23 | @Data 24 | @ConfigurationProperties(prefix = "controller.pipeline") 25 | public class PipelineControllerConfig { 26 | 27 | /** Holds the configurations to be used for save/update controller mappings */ 28 | private SavePipelineConfiguration save = new SavePipelineConfiguration(); 29 | 30 | @Data 31 | public static class SavePipelineConfiguration { 32 | /** This controls whether cache should be refreshes while checking for duplicate pipelines */ 33 | boolean refreshCacheOnDuplicatesCheck = true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /front50-api/src/main/java/com/netflix/spinnaker/front50/api/validator/PipelineValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.front50.api.validator; 18 | 19 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 20 | import com.netflix.spinnaker.kork.annotations.Beta; 21 | import com.netflix.spinnaker.kork.plugins.api.internal.SpinnakerExtensionPoint; 22 | 23 | /** 24 | * A {@link PipelineValidator} provides a hook where custom validation can be applied to pipeline 25 | * pre-save/update operations. 26 | */ 27 | @Beta 28 | public interface PipelineValidator extends SpinnakerExtensionPoint { 29 | /** 30 | * @param pipeline the pipeline being created/modified 31 | * @param errors specific validation errors for @param pipeline 32 | */ 33 | void validate(Pipeline pipeline, ValidatorErrors errors); 34 | } 35 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/plugins/PluginInfoRepository.java: -------------------------------------------------------------------------------- 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.front50.model.plugins; 17 | 18 | import com.netflix.spinnaker.front50.model.ItemDAO; 19 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo.Release; 20 | import java.util.Collection; 21 | import javax.annotation.Nonnull; 22 | 23 | public interface PluginInfoRepository extends ItemDAO { 24 | /** 25 | * Returns a collection of plugins that should be installed by a particular service. 26 | * 27 | *

This is determined by inference, using a {@link Release}'s {@code requires} field. 28 | * 29 | * @param service The service to retrieve plugin info for 30 | * @return All available plugins for a service 31 | */ 32 | @Nonnull 33 | Collection getByService(@Nonnull String service); 34 | } 35 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/HasCanonicalPluginIdValidator.java: -------------------------------------------------------------------------------- 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.front50.validator; 17 | 18 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo; 19 | import com.netflix.spinnaker.kork.plugins.CanonicalPluginId; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.validation.Errors; 22 | 23 | @Component 24 | public class HasCanonicalPluginIdValidator implements PluginInfoValidator { 25 | 26 | @Override 27 | public void validate(PluginInfo pluginInfo, Errors validationErrors) { 28 | if (!CanonicalPluginId.Companion.isValid(pluginInfo.getId())) { 29 | validationErrors.rejectValue( 30 | "id", 31 | "pluginInfo.id.invalid", 32 | "Plugin Info must have a '{namespace}.{id}' canonical format"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /front50-s3/src/main/java/com/netflix/spinnaker/front50/config/S3EventingProperties.java: -------------------------------------------------------------------------------- 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.front50.config; 18 | 19 | public class S3EventingProperties { 20 | boolean enabled = false; 21 | 22 | String snsTopicName; 23 | 24 | long refreshIntervalMs = 120000; 25 | 26 | public boolean isEnabled() { 27 | return enabled; 28 | } 29 | 30 | public void setEnabled(boolean enabled) { 31 | this.enabled = enabled; 32 | } 33 | 34 | public String getSnsTopicName() { 35 | return snsTopicName; 36 | } 37 | 38 | public void setSnsTopicName(String snsTopicName) { 39 | this.snsTopicName = snsTopicName; 40 | } 41 | 42 | public long getRefreshIntervalMs() { 43 | return refreshIntervalMs; 44 | } 45 | 46 | public void setRefreshIntervalMs(long refreshIntervalMs) { 47 | this.refreshIntervalMs = refreshIntervalMs; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/jackson/Front50ApiModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Armory, 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.front50.jackson; 18 | 19 | import com.fasterxml.jackson.databind.module.SimpleModule; 20 | import com.netflix.spinnaker.front50.api.model.Timestamped; 21 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 22 | import com.netflix.spinnaker.front50.jackson.mixins.PipelineMixins; 23 | import com.netflix.spinnaker.front50.jackson.mixins.TimestampedMixins; 24 | 25 | public class Front50ApiModule extends SimpleModule { 26 | 27 | public Front50ApiModule() { 28 | super("Front50 API"); 29 | } 30 | 31 | @Override 32 | public void setupModule(SetupContext context) { 33 | super.setupModule(context); 34 | context.setMixInAnnotations(Pipeline.class, PipelineMixins.class); 35 | context.setMixInAnnotations(Timestamped.class, TimestampedMixins.class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/HasOnePreferredReleaseValidator.java: -------------------------------------------------------------------------------- 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.front50.validator; 18 | 19 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.validation.Errors; 22 | 23 | @Component 24 | public class HasOnePreferredReleaseValidator implements PluginInfoValidator { 25 | 26 | @Override 27 | public void validate(PluginInfo pluginInfo, Errors validationErrors) { 28 | long preferredReleases = 29 | pluginInfo.getReleases().stream().filter(PluginInfo.Release::isPreferred).count(); 30 | 31 | if (preferredReleases > 1) { 32 | validationErrors.rejectValue( 33 | "preferred", 34 | "pluginInfo.releases.invalid", 35 | "Plugin Info Releases can have only one preferred release."); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /front50-redis/front50-redis.gradle: -------------------------------------------------------------------------------- 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 | 17 | configurations.all { 18 | exclude group: 'org.slf4j', module: 'slf4j-log4j12' 19 | } 20 | 21 | dependencies { 22 | implementation project(":front50-core") 23 | implementation project(":front50-api") 24 | implementation "io.spinnaker.kork:kork-exceptions" 25 | 26 | implementation("org.apache.groovy:groovy") 27 | 28 | implementation "org.springframework.boot:spring-boot-starter-web" 29 | implementation "org.springframework.boot:spring-boot-starter-actuator" 30 | implementation "org.springframework.boot:spring-boot-autoconfigure" 31 | implementation 'org.springframework.data:spring-data-redis' 32 | implementation 'redis.clients:jedis' 33 | implementation "com.github.ben-manes.caffeine:guava" 34 | 35 | testImplementation project(":front50-test") 36 | testImplementation "io.spinnaker.kork:kork-jedis-test" 37 | } 38 | 39 | tasks.compileGroovy.enabled = true 40 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/events/ApplicationPermissionEventListener.java: -------------------------------------------------------------------------------- 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.front50.events; 17 | 18 | import com.netflix.spinnaker.front50.model.application.Application; 19 | import javax.annotation.Nonnull; 20 | import javax.annotation.Nullable; 21 | 22 | public interface ApplicationPermissionEventListener { 23 | 24 | boolean supports(Type type); 25 | 26 | /** TODO(rz): Add Type to signature */ 27 | @Nullable 28 | Application.Permission call( 29 | @Nullable Application.Permission originalPermission, 30 | @Nullable Application.Permission updatedPermission); 31 | 32 | /** TODO(rz): Add type to signature */ 33 | void rollback(@Nonnull Application.Permission originalPermission); 34 | 35 | enum Type { 36 | PRE_UPDATE, 37 | POST_UPDATE, 38 | PRE_CREATE, 39 | POST_CREATE, 40 | PRE_DELETE, 41 | POST_DELETE 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /front50-api/src/main/java/com/netflix/spinnaker/front50/api/validator/ValidatorErrors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Armory, 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.front50.api.validator; 17 | 18 | import com.netflix.spinnaker.kork.annotations.Alpha; 19 | import com.netflix.spinnaker.kork.plugins.api.internal.SpinnakerExtensionPoint; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** A {@link ValidatorErrors} stores and reads multiple validation failure error messages */ 24 | @Alpha 25 | public class ValidatorErrors implements SpinnakerExtensionPoint { 26 | private List errors = new ArrayList<>(); 27 | 28 | public List getAllErrors() { 29 | return this.errors; 30 | } 31 | 32 | public String getAllErrorsMessage() { 33 | return String.join("\n", this.errors); 34 | } 35 | 36 | public Boolean hasErrors() { 37 | return !this.errors.isEmpty(); 38 | } 39 | 40 | public void reject(String message) { 41 | this.errors.add(message); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/events/ApplicationEventListener.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.events; 2 | 3 | import com.netflix.spinnaker.front50.model.application.Application; 4 | import com.netflix.spinnaker.kork.annotations.NonnullByDefault; 5 | import java.util.function.Consumer; 6 | import javax.annotation.Nullable; 7 | 8 | /** Allows mutating an {@link Application} at different {@link Type} hooks. */ 9 | public interface ApplicationEventListener 10 | extends Consumer { 11 | /** @return Whether the listener supports {@code type}. */ 12 | boolean supports(Type type); 13 | 14 | enum Type { 15 | PRE_UPDATE, 16 | POST_UPDATE, 17 | PRE_CREATE, 18 | POST_CREATE, 19 | PRE_DELETE, 20 | POST_DELETE; 21 | } 22 | 23 | @NonnullByDefault 24 | class ApplicationModelEvent { 25 | /** The {@link Type} of application event. */ 26 | public final Type type; 27 | 28 | /** The original {@link Application} state before modifications. */ 29 | @Nullable public final Application original; 30 | 31 | /** The updated {@link Application} state during modification. */ 32 | public final Application updated; 33 | 34 | public ApplicationModelEvent(Type type, @Nullable Application original, Application updated) { 35 | this.type = type; 36 | this.original = original; 37 | this.updated = updated; 38 | } 39 | 40 | /** Returns the {@code updated} {@link Application}, which is often the one you want. */ 41 | public Application getApplication() { 42 | return updated; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /front50-api-tck/src/test/kotlin/com/netflix/spinnaker/front50/api/test/Front50FixtureTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, 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 | 18 | package com.netflix.spinnaker.front50.api.test 19 | 20 | import com.netflix.spinnaker.front50.model.SqlStorageService 21 | import com.netflix.spinnaker.front50.model.StorageService 22 | import dev.minutest.junit.JUnit5Minutests 23 | import dev.minutest.rootContext 24 | import org.springframework.beans.factory.annotation.Autowired 25 | import strikt.api.expectThat 26 | import strikt.assertions.isA 27 | 28 | class Front50FixtureTest : JUnit5Minutests { 29 | 30 | fun tests() = rootContext { 31 | context("a front50 integration test environment") { 32 | front50Fixture { 33 | Fixture() 34 | } 35 | 36 | test("service starts with SQL storage service") { 37 | expectThat(storageService).isA() 38 | } 39 | } 40 | } 41 | 42 | private inner class Fixture : Front50Fixture() { 43 | 44 | @Autowired 45 | lateinit var storageService: StorageService 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/changelog/20190415-initial-snapshots-schema.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: create-snapshots-table 4 | author: ajordens 5 | changes: 6 | - createTable: 7 | tableName: snapshots 8 | columns: 9 | - column: 10 | name: id 11 | type: char(255) 12 | constraints: 13 | primaryKey: true 14 | nullable: false 15 | - column: 16 | name: body 17 | type: longtext 18 | constraints: 19 | nullable: false 20 | - column: 21 | name: created_at 22 | type: bigint 23 | constraints: 24 | nullable: false 25 | - column: 26 | name: last_modified_at 27 | type: bigint 28 | constraints: 29 | nullable: false 30 | - column: 31 | name: last_modified_by 32 | type: varchar(255) 33 | constraints: 34 | nullable: false 35 | - column: 36 | name: is_deleted 37 | type: boolean 38 | defaultValueBoolean: false 39 | constraints: 40 | nullable: false 41 | - modifySql: 42 | dbms: mysql 43 | append: 44 | value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" 45 | rollback: 46 | - dropTable: 47 | tableName: snapshots 48 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/changelog/20190415-initial-entity-tags-schema.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: create-entity-tags-table 4 | author: ajordens 5 | changes: 6 | - createTable: 7 | tableName: entity_tags 8 | columns: 9 | - column: 10 | name: id 11 | type: char(255) 12 | constraints: 13 | primaryKey: true 14 | nullable: false 15 | - column: 16 | name: body 17 | type: longtext 18 | constraints: 19 | nullable: false 20 | - column: 21 | name: created_at 22 | type: bigint 23 | constraints: 24 | nullable: false 25 | - column: 26 | name: last_modified_at 27 | type: bigint 28 | constraints: 29 | nullable: false 30 | - column: 31 | name: last_modified_by 32 | type: varchar(255) 33 | constraints: 34 | nullable: false 35 | - column: 36 | name: is_deleted 37 | type: boolean 38 | defaultValueBoolean: false 39 | constraints: 40 | nullable: false 41 | - modifySql: 42 | dbms: mysql 43 | append: 44 | value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" 45 | rollback: 46 | - dropTable: 47 | tableName: entity_tags 48 | -------------------------------------------------------------------------------- /front50-web/src/test/groovy/com/netflix/spinnaker/front50/controllers/SimpleExceptionHandlerExceptionResolver.groovy: -------------------------------------------------------------------------------- 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.front50.controllers 18 | 19 | import com.netflix.spinnaker.kork.web.exceptions.GenericExceptionHandlers 20 | import org.springframework.web.method.HandlerMethod 21 | import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver 22 | import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver 23 | import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod 24 | 25 | import java.lang.reflect.Method; 26 | 27 | class SimpleExceptionHandlerExceptionResolver extends ExceptionHandlerExceptionResolver { 28 | protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { 29 | Method method = new ExceptionHandlerMethodResolver(GenericExceptionHandlers.class).resolveMethod(exception); 30 | return new ServletInvocableHandlerMethod(new GenericExceptionHandlers(), method); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/Jooq.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.front50.model.sql 18 | 19 | import com.netflix.spinnaker.kork.core.RetrySupport 20 | import com.netflix.spinnaker.kork.sql.config.RetryProperties 21 | import org.jooq.DSLContext 22 | import org.jooq.impl.DSL 23 | 24 | internal val retrySupport = RetrySupport() 25 | 26 | /** 27 | * Run the provided [fn] in a transaction. 28 | */ 29 | internal fun DSLContext.transactional(retryProperties: RetryProperties, fn: (DSLContext) -> Unit) { 30 | retrySupport.retry( 31 | { 32 | transaction { ctx -> 33 | fn(DSL.using(ctx)) 34 | } 35 | }, 36 | retryProperties.maxRetries, retryProperties.backoffMs, false 37 | ) 38 | } 39 | 40 | /** 41 | * Run the provided [fn] with retry support. 42 | */ 43 | internal fun DSLContext.withRetry(retryProperties: RetryProperties, fn: (DSLContext) -> T): T { 44 | return retrySupport.retry( 45 | { 46 | fn(this) 47 | }, 48 | retryProperties.maxRetries, retryProperties.backoffMs, false 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/changelog/20191213-initial-plugin-artifacts-schema.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: create-plugin-artifacts-table 4 | author: robzienert 5 | changes: 6 | - createTable: 7 | tableName: plugin_artifacts 8 | columns: 9 | - column: 10 | name: id 11 | type: char(255) 12 | constraints: 13 | primaryKey: true 14 | nullable: false 15 | - column: 16 | name: body 17 | type: longtext 18 | constraints: 19 | nullable: false 20 | - column: 21 | name: created_at 22 | type: bigint 23 | constraints: 24 | nullable: false 25 | - column: 26 | name: last_modified_at 27 | type: bigint 28 | constraints: 29 | nullable: false 30 | - column: 31 | name: last_modified_by 32 | type: varchar(255) 33 | constraints: 34 | nullable: false 35 | - column: 36 | name: is_deleted 37 | type: boolean 38 | defaultValueBoolean: false 39 | constraints: 40 | nullable: false 41 | - modifySql: 42 | dbms: mysql 43 | append: 44 | value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" 45 | rollback: 46 | - dropTable: 47 | tableName: plugin_artifacts 48 | -------------------------------------------------------------------------------- /front50-core/src/test/groovy/com/netflix/spinnaker/front50/model/pipeline/PipelineTemplateSpec.groovy: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.model.pipeline 17 | 18 | import spock.lang.Specification 19 | import spock.lang.Unroll 20 | 21 | class PipelineTemplateSpec extends Specification { 22 | 23 | @Unroll 24 | def 'should match scopes'() { 25 | given: 26 | def template = new PipelineTemplate(metadata: [scopes: templateScopes]) 27 | 28 | expect: 29 | shouldMatch == template.containsAnyScope(requestedScopes) 30 | 31 | where: 32 | templateScopes | requestedScopes || shouldMatch 33 | ['global'] | ['global'] || true 34 | ['spinnaker.*'] | ['spinnaker_front50'] || true 35 | ['front50'] | ['FRONT50'] || true 36 | } 37 | 38 | def 'should be ok if template has no scopes'() { 39 | given: 40 | def template = new PipelineTemplate(metadata: [description: 'I have no scopes']) 41 | 42 | when: 43 | template.containsAnyScope(['global']) 44 | 45 | then: 46 | notThrown(NullPointerException) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /front50-redis/src/test/groovy/com/netflix/spinnaker/front50/redis/config/EmbeddedRedisConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, 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.front50.redis.config; 18 | 19 | import com.netflix.spinnaker.front50.redis.RedisConfigurationProperties; 20 | import com.netflix.spinnaker.kork.jedis.EmbeddedRedis; 21 | import org.springframework.boot.test.context.TestConfiguration; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Primary; 24 | 25 | @TestConfiguration 26 | public class EmbeddedRedisConfig { 27 | @Bean(destroyMethod = "destroy") 28 | EmbeddedRedis embeddedRedis() { 29 | return EmbeddedRedis.embed(); 30 | } 31 | 32 | @Bean 33 | @Primary 34 | RedisConfigurationProperties redisConfigurationProperties(EmbeddedRedis embeddedRedis) { 35 | RedisConfigurationProperties redisConfigurationProperties = new RedisConfigurationProperties(); 36 | redisConfigurationProperties.setHost(embeddedRedis.getHost()); 37 | redisConfigurationProperties.setPort(embeddedRedis.getPort()); 38 | return redisConfigurationProperties; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/pipeline/TemplateConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google, 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.front50.model.pipeline; 18 | 19 | public class TemplateConfiguration { 20 | 21 | private PipelineDefinition pipeline; 22 | 23 | public PipelineDefinition getPipeline() { 24 | return pipeline; 25 | } 26 | 27 | public void setPipeline(PipelineDefinition pipeline) { 28 | this.pipeline = pipeline; 29 | } 30 | 31 | public static class PipelineDefinition { 32 | 33 | private TemplateSource template; 34 | 35 | public TemplateSource getTemplate() { 36 | return template; 37 | } 38 | 39 | public void setTemplate(TemplateSource template) { 40 | this.template = template; 41 | } 42 | } 43 | 44 | public static class TemplateSource { 45 | 46 | public static final String SPINNAKER_PREFIX = "spinnaker://"; 47 | 48 | private String source; 49 | 50 | public String getSource() { 51 | return source; 52 | } 53 | 54 | public void setSource(String source) { 55 | this.source = source; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/annotations/AnyProviderExceptRedisCondition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Schibsted ASA. 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 | * 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 | package com.netflix.spinnaker.front50.config.annotations; 19 | 20 | import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 22 | 23 | public class AnyProviderExceptRedisCondition extends AnyNestedCondition { 24 | 25 | public AnyProviderExceptRedisCondition() { 26 | super(ConfigurationPhase.PARSE_CONFIGURATION); 27 | } 28 | 29 | @ConditionalOnProperty("sql.enabled") 30 | static class SqlEnabled {} 31 | 32 | @ConditionalOnProperty("spinnaker.s3.enabled") 33 | static class S3Enabled {} 34 | 35 | @ConditionalOnProperty("spinnaker.oracle.enabled") 36 | static class OracleEnabled {} 37 | 38 | @ConditionalOnProperty("spinnaker.gcs.enabled") 39 | static class GcsEnabled {} 40 | 41 | @ConditionalOnProperty("spinnaker.azs.enabled") 42 | static class AzureEnabled {} 43 | 44 | @ConditionalOnProperty("spinnaker.swift.enabled") 45 | static class SwiftEnabled {} 46 | } 47 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/filters/SimpleCORSFilter.java: -------------------------------------------------------------------------------- 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.spinnaker.front50.filters; 17 | 18 | import java.io.IOException; 19 | import javax.servlet.*; 20 | import javax.servlet.http.HttpServletResponse; 21 | import org.springframework.core.annotation.Order; 22 | import org.springframework.stereotype.Component; 23 | 24 | @Component 25 | @Order(Integer.MIN_VALUE) 26 | public class SimpleCORSFilter implements Filter { 27 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 28 | throws IOException, ServletException { 29 | HttpServletResponse response = (HttpServletResponse) res; 30 | response.setHeader("Access-Control-Allow-Origin", "*"); 31 | response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); 32 | response.setHeader("Access-Control-Max-Age", "3600"); 33 | response.setHeader("Access-Control-Allow-Headers", "x-requested-with, content-type"); 34 | chain.doFilter(req, res); 35 | } 36 | 37 | public void init(FilterConfig filterConfig) {} 38 | 39 | public void destroy() {} 40 | } 41 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/PluginBinaryCacheConfiguration.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.config; 17 | 18 | import com.netflix.spinnaker.front50.plugins.CachingPluginBinaryStorageService; 19 | import com.netflix.spinnaker.front50.plugins.PluginBinaryStorageService; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.context.annotation.Primary; 25 | 26 | @Configuration 27 | @ConditionalOnBean(PluginBinaryStorageService.class) 28 | @ConditionalOnProperty(value = "plugin-binary-cache.enabled", matchIfMissing = true) 29 | public class PluginBinaryCacheConfiguration { 30 | 31 | @Primary 32 | @Bean 33 | PluginBinaryStorageService cachingPluginBinaryStorageService( 34 | PluginBinaryStorageService pluginBinaryStorageService) { 35 | return new CachingPluginBinaryStorageService(pluginBinaryStorageService); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /front50-oracle/src/main/java/com/netflix/spinnaker/front50/config/OracleConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Oracle Corporation and/or its affiliates. All rights reserved. 3 | * 4 | * The contents of this file are subject to the Apache License Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * If a copy of the Apache License Version 2.0 was not distributed with this file, 7 | * You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.html 8 | */ 9 | package com.netflix.spinnaker.front50.config; 10 | 11 | import com.netflix.spinnaker.front50.model.OracleStorageService; 12 | import java.io.IOException; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.web.client.RestTemplate; 19 | 20 | @Configuration 21 | @ConditionalOnExpression("${spinnaker.oracle.enabled:false}") 22 | @EnableConfigurationProperties(OracleProperties.class) 23 | public class OracleConfig { 24 | 25 | @Bean 26 | public OracleStorageService oracleStorageService(OracleProperties oracleProperties) 27 | throws IOException { 28 | OracleStorageService oracleStorageService = new OracleStorageService(oracleProperties); 29 | oracleStorageService.ensureBucketExists(); 30 | return oracleStorageService; 31 | } 32 | 33 | @Bean 34 | @ConditionalOnMissingBean(RestTemplate.class) 35 | public RestTemplate restTemplate() { 36 | return new RestTemplate(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/EchoConfiguration.java: -------------------------------------------------------------------------------- 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 | 17 | package com.netflix.spinnaker.front50.config; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.netflix.spinnaker.config.DefaultServiceEndpoint; 21 | import com.netflix.spinnaker.front50.echo.EchoService; 22 | import com.netflix.spinnaker.kork.client.ServiceClientProvider; 23 | import org.springframework.beans.factory.annotation.Value; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Configuration; 27 | 28 | /** echo service configuration */ 29 | @Configuration 30 | @ConditionalOnProperty("services.echo.enabled") 31 | public class EchoConfiguration { 32 | @Bean 33 | EchoService echoService( 34 | ServiceClientProvider serviceClientProvider, 35 | @Value("${services.echo.base-url}") String baseUrl) { 36 | ObjectMapper objectMapper = new ObjectMapper(); 37 | return serviceClientProvider.getService( 38 | EchoService.class, new DefaultServiceEndpoint("echo", baseUrl), objectMapper); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 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 | 17 | ['fiat', '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 = "front50" 26 | 27 | include 'front50-web', 28 | 'front50-api', 29 | 'front50-api-tck', 30 | 'front50-core', 31 | 'front50-gcs', 32 | 'front50-integration', 33 | 'front50-redis', 34 | 'front50-s3', 35 | 'front50-sql', 36 | 'front50-sql-mysql', 37 | 'front50-sql-postgres', 38 | 'front50-test', 39 | 'front50-migrations', 40 | 'front50-azure', 41 | 'front50-swift', 42 | 'front50-oracle', 43 | 'front50-bom' 44 | 45 | def setBuildFile(project) { 46 | project.buildFileName = "${project.name}.gradle" 47 | project.children.each { 48 | setBuildFile(it) 49 | } 50 | } 51 | 52 | rootProject.children.each { 53 | setBuildFile(it) 54 | } 55 | 56 | // Set as an ext variable so that build scripts can access it 57 | gradle.ext.includedProviderProjects = includeProviders.split(',').collect{ ':front50-' + it.toLowerCase() } 58 | -------------------------------------------------------------------------------- /front50-web/src/test/groovy/com/netflix/spinnaker/front50/controllers/AdminControllerSpec.groovy: -------------------------------------------------------------------------------- 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 | 18 | package com.netflix.spinnaker.front50.controllers 19 | 20 | import com.netflix.spinnaker.front50.model.AdminOperations 21 | import com.netflix.spinnaker.front50.model.ObjectType 22 | import spock.lang.Specification 23 | import spock.lang.Subject 24 | 25 | class AdminControllerSpec extends Specification { 26 | 27 | AdminOperations adminOperations = Mock(AdminOperations) 28 | 29 | @Subject 30 | AdminController controller = new AdminController([adminOperations]) 31 | 32 | 33 | def 'should recover application and permission record'() { 34 | given: 35 | AdminOperations.Recover operation = new AdminOperations.Recover('application', 'test-app') 36 | 37 | when: 38 | controller.recover(operation) 39 | 40 | then: 41 | noExceptionThrown() 42 | 2 * adminOperations.recover(_) >> { AdminOperations.Recover op -> 43 | assert op.objectId == 'test-app' 44 | assert op.objectType.toLowerCase() in [ObjectType.APPLICATION.clazz.simpleName.toLowerCase(), ObjectType.APPLICATION_PERMISSION.clazz.simpleName.toLowerCase()] 45 | } 46 | 0 * _ 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /front50-core/src/test/groovy/com/netflix/spinnaker/front50/UntypedUtilsSpec.groovy: -------------------------------------------------------------------------------- 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 | 18 | package com.netflix.spinnaker.front50 19 | 20 | import spock.lang.Specification 21 | import spock.lang.Unroll 22 | 23 | class UntypedUtilsSpec extends Specification { 24 | 25 | @Unroll 26 | def "get and set property"() { 27 | given: 28 | def obj = new SomeObject() 29 | 30 | when: 31 | UntypedUtils.setProperty(obj, "property", value) 32 | 33 | then: 34 | value == UntypedUtils.getProperty(obj, "property") 35 | 36 | where: 37 | value << [null, "hello"] 38 | } 39 | 40 | def "get properties"() { 41 | given: 42 | def obj = new SomeObject(property: 1, property2: 2, property3: 3) 43 | 44 | expect: 45 | UntypedUtils.getProperties(obj) == [ 46 | property: "1", 47 | property2: "2", 48 | property3: "3" 49 | ] 50 | } 51 | 52 | def "has property"() { 53 | given: 54 | def obj = new SomeObject() 55 | 56 | expect: 57 | UntypedUtils.hasProperty(obj, "property") 58 | !UntypedUtils.hasProperty(obj, "nope") 59 | } 60 | 61 | class SomeObject { 62 | def property 63 | def property2 64 | def property3 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /front50-web/src/main/resources/graphql/pipelineConfig.graphqls: -------------------------------------------------------------------------------- 1 | scalar JSON 2 | 3 | schema { 4 | query: Query 5 | } 6 | 7 | type Query { 8 | version: String! 9 | pipelines(havingTriggerType: String, disabled: Boolean): [Pipeline]! 10 | } 11 | 12 | type Pipeline { 13 | id: ID! 14 | type: String! 15 | name: String! 16 | application: String! 17 | description: String 18 | disabled: Boolean! 19 | executionEngine: String! 20 | keepWaitingPipelines: Boolean! 21 | lastModifiedBy: String! 22 | limitConcurrent: Boolean! 23 | maxConcurrentExecutions: Int 24 | parameterConfig: [ParameterConfig]! 25 | spelEvaluator: String! 26 | stageCounter: Int 27 | stages: [Stage]! 28 | triggers(types: [String!]): [Trigger]! 29 | updateTs: String! 30 | extra: JSON 31 | } 32 | 33 | type ParameterConfig { 34 | name: String! 35 | label: String 36 | description: String 37 | default: String 38 | hasOptions: Boolean 39 | options: [ParameterConfigOption] 40 | extra: JSON 41 | } 42 | 43 | type ParameterConfigOption { 44 | value: String 45 | } 46 | 47 | type Stage { 48 | id: ID! 49 | type: String! 50 | name: String 51 | refId: ID 52 | requisiteStageRefIds: [ID] 53 | 54 | failPipeline: Boolean 55 | failOnFailedExpressions: Boolean 56 | continuePipeline: Boolean 57 | completeOtherBranchesThenFail: Boolean 58 | 59 | startTimeExpiry: Long 60 | overrideTimeout: Boolean 61 | stageTimeoutMs: Long 62 | 63 | restrictExecutionDuringTimeWindow: Boolean 64 | restrictedExecutionWindow: JSON 65 | 66 | sendNotifications: Boolean 67 | notifications: [JSON] 68 | 69 | extra: JSON 70 | } 71 | 72 | interface Trigger { 73 | type: String! 74 | enabled: Boolean 75 | extra: JSON 76 | } 77 | 78 | type CronTrigger implements Trigger { 79 | id: ID 80 | type: String! 81 | enabled: Boolean 82 | extra: JSON 83 | cronExpression: String! 84 | } 85 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/plugins/PluginInfoDelta.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.model.plugins; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.function.BiPredicate; 7 | import java.util.stream.Collectors; 8 | import javax.annotation.Nonnull; 9 | import javax.annotation.Nullable; 10 | 11 | public class PluginInfoDelta { 12 | public final List removedReleases; 13 | public final List addedReleases; 14 | public final @Nullable PluginInfo.Release oldPreferredRelease; 15 | public final @Nullable PluginInfo.Release newPreferredRelease; 16 | 17 | public PluginInfoDelta(@Nonnull PluginInfo newPluginInfo, @Nullable PluginInfo oldPluginInfo) { 18 | BiPredicate isSameRelease = 19 | (release1, release2) -> release1.getVersion().equals(release2.getVersion()); 20 | 21 | List oldReleases = 22 | Optional.ofNullable(oldPluginInfo).map(PluginInfo::getReleases).orElse(new ArrayList<>()); 23 | List newReleases = newPluginInfo.getReleases(); 24 | 25 | removedReleases = firstWithoutSecond(oldReleases, newReleases, isSameRelease); 26 | addedReleases = firstWithoutSecond(newReleases, oldReleases, isSameRelease); 27 | 28 | oldPreferredRelease = 29 | oldReleases.stream().filter(PluginInfo.Release::isPreferred).findFirst().orElse(null); 30 | newPreferredRelease = 31 | newReleases.stream().filter(PluginInfo.Release::isPreferred).findFirst().orElse(null); 32 | } 33 | 34 | private List firstWithoutSecond(List first, List second, BiPredicate isSame) { 35 | return first.stream() 36 | .filter(it -> second.stream().noneMatch(it2 -> isSame.test(it, it2))) 37 | .collect(Collectors.toList()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /front50-azure/src/main/java/com/netflix/spinnaker/front50/config/AzureStorageConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Microsoft, 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.front50.config; 18 | 19 | import com.netflix.spinnaker.front50.model.*; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | @Configuration 28 | @ConditionalOnExpression("${spinnaker.azs.enabled:false}") 29 | @EnableConfigurationProperties(AzureStorageProperties.class) 30 | public class AzureStorageConfig { 31 | @Bean 32 | @ConditionalOnMissingBean(RestTemplate.class) 33 | public RestTemplate restTemplate() { 34 | return new RestTemplate(); 35 | } 36 | 37 | @Bean 38 | public AzureStorageService azureStorageService(AzureStorageProperties azureStorageProperties) { 39 | return new AzureStorageService( 40 | azureStorageProperties.getStorageConnectionString(), 41 | azureStorageProperties.getStorageContainerName()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/HasValidRequiresFieldsValidator.java: -------------------------------------------------------------------------------- 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.front50.validator; 17 | 18 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo; 19 | import com.netflix.spinnaker.kork.plugins.VersionRequirementsParser; 20 | import com.netflix.spinnaker.kork.plugins.VersionRequirementsParser.InvalidPluginVersionRequirementException; 21 | import org.springframework.stereotype.Component; 22 | import org.springframework.validation.Errors; 23 | 24 | @Component 25 | public class HasValidRequiresFieldsValidator implements PluginInfoValidator { 26 | 27 | @Override 28 | public void validate(PluginInfo pluginInfo, Errors validationErrors) { 29 | 30 | pluginInfo 31 | .getReleases() 32 | .forEach( 33 | release -> { 34 | try { 35 | VersionRequirementsParser.INSTANCE.parseAll(release.getRequires()); 36 | } catch (InvalidPluginVersionRequirementException invalidPluginVersionRequirement) { 37 | validationErrors.reject( 38 | "pluginInfo.id.invalidPluginVersionRequirement", 39 | invalidPluginVersionRequirement.getMessage()); 40 | } 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /front50-api/src/main/java/com/netflix/spinnaker/front50/api/model/pipeline/ForwardingMap.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.api.model.pipeline; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import javax.annotation.Nullable; 7 | 8 | abstract class ForwardingMap implements Map { 9 | public ForwardingMap() {} 10 | 11 | protected abstract Map delegate(); 12 | 13 | @Override 14 | public int size() { 15 | return delegate().size(); 16 | } 17 | 18 | @Override 19 | public boolean isEmpty() { 20 | return delegate().isEmpty(); 21 | } 22 | 23 | @Override 24 | public boolean containsKey(Object key) { 25 | return delegate().containsKey(key); 26 | } 27 | 28 | @Override 29 | public boolean containsValue(@Nullable Object value) { 30 | return delegate().containsValue(value); 31 | } 32 | 33 | @Override 34 | public V get(Object key) { 35 | return delegate().get(key); 36 | } 37 | 38 | @Override 39 | public V put(K key, V value) { 40 | return delegate().put(key, value); 41 | } 42 | 43 | @Override 44 | public V remove(Object key) { 45 | return delegate().remove(key); 46 | } 47 | 48 | @Override 49 | public void putAll(Map m) { 50 | delegate().putAll(m); 51 | } 52 | 53 | @Override 54 | public void clear() { 55 | delegate().clear(); 56 | } 57 | 58 | @Override 59 | public Set keySet() { 60 | return delegate().keySet(); 61 | } 62 | 63 | @Override 64 | public Collection values() { 65 | return delegate().values(); 66 | } 67 | 68 | @Override 69 | public Set> entrySet() { 70 | return delegate().entrySet(); 71 | } 72 | 73 | @Override 74 | public boolean equals(@Nullable Object object) { 75 | return object == this || delegate().equals(object); 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | return delegate().hashCode(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /front50-api-tck/src/main/kotlin/com/netflix/spinnaker/front50/api/test/Front50Fixture.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, 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 | 18 | package com.netflix.spinnaker.front50.api.test 19 | 20 | import com.netflix.spinnaker.front50.Main 21 | import dev.minutest.TestContextBuilder 22 | import dev.minutest.TestDescriptor 23 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase 24 | import org.springframework.boot.test.context.SpringBootTest 25 | import org.springframework.test.context.TestContextManager 26 | import org.springframework.test.context.TestPropertySource 27 | 28 | /** 29 | * Front50Fixture is the base configuration for a Front50 integration test. 30 | * 31 | * The fixture uses an embedded SQL instance as a backing store. 32 | */ 33 | @SpringBootTest(classes = [Main::class]) 34 | @TestPropertySource(properties = ["spring.config.location=classpath:front50-test-app.yml"]) 35 | @AutoConfigureTestDatabase 36 | abstract class Front50Fixture 37 | 38 | /** 39 | * DSL for constructing a Front50Fixture within a Minutest suite. 40 | */ 41 | inline fun TestContextBuilder.front50Fixture( 42 | crossinline factory: (Unit).(testDescriptor: TestDescriptor) -> F 43 | ) { 44 | fixture { testDescriptor -> 45 | factory(testDescriptor).also { 46 | TestContextManager(F::class.java).prepareTestInstance(it) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/plugins/ServerGroupPluginVersions.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.model.plugins; 17 | 18 | import com.netflix.spinnaker.front50.api.model.Timestamped; 19 | import java.util.Map; 20 | import javax.annotation.Nonnull; 21 | import lombok.Data; 22 | 23 | @Data 24 | public class ServerGroupPluginVersions 25 | implements Timestamped, Comparable { 26 | 27 | @Nonnull private String id; 28 | 29 | @Nonnull private String serverGroupName; 30 | 31 | @Nonnull private String location; 32 | 33 | @Nonnull Map pluginVersions; 34 | 35 | private Long createTs; 36 | 37 | private Long lastModified; 38 | 39 | private String lastModifiedBy; 40 | 41 | public ServerGroupPluginVersions() { 42 | // Jackson 43 | } 44 | 45 | public ServerGroupPluginVersions( 46 | @Nonnull String id, 47 | @Nonnull String serverGroupName, 48 | @Nonnull String location, 49 | @Nonnull Map pluginVersions) { 50 | this.id = id; 51 | this.serverGroupName = serverGroupName; 52 | this.location = location; 53 | this.pluginVersions = pluginVersions; 54 | } 55 | 56 | @Override 57 | public int compareTo(@Nonnull ServerGroupPluginVersions o) { 58 | return createTs.compareTo(o.createTs); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/serviceaccount/ServiceAccount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google, 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.front50.model.serviceaccount; 18 | 19 | import com.fasterxml.jackson.annotation.JsonIgnore; 20 | import com.netflix.spinnaker.front50.api.model.Timestamped; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class ServiceAccount implements Timestamped { 25 | 26 | private String name; 27 | private Long lastModified; 28 | private String lastModifiedBy; 29 | private List memberOf = new ArrayList<>(); 30 | 31 | @Override 32 | @JsonIgnore 33 | public String getId() { 34 | return name.toLowerCase(); 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | public Long getLastModified() { 46 | return lastModified; 47 | } 48 | 49 | public void setLastModified(Long lastModified) { 50 | this.lastModified = lastModified; 51 | } 52 | 53 | public String getLastModifiedBy() { 54 | return lastModifiedBy; 55 | } 56 | 57 | public void setLastModifiedBy(String lastModifiedBy) { 58 | this.lastModifiedBy = lastModifiedBy; 59 | } 60 | 61 | public List getMemberOf() { 62 | return memberOf; 63 | } 64 | 65 | public void setMemberOf(List memberOf) { 66 | this.memberOf = memberOf; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /front50-core/src/test/groovy/com/netflix/spinnaker/front50/plugins/CachingPluginBinaryStorageServiceSpec.groovy: -------------------------------------------------------------------------------- 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.front50.plugins 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | import java.nio.file.Path 23 | import java.nio.file.Files 24 | 25 | class CachingPluginBinaryStorageServiceSpec extends Specification { 26 | 27 | PluginBinaryStorageService delegate = Mock() 28 | @Subject PluginBinaryStorageService subject = new CachingPluginBinaryStorageService(delegate) 29 | 30 | def "cache on store"() { 31 | when: 32 | subject.store("hello.zip", "world".bytes) 33 | 34 | then: 35 | 1 * delegate.store("hello.zip", "world".bytes) 36 | getCacheFile("hello.zip") == "world" 37 | } 38 | 39 | def "clear cache on delete"() { 40 | when: 41 | subject.delete("hello.zip") 42 | 43 | then: 44 | 1 * delegate.delete("hello.zip") 45 | !getCachePath("hello.zip").toFile().exists() 46 | } 47 | 48 | def "store on load"() { 49 | when: 50 | subject.load("hello.zip") 51 | 52 | then: 53 | 1 * delegate.load("hello.zip") >> "mom" 54 | getCacheFile("hello.zip") == "mom" 55 | } 56 | 57 | private Path getCachePath(String key) { 58 | return subject.CACHE_PATH.resolve(key) 59 | } 60 | 61 | private String getCacheFile(String key) { 62 | return new String(Files.readAllBytes(getCachePath(key))) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /front50-core/src/test/java/com/netflix/spinnaker/front50/validator/HasCanonicalPluginIdValidatorTest.java: -------------------------------------------------------------------------------- 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.front50.validator; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo; 22 | import java.util.stream.Stream; 23 | import org.junit.jupiter.params.ParameterizedTest; 24 | import org.junit.jupiter.params.provider.MethodSource; 25 | import org.springframework.validation.Errors; 26 | 27 | public class HasCanonicalPluginIdValidatorTest { 28 | private final HasCanonicalPluginIdValidator validator = new HasCanonicalPluginIdValidator(); 29 | 30 | @ParameterizedTest 31 | @MethodSource("pluginIdProvider") 32 | public void requiresCanonicalPluginId(final String id, final boolean hasErrors) { 33 | PluginInfo pluginInfo = new PluginInfo(); 34 | pluginInfo.setId(id); 35 | Errors errors = new GenericValidationErrors(pluginInfo); 36 | 37 | validator.validate(pluginInfo, errors); 38 | 39 | assertEquals(hasErrors, errors.hasErrors()); 40 | } 41 | 42 | private static Stream pluginIdProvider() { 43 | return Stream.of( 44 | new Object[] {"foo", true}, 45 | new Object[] {"foo/bar", true}, 46 | new Object[] {"foo.bar", false}, 47 | new Object[] {".", true}, 48 | new Object[] {".bar", true}, 49 | new Object[] {"foo.", true}); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /front50-gcs/front50-gcs.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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 from: "$rootDir/gradle/kotlin.gradle" 18 | 19 | dependencies { 20 | implementation project(":front50-core") 21 | implementation project(":front50-api") 22 | 23 | implementation "com.fasterxml.jackson.core:jackson-databind" 24 | implementation "com.google.apis:google-api-services-storage" 25 | implementation "com.google.auth:google-auth-library-oauth2-http" 26 | implementation "com.google.cloud:google-cloud-storage" 27 | implementation "com.google.guava:guava" 28 | implementation "com.netflix.spectator:spectator-api" 29 | implementation "io.spinnaker.kork:kork-exceptions" 30 | implementation "io.github.resilience4j:resilience4j-circuitbreaker" 31 | implementation "io.reactivex:rxjava" 32 | implementation "net.logstash.logback:logstash-logback-encoder" 33 | implementation "org.slf4j:slf4j-api" 34 | implementation "org.springframework:spring-beans" 35 | implementation "org.springframework:spring-context" 36 | implementation "org.springframework:spring-web" 37 | implementation "org.springframework.boot:spring-boot-autoconfigure" 38 | implementation "org.springframework.boot:spring-boot" 39 | testImplementation "com.google.http-client:google-http-client" 40 | testImplementation "com.google.cloud:google-cloud-core" 41 | testImplementation "io.strikt:strikt-core" 42 | testImplementation "io.mockk:mockk" 43 | testImplementation "org.junit.jupiter:junit-jupiter-api" 44 | } 45 | -------------------------------------------------------------------------------- /front50-azure/src/main/java/com/netflix/spinnaker/front50/config/AzureStorageProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Microsoft, 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.front50.config; 18 | 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | @ConfigurationProperties("spinnaker.azs") 22 | public class AzureStorageProperties { 23 | private String storageAccountKey; 24 | private String storageAccountName; 25 | private String storageContainerName = "spinnaker"; 26 | 27 | public String getStorageConnectionString() { 28 | return "DefaultEndpointsProtocol=https;" 29 | + "AccountName=" 30 | + this.storageAccountName 31 | + ";" 32 | + "AccountKey=" 33 | + this.storageAccountKey; 34 | } 35 | 36 | public String getStorageAccountKey() { 37 | return this.storageAccountKey; 38 | } 39 | 40 | public void setStorageAccountKey(String storageAccountKey) { 41 | this.storageAccountKey = storageAccountKey; 42 | } 43 | 44 | public String getStorageAccountName() { 45 | return this.storageAccountName; 46 | } 47 | 48 | public void setStorageAccountName(String storageAccountName) { 49 | this.storageAccountName = storageAccountName; 50 | } 51 | 52 | public String getStorageContainerName() { 53 | return this.storageContainerName; 54 | } 55 | 56 | public void setStorageContainerName(String storageContainerName) { 57 | this.storageContainerName = storageContainerName; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /front50-core/src/test/java/com/netflix/spinnaker/front50/validator/HasNameValidatorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.netflix.spinnaker.front50.validator; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import com.netflix.spinnaker.front50.model.application.Application; 22 | import java.util.stream.Stream; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.params.ParameterizedTest; 25 | import org.junit.jupiter.params.provider.MethodSource; 26 | 27 | public class HasNameValidatorTest { 28 | private final HasNameValidator validator = new HasNameValidator(); 29 | 30 | @ParameterizedTest(name = "Name validation: {0} should result in {1} error(s)") 31 | @MethodSource("nameProvider") 32 | @DisplayName("Name validation test") 33 | public void testNameValidation(String name, int numberOfErrors) { 34 | Application application = new Application(); 35 | ApplicationValidationErrors errors = new ApplicationValidationErrors(application); 36 | 37 | application.setName(name); 38 | validator.validate(application, errors); 39 | 40 | assertEquals( 41 | numberOfErrors, 42 | errors.getAllErrors().size(), 43 | String.format("Expected %d error(s) for name: '%s'", numberOfErrors, name)); 44 | } 45 | 46 | private static Stream nameProvider() { 47 | return Stream.of( 48 | new Object[] {null, 1}, 49 | new Object[] {"", 1}, 50 | new Object[] {" ", 1}, 51 | new Object[] {"application", 0}); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /front50-web/src/test/groovy/com/netflix/spinnaker/front50/ItemDAOHealthIndicatorSpec.groovy: -------------------------------------------------------------------------------- 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 | 18 | 19 | package com.netflix.spinnaker.front50 20 | 21 | import com.netflix.spinnaker.front50.ItemDAOHealthIndicator 22 | import com.netflix.spinnaker.front50.model.application.ApplicationDAO 23 | import org.springframework.boot.actuate.health.Status 24 | import org.springframework.scheduling.TaskScheduler 25 | import spock.lang.Specification 26 | import spock.lang.Subject 27 | 28 | class ItemDAOHealthIndicatorSpec extends Specification { 29 | ApplicationDAO dao = Mock(ApplicationDAO) 30 | 31 | @Subject 32 | ItemDAOHealthIndicator healthCheck = new ItemDAOHealthIndicator(dao, Stub(TaskScheduler)) 33 | 34 | void 'health check should return 5xx error if dao is not working'() { 35 | when: 36 | healthCheck.run() 37 | def result = healthCheck.health() 38 | 39 | then: 40 | 1 * dao.isHealthy() >> false 41 | result.status == Status.DOWN 42 | } 43 | 44 | void 'health check should return 5xx error if dao throws an error'() { 45 | when: 46 | healthCheck.run() 47 | def result = healthCheck.health() 48 | 49 | then: 50 | 1 * dao.isHealthy() >> { throw new RuntimeException("Boom goes the dynamite") } 51 | result.status == Status.DOWN 52 | } 53 | 54 | void 'health check should return Ok'() { 55 | when: 56 | healthCheck.run() 57 | def result = healthCheck.health() 58 | 59 | then: 60 | 1 * dao.isHealthy() >> true 61 | result.status == Status.UP 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /front50-core/src/test/java/com/netflix/spinnaker/front50/validator/HasEmailValidatorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.netflix.spinnaker.front50.validator; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import com.netflix.spinnaker.front50.model.application.Application; 22 | import java.util.stream.Stream; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.params.ParameterizedTest; 25 | import org.junit.jupiter.params.provider.MethodSource; 26 | 27 | public class HasEmailValidatorTest { 28 | private final ApplicationValidator validator = new HasEmailValidator(); 29 | 30 | @ParameterizedTest(name = "{0} is {2} email") 31 | @MethodSource("emailProvider") 32 | @DisplayName("Email validation test") 33 | public void testEmailValidation(String email, int numberOfErrors) { 34 | Application application = new Application(); 35 | ApplicationValidationErrors errors = new ApplicationValidationErrors(application); 36 | 37 | application.setEmail(email); 38 | validator.validate(application, errors); 39 | 40 | assertEquals( 41 | numberOfErrors, 42 | errors.getAllErrors().size(), 43 | String.format("Expected %d error(s) for email: '%s'", numberOfErrors, email)); 44 | } 45 | 46 | private static Stream emailProvider() { 47 | return Stream.of( 48 | new Object[] {null, 1, "an empty"}, 49 | new Object[] {"", 1, "an empty"}, 50 | new Object[] {" ", 1, "an empty"}, 51 | new Object[] {"email@netflix.com", 0, "a non-empty"}); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/Front50CoreConfiguration.java: -------------------------------------------------------------------------------- 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.front50.config; 17 | 18 | import com.fasterxml.jackson.databind.Module; 19 | import com.netflix.spinnaker.front50.jackson.Front50ApiModule; 20 | import com.netflix.spinnaker.moniker.Namer; 21 | import com.netflix.spinnaker.moniker.frigga.FriggaReflectiveNamer; 22 | import java.util.List; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | import org.springframework.web.client.RestTemplate; 29 | 30 | @Configuration 31 | @EnableConfigurationProperties({FiatConfigurationProperties.class}) 32 | public class Front50CoreConfiguration { 33 | 34 | @Bean 35 | @ConditionalOnMissingBean(RestTemplate.class) 36 | public RestTemplate restTemplate() { 37 | return new RestTemplate(); 38 | } 39 | 40 | @Bean 41 | @ConditionalOnMissingBean(Namer.class) 42 | public Namer namer() { 43 | return new FriggaReflectiveNamer(); 44 | } 45 | 46 | @Bean 47 | Jackson2ObjectMapperBuilderCustomizer defaultObjectMapperCustomizer(List modules) { 48 | return jacksonObjectMapperBuilder -> { 49 | modules.addAll(List.of(new Front50ApiModule())); 50 | jacksonObjectMapperBuilder.modules(modules); 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /front50-gcs/src/main/java/com/netflix/spinnaker/front50/config/GcsProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.config; 18 | 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | @ConfigurationProperties("spinnaker.gcs") 22 | public class GcsProperties { 23 | private String bucket; 24 | 25 | private String bucketLocation = ""; 26 | 27 | private String rootFolder = "front50"; 28 | 29 | private String jsonPath = ""; 30 | 31 | private String project = ""; 32 | 33 | public String getBucket() { 34 | return bucket; 35 | } 36 | 37 | public void setBucket(String bucket) { 38 | // "google.com:" is deprecated but may be in certain old projects. 39 | if (bucket.startsWith("google.com:")) { 40 | bucket = bucket.substring("google.com:".length()); 41 | } 42 | this.bucket = bucket; 43 | } 44 | 45 | public String getBucketLocation() { 46 | return bucketLocation; 47 | } 48 | 49 | public void setBucketLocation(String bucketLocation) { 50 | this.bucketLocation = bucketLocation; 51 | } 52 | 53 | public String getRootFolder() { 54 | return rootFolder; 55 | } 56 | 57 | public void setRootFolder(String rootFolder) { 58 | this.rootFolder = rootFolder; 59 | } 60 | 61 | public String getJsonPath() { 62 | return jsonPath; 63 | } 64 | 65 | public void setJsonPath(String jsonPath) { 66 | this.jsonPath = jsonPath; 67 | } 68 | 69 | public String getProject() { 70 | return project; 71 | } 72 | 73 | public void setProject(String project) { 74 | this.project = project; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/controllers/PermissionsController.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.front50.controllers; 2 | 3 | import com.netflix.spinnaker.front50.ApplicationPermissionsService; 4 | import com.netflix.spinnaker.front50.model.application.Application; 5 | import io.swagger.v3.oas.annotations.Operation; 6 | import java.util.Set; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | @RestController 10 | @RequestMapping("/permissions") 11 | public class PermissionsController { 12 | 13 | private final ApplicationPermissionsService permissionsService; 14 | 15 | public PermissionsController(ApplicationPermissionsService permissionsService) { 16 | this.permissionsService = permissionsService; 17 | } 18 | 19 | @Operation(summary = "", description = "Get all application permissions. Internal use only.") 20 | @RequestMapping(method = RequestMethod.GET, value = "/applications") 21 | public Set getAllApplicationPermissions() { 22 | return permissionsService.getAllApplicationPermissions(); 23 | } 24 | 25 | @RequestMapping(method = RequestMethod.GET, value = "/applications/{appName:.+}") 26 | public Application.Permission getApplicationPermission(@PathVariable String appName) { 27 | return permissionsService.getApplicationPermission(appName); 28 | } 29 | 30 | @Operation(summary = "", description = "Create an application permission.") 31 | @RequestMapping(method = RequestMethod.POST, value = "/applications") 32 | public Application.Permission createApplicationPermission( 33 | @RequestBody Application.Permission newPermission) { 34 | return permissionsService.createApplicationPermission(newPermission); 35 | } 36 | 37 | @RequestMapping(method = RequestMethod.PUT, value = "/applications/{appName:.+}") 38 | public Application.Permission updateApplicationPermission( 39 | @PathVariable String appName, @RequestBody Application.Permission newPermission) { 40 | return permissionsService.updateApplicationPermission(appName, newPermission, false); 41 | } 42 | 43 | @RequestMapping(method = RequestMethod.DELETE, value = "/applications/{appName:.+}") 44 | public void deleteApplicationPermission(@PathVariable String appName) { 45 | permissionsService.deleteApplicationPermission(appName); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/validator/ApplicationNameValidator.java: -------------------------------------------------------------------------------- 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.front50.validator; 18 | 19 | import com.google.common.base.Strings; 20 | import com.netflix.spinnaker.front50.model.application.Application; 21 | import java.util.Optional; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 24 | import org.springframework.stereotype.Component; 25 | 26 | @Component 27 | @EnableConfigurationProperties(ApplicationNameValidatorConfigurationProperties.class) 28 | public class ApplicationNameValidator implements ApplicationValidator { 29 | private ApplicationNameValidatorConfigurationProperties properties; 30 | 31 | @Autowired 32 | public ApplicationNameValidator(ApplicationNameValidatorConfigurationProperties properties) { 33 | this.properties = properties; 34 | } 35 | 36 | @Override 37 | public void validate(Application application, ApplicationValidationErrors validationErrors) { 38 | if (Strings.isNullOrEmpty(properties.getValidationRegex())) { 39 | return; 40 | } 41 | 42 | String appName = Optional.ofNullable(application.getName()).orElse(""); 43 | if (!appName.matches(properties.getValidationRegex())) { 44 | validationErrors.rejectValue( 45 | "name", 46 | "application.name.invalid", 47 | Optional.ofNullable(properties.getValidationMessage()) 48 | .orElse( 49 | "Application name doesn't satisfy the validation regex: " 50 | + properties.getValidationRegex())); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /front50-web/src/main/java/com/netflix/spinnaker/front50/controllers/PluginVersionController.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.controllers; 17 | 18 | import com.netflix.spinnaker.front50.config.annotations.ConditionalOnAnyProviderExceptRedisIsEnabled; 19 | import com.netflix.spinnaker.front50.model.plugins.PluginInfo; 20 | import com.netflix.spinnaker.front50.model.plugins.PluginVersionPinningService; 21 | import java.util.Map; 22 | import org.springframework.web.bind.annotation.PathVariable; 23 | import org.springframework.web.bind.annotation.PutMapping; 24 | import org.springframework.web.bind.annotation.RequestBody; 25 | import org.springframework.web.bind.annotation.RequestMapping; 26 | import org.springframework.web.bind.annotation.RequestParam; 27 | import org.springframework.web.bind.annotation.RestController; 28 | 29 | @RestController 30 | @RequestMapping("/pluginVersions") 31 | @ConditionalOnAnyProviderExceptRedisIsEnabled 32 | public class PluginVersionController { 33 | 34 | private final PluginVersionPinningService pluginVersionPinningService; 35 | 36 | public PluginVersionController(PluginVersionPinningService pluginVersionPinningService) { 37 | this.pluginVersionPinningService = pluginVersionPinningService; 38 | } 39 | 40 | @PutMapping("/{serverGroupName}") 41 | Map pinVersions( 42 | @PathVariable String serverGroupName, 43 | @RequestParam String location, 44 | @RequestParam String serviceName, 45 | @RequestBody Map pinnedVersions) { 46 | return pluginVersionPinningService.pinVersions( 47 | serviceName, location, serverGroupName, pinnedVersions); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /front50-web/src/test/groovy/com/netflix/spinnaker/front50/controllers/AuthorizationSupportSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google, 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.front50.controllers 18 | 19 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 20 | import com.netflix.spinnaker.fiat.shared.FiatPermissionEvaluator 21 | import com.netflix.spinnaker.front50.api.model.pipeline.Trigger 22 | import spock.lang.Specification 23 | import spock.lang.Subject 24 | import spock.lang.Unroll 25 | 26 | class AuthorizationSupportSpec extends Specification { 27 | 28 | FiatPermissionEvaluator evaluator = Mock(FiatPermissionEvaluator) 29 | 30 | @Subject 31 | AuthorizationSupport authorizationSupport = new AuthorizationSupport(evaluator) 32 | 33 | @Unroll 34 | def "should validate run as user access"() { 35 | expect: 36 | authorizationSupport.hasRunAsUserPermission(new Pipeline()) == true 37 | authorizationSupport.hasRunAsUserPermission(new Pipeline(triggers: [])) == true 38 | 39 | when: 40 | Pipeline p = new Pipeline(application: "app", 41 | triggers: [new Trigger(["runAsUser": "service-acct"])]) 42 | def result = authorizationSupport.hasRunAsUserPermission(p) 43 | 44 | then: 45 | evaluator.hasPermission(_, _, 'SERVICE_ACCOUNT', _) >> userAccess 46 | evaluator.hasPermission(_, _, 'APPLICATION', _) >> serviceAccountAccess 47 | result == expected 48 | 49 | where: 50 | userAccess | serviceAccountAccess || expected 51 | false | false || false 52 | false | true || false 53 | true | false || false 54 | true | true || true 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /front50-swift/src/main/java/com/netflix/spinnaker/front50/config/SwiftConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Veritas Technologies, LLC. 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.front50.config; 18 | 19 | import com.netflix.spectator.api.Registry; 20 | import com.netflix.spinnaker.front50.model.SwiftStorageService; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.web.client.RestTemplate; 30 | 31 | @Configuration 32 | @ConditionalOnExpression("${spinnaker.swift.enabled:false}") 33 | @EnableConfigurationProperties(SwiftProperties.class) 34 | public class SwiftConfig { 35 | 36 | @Autowired Registry registry; 37 | 38 | private final Logger log = LoggerFactory.getLogger(getClass()); 39 | 40 | @Bean 41 | public SwiftStorageService swiftService(SwiftProperties properties) { 42 | return new SwiftStorageService( 43 | properties.getContainerName(), 44 | properties.getIdentityEndpoint(), 45 | properties.getUsername(), 46 | properties.getPassword(), 47 | properties.getProjectName(), 48 | properties.getDomainName()); 49 | } 50 | 51 | @Bean 52 | @ConditionalOnMissingBean(RestTemplate.class) 53 | public RestTemplate restTemplate() { 54 | return new RestTemplate(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/changelog-master.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - include: 3 | file: changelog/20190415-initial-application-permissions-schema.yml 4 | relativeToChangelogFile: true 5 | - include: 6 | file: changelog/20190415-initial-applications-schema.yml 7 | relativeToChangelogFile: true 8 | - include: 9 | file: changelog/20190415-initial-deliveries-schema.yml 10 | relativeToChangelogFile: true 11 | - include: 12 | file: changelog/20190415-initial-entity-tags-schema.yml 13 | relativeToChangelogFile: true 14 | - include: 15 | file: changelog/20190415-initial-notifications-schema.yml 16 | relativeToChangelogFile: true 17 | - include: 18 | file: changelog/20190415-initial-pipeline-strategies-schema.yml 19 | relativeToChangelogFile: true 20 | - include: 21 | file: changelog/20190415-initial-pipeline-templates-schema.yml 22 | relativeToChangelogFile: true 23 | - include: 24 | file: changelog/20190415-initial-pipelines-schema.yml 25 | relativeToChangelogFile: true 26 | - include: 27 | file: changelog/20190415-initial-projects-schema.yml 28 | relativeToChangelogFile: true 29 | - include: 30 | file: changelog/20190415-initial-service-accounts-schema.yml 31 | relativeToChangelogFile: true 32 | - include: 33 | file: changelog/20190415-initial-snapshots-schema.yml 34 | relativeToChangelogFile: true 35 | - include: 36 | file: changelog/20190530-add-is-deleted-indexes.yml 37 | relativeToChangelogFile: true 38 | - include: 39 | file: changelog/20190530-add-is-deleted-indexes-postgres.yml 40 | relativeToChangelogFile: true 41 | - include: 42 | file: changelog/20191112-modify-char-columns-postgres.yml 43 | relativeToChangelogFile: true 44 | - include: 45 | file: changelog/20191213-initial-plugin-artifacts-schema.yml 46 | relativeToChangelogFile: true 47 | - include: 48 | file: changelog/20200113-rename-plugin-artifacts-to-plugin-info.yml 49 | relativeToChangelogFile: true 50 | - include: 51 | file: changelog/20200428-initial-plugin-versions-schema.yml 52 | relativeToChangelogFile: true 53 | - include: 54 | file: changelog/20230420-add-last-modified-at-indexes.yml 55 | relativeToChangelogFile: true 56 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/plugins/PluginBinaryStorageService.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.plugins; 17 | 18 | import static java.lang.String.format; 19 | 20 | import java.util.List; 21 | import javax.annotation.Nonnull; 22 | import javax.annotation.Nullable; 23 | 24 | /** A specialized storage service for plugin binaries. */ 25 | public interface PluginBinaryStorageService { 26 | 27 | /** 28 | * Store a new version of a plugin binary. 29 | * 30 | *

If the key already exists, the storage service should not accept the request. 31 | * 32 | * @param key The plugin binary key 33 | * @param item The plugin binary 34 | */ 35 | void store(@Nonnull String key, @Nonnull byte[] item); 36 | 37 | /** 38 | * Deletes an existing plugin binary. 39 | * 40 | * @param key The plugin binary key 41 | */ 42 | void delete(@Nonnull String key); 43 | 44 | /** 45 | * Get a list of all plugin binaries that are currently stored. 46 | * 47 | * @return A list of all plugin binary keys 48 | */ 49 | @Nonnull 50 | List listKeys(); 51 | 52 | /** 53 | * Load a single plugin binary, returning its raw data. 54 | * 55 | * @param key The plugin binary key 56 | * @return The plugin binary, if it exists 57 | */ 58 | @Nullable 59 | byte[] load(@Nonnull String key); 60 | 61 | /** 62 | * Create a binary key. 63 | * 64 | * @param pluginId The plugin ID. 65 | * @param version The plugin version. 66 | * @return The plugin version binary storage key. 67 | */ 68 | default String getKey(String pluginId, String version) { 69 | return format("%s/%s.zip", pluginId, version); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /front50-gcs/src/test/kotlin/com/netflix/spinnaker/front50/model/GcsIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC 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 | 18 | package com.netflix.spinnnaker.front50.model 19 | 20 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; 21 | import com.netflix.spinnaker.front50.config.GcsConfig 22 | import com.netflix.spinnaker.front50.model.GcsIntegrationTestConfiguration 23 | import com.netflix.spinnaker.front50.model.GcsStorageService 24 | import com.netflix.spinnaker.front50.model.ObjectType 25 | import org.junit.jupiter.api.Test 26 | import org.junit.jupiter.api.extension.ExtendWith 27 | import org.springframework.beans.factory.annotation.Autowired 28 | import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer 29 | import org.springframework.test.context.ContextConfiguration 30 | import org.springframework.test.context.TestPropertySource 31 | import org.springframework.test.context.junit.jupiter.SpringExtension 32 | import strikt.api.expectThat 33 | import strikt.assertions.hasSize 34 | import strikt.assertions.isEmpty 35 | 36 | @ExtendWith(SpringExtension::class) 37 | @ContextConfiguration( 38 | classes = [GcsConfig::class, GcsIntegrationTestConfiguration::class], 39 | initializers = [ConfigDataApplicationContextInitializer::class] 40 | ) 41 | @TestPropertySource(properties = ["spring.config.location=classpath:minimal-gcs-account.yml"]) 42 | class GcsIntegrationTest { 43 | @Test 44 | fun startupTest(@Autowired storageService: GcsStorageService) { 45 | expectThat(storageService.listObjectKeys(ObjectType.PIPELINE)).isEmpty() 46 | storageService.storeObject(ObjectType.PIPELINE, "my-key", Pipeline()) 47 | expectThat(storageService.listObjectKeys(ObjectType.PIPELINE)).hasSize(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/pipeline/DefaultPipelineTemplateDAO.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.model.pipeline; 17 | 18 | import com.netflix.spectator.api.Registry; 19 | import com.netflix.spinnaker.front50.config.StorageServiceConfigurationProperties; 20 | import com.netflix.spinnaker.front50.model.ObjectKeyLoader; 21 | import com.netflix.spinnaker.front50.model.ObjectType; 22 | import com.netflix.spinnaker.front50.model.StorageService; 23 | import com.netflix.spinnaker.front50.model.StorageServiceSupport; 24 | import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; 25 | import org.springframework.util.Assert; 26 | import rx.Scheduler; 27 | 28 | public class DefaultPipelineTemplateDAO extends StorageServiceSupport 29 | implements PipelineTemplateDAO { 30 | 31 | public DefaultPipelineTemplateDAO( 32 | StorageService service, 33 | Scheduler scheduler, 34 | ObjectKeyLoader objectKeyLoader, 35 | StorageServiceConfigurationProperties.PerObjectType configurationProperties, 36 | Registry registry, 37 | CircuitBreakerRegistry circuitBreakerRegistry) { 38 | super( 39 | ObjectType.PIPELINE_TEMPLATE, 40 | service, 41 | scheduler, 42 | objectKeyLoader, 43 | configurationProperties, 44 | registry, 45 | circuitBreakerRegistry); 46 | } 47 | 48 | @Override 49 | public PipelineTemplate create(String id, PipelineTemplate item) { 50 | Assert.notNull(item.getId(), "id field must NOT to be null!"); 51 | Assert.notEmpty(item.getScopes(), "scopes field must have at least ONE scope!"); 52 | 53 | update(id, item); 54 | return findById(id); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /front50-swift/src/main/java/com/netflix/spinnaker/front50/config/SwiftProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Veritas Technologies, LLC. 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.front50.config; 18 | 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | @ConfigurationProperties("spinnaker.swift") 22 | public class SwiftProperties { 23 | private String containerName; 24 | 25 | private String identityEndpoint; 26 | 27 | // TODO: Refactor to use .openrc file 28 | private String username; 29 | private String password; 30 | 31 | private String projectName; 32 | private String domainName; 33 | 34 | public String getContainerName() { 35 | return containerName; 36 | } 37 | 38 | public void setContainerName(String containerName) { 39 | this.containerName = containerName; 40 | } 41 | 42 | public String getIdentityEndpoint() { 43 | return identityEndpoint; 44 | } 45 | 46 | public void setIdentityEndpoint(String identityEndpoint) { 47 | this.identityEndpoint = identityEndpoint; 48 | } 49 | 50 | public String getProjectName() { 51 | return projectName; 52 | } 53 | 54 | public void setProjectName(String projectName) { 55 | this.projectName = projectName; 56 | } 57 | 58 | public String getDomainName() { 59 | return domainName; 60 | } 61 | 62 | public void setDomainName(String domainName) { 63 | this.domainName = domainName; 64 | } 65 | 66 | public String getUsername() { 67 | return username; 68 | } 69 | 70 | public void setUsername(String username) { 71 | this.username = username; 72 | } 73 | 74 | public String getPassword() { 75 | return password; 76 | } 77 | 78 | public void setPassword(String password) { 79 | this.password = password; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/config/PluginVersioningConfiguration.java: -------------------------------------------------------------------------------- 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 | package com.netflix.spinnaker.front50.config; 17 | 18 | import com.netflix.spinnaker.front50.config.annotations.ConditionalOnAnyProviderExceptRedisIsEnabled; 19 | import com.netflix.spinnaker.front50.model.plugins.PluginInfoRepository; 20 | import com.netflix.spinnaker.front50.model.plugins.PluginVersionCleanupAgent; 21 | import com.netflix.spinnaker.front50.model.plugins.PluginVersionPinningRepository; 22 | import com.netflix.spinnaker.front50.model.plugins.PluginVersionPinningService; 23 | import com.netflix.spinnaker.moniker.Namer; 24 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Configuration; 27 | import org.springframework.scheduling.TaskScheduler; 28 | 29 | @Configuration 30 | @EnableConfigurationProperties(PluginVersionCleanupProperties.class) 31 | @ConditionalOnAnyProviderExceptRedisIsEnabled 32 | public class PluginVersioningConfiguration { 33 | 34 | @Bean 35 | PluginVersionPinningService pluginVersionPinningService( 36 | PluginVersionPinningRepository pluginVersionPinningRepository, 37 | PluginInfoRepository pluginInfoRepository) { 38 | return new PluginVersionPinningService(pluginVersionPinningRepository, pluginInfoRepository); 39 | } 40 | 41 | @Bean 42 | PluginVersionCleanupAgent pluginVersionCleanupAgent( 43 | PluginVersionPinningRepository repository, 44 | PluginVersionCleanupProperties properties, 45 | Namer namer, 46 | TaskScheduler taskScheduler) { 47 | return new PluginVersionCleanupAgent(repository, properties, namer, taskScheduler); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /front50-sql/src/main/resources/db/changelog/20200428-initial-plugin-versions-schema.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: create-plugin-versions-table 4 | author: robzienert 5 | changes: 6 | - createTable: 7 | tableName: plugin_versions 8 | columns: 9 | - column: 10 | name: id 11 | type: char(255) 12 | constraints: 13 | primaryKey: true 14 | nullable: false 15 | - column: 16 | name: body 17 | type: longtext 18 | constraints: 19 | nullable: false 20 | - column: 21 | name: created_at 22 | type: bigint 23 | constraints: 24 | nullable: false 25 | - column: 26 | name: last_modified_at 27 | type: bigint 28 | constraints: 29 | nullable: false 30 | - column: 31 | name: last_modified_by 32 | type: varchar(255) 33 | constraints: 34 | nullable: false 35 | - column: 36 | name: is_deleted 37 | type: boolean 38 | defaultValueBoolean: false 39 | constraints: 40 | nullable: false 41 | - modifySql: 42 | dbms: mysql 43 | append: 44 | value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" 45 | rollback: 46 | - dropTable: 47 | tableName: plugin_versions 48 | - changeSet: 49 | id: plugin-versions-is-deleted-index 50 | author: rzienert 51 | changes: 52 | - createIndex: 53 | indexName: is_deleted_plugin_versions_idx 54 | tableName: plugin_versions 55 | columns: 56 | - column: 57 | name: is_deleted 58 | - changeSet: 59 | preConditions: 60 | onFail: CONTINUE 61 | dbms: 62 | type: postgresql 63 | id: modify-plugin-versions-id-to-varchar 64 | author: rzienert 65 | changes: 66 | - modifyDataType: 67 | columnName: id 68 | newDataType: varchar(255) 69 | tableName: plugin_versions 70 | -------------------------------------------------------------------------------- /front50-migrations/src/test/groovy/com/netflix/spinnaker/front50/migrations/EnsureCronTriggerHasIdentifierMigrationSpec.groovy: -------------------------------------------------------------------------------- 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.front50.migrations 18 | 19 | import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline 20 | import com.netflix.spinnaker.front50.api.model.pipeline.Trigger; 21 | import com.netflix.spinnaker.front50.model.pipeline.PipelineDAO 22 | import spock.lang.Specification 23 | import spock.lang.Subject 24 | import spock.lang.Unroll 25 | 26 | class EnsureCronTriggerHasIdentifierMigrationSpec extends Specification { 27 | def pipelineDAO = Mock(PipelineDAO) 28 | 29 | @Subject 30 | def migration = new EnsureCronTriggerHasIdentifierMigration(pipelineDAO) 31 | 32 | @Unroll 33 | def "should set cron trigger identifier"() { 34 | given: 35 | def pipeline = new Pipeline([ 36 | application: "test", 37 | triggers : [ 38 | new Trigger([type: "cron", id: "original-id", expression: "1"]), 39 | new Trigger([type: "cron", expression: "2"]), 40 | new Trigger([type: "cron", id: "", expression: "3"]) 41 | ] 42 | ]) 43 | 44 | pipeline.id = "pipeline-1" 45 | 46 | when: 47 | migration.run() 48 | migration.run() // subsequent migration run should be a no-op 49 | 50 | then: 51 | pipeline.getTriggers().find { it.expression == "1" }.id == "original-id" 52 | pipeline.getTriggers().find { it.expression == "2" }.id.length() > 1 53 | pipeline.getTriggers().find { it.expression == "3" }.id.length() > 1 54 | 55 | 2 * pipelineDAO.all() >> { return [pipeline] } 56 | 1 * pipelineDAO.update("pipeline-1", _) 57 | 0 * _ 58 | 59 | where: 60 | additionalPipelineContext || _ 61 | [:] || _ 62 | [parallel: false] || _ 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /front50-core/src/main/java/com/netflix/spinnaker/front50/model/serviceaccount/DefaultServiceAccountDAO.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.front50.model.serviceaccount; 18 | 19 | import com.netflix.spectator.api.Registry; 20 | import com.netflix.spinnaker.front50.config.StorageServiceConfigurationProperties; 21 | import com.netflix.spinnaker.front50.model.ObjectKeyLoader; 22 | import com.netflix.spinnaker.front50.model.ObjectType; 23 | import com.netflix.spinnaker.front50.model.StorageService; 24 | import com.netflix.spinnaker.front50.model.StorageServiceSupport; 25 | import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; 26 | import rx.Scheduler; 27 | 28 | public class DefaultServiceAccountDAO extends StorageServiceSupport 29 | implements ServiceAccountDAO { 30 | public DefaultServiceAccountDAO( 31 | StorageService service, 32 | Scheduler scheduler, 33 | ObjectKeyLoader objectKeyLoader, 34 | StorageServiceConfigurationProperties.PerObjectType configurationProperties, 35 | Registry registry, 36 | CircuitBreakerRegistry circuitBreakerRegistry) { 37 | super( 38 | ObjectType.SERVICE_ACCOUNT, 39 | service, 40 | scheduler, 41 | objectKeyLoader, 42 | configurationProperties, 43 | registry, 44 | circuitBreakerRegistry); 45 | } 46 | 47 | @Override 48 | public ServiceAccount create(String id, ServiceAccount permission) { 49 | return upsert(id, permission); 50 | } 51 | 52 | @Override 53 | public void update(String id, ServiceAccount permission) { 54 | upsert(id, permission); 55 | } 56 | 57 | private ServiceAccount upsert(String id, ServiceAccount permission) { 58 | permission.setName(id); 59 | super.update(id, permission); 60 | return findById(id); 61 | } 62 | } 63 | --------------------------------------------------------------------------------