diff --git a/Dockerfile.native b/Dockerfile.native new file mode 100644 index 0000000..4f613ff --- /dev/null +++ b/Dockerfile.native @@ -0,0 +1,27 @@ +FROM ghcr.io/graalvm/graalvm-community:22 AS build + +ARG MAVEN_VERSION=3.9.9 +ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries + +RUN mkdir -p /usr/share/maven /usr/share/maven/ref +ADD ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz /tmp/apache-maven.tar.gz +RUN tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \ + && rm -f /tmp/apache-maven.tar.gz \ + && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn + +WORKDIR /usr/src/app + +COPY pom.xml . +RUN mvn dependency:go-offline + +COPY . . + +RUN mvn -DskipTests -Pprod -Pnative native:compile + +FROM debian:bookworm-slim + +WORKDIR /app + +COPY --from=build /usr/src/app/target/switcher-ac /app/switcher-ac + +CMD ["/app/switcher-ac"] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9ccd293..626cd19 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.5 + 3.4.0 @@ -59,14 +59,15 @@ ${java.version} 4.0.0-M1 - 2.2.0 + 2.2.1 0.12.6 2.13.0 2.11.0 - 2.6.0 + 2.7.0 4.16.1 + 4.12.0 0.10.3 @@ -86,21 +87,27 @@ - - local - - true - - - local - - prod prod + + native + + + + org.graalvm.buildtools + native-maven-plugin + ${native-image-plugin.version} + + com.github.switcherapi.ac.SwitcherAcApplication + + + + + coverage @@ -268,20 +275,28 @@ - com.squareup.okhttp3 - okhttp + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test test com.squareup.okhttp3 - mockwebserver + okhttp + ${okhttp.version} test - org.springframework.security - spring-security-test + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} test @@ -297,12 +312,6 @@ ${flapdoodle.embed.mongo.version} test - - - org.springframework.boot - spring-boot-starter-test - test - @@ -317,11 +326,6 @@ - - org.graalvm.buildtools - native-maven-plugin - ${native-image-plugin.version} - org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/com/github/switcherapi/ac/SwitcherAcApplication.java b/src/main/java/com/github/switcherapi/ac/SwitcherAcApplication.java index f205a83..95b5058 100644 --- a/src/main/java/com/github/switcherapi/ac/SwitcherAcApplication.java +++ b/src/main/java/com/github/switcherapi/ac/SwitcherAcApplication.java @@ -9,8 +9,6 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.context.annotation.ComponentScan; -import static com.github.switcherapi.ac.config.SwitcherFeatures.checkSwitchers; - @SpringBootApplication @ConfigurationPropertiesScan @ComponentScan(basePackages = { "com.github.switcherapi.ac" }) @@ -29,8 +27,6 @@ public static void main(String[] args) { @Override public void run(String... args) { - checkSwitchers(); - log.info("Loading default Plan..."); planService.createPlan(Plan.loadDefault()); log.info("Plan loaded"); diff --git a/src/main/java/com/github/switcherapi/ac/config/SwitcherConfig.java b/src/main/java/com/github/switcherapi/ac/config/SwitcherConfig.java deleted file mode 100644 index f94577a..0000000 --- a/src/main/java/com/github/switcherapi/ac/config/SwitcherConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.github.switcherapi.ac.config; - -import com.github.switcherapi.ac.util.FileUtil; -import com.github.switcherapi.client.ContextBuilder; -import com.github.switcherapi.client.SnapshotCallback; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import static com.github.switcherapi.ac.config.SwitcherFeatures.configure; -import static com.github.switcherapi.ac.config.SwitcherFeatures.initializeClient; -import static com.github.switcherapi.ac.config.SwitcherFeatures.scheduleSnapshotAutoUpdate; - -@Slf4j -@ConfigurationProperties(prefix = "switcher") -public record SwitcherConfig( - String url, - String apikey, - String domain, - String component, - String environment, - boolean local, - String silent, - SnapshotConfig snapshot, - String relayCode, - TruststoreConfig truststore) implements SnapshotCallback { - - record SnapshotConfig( - String autoUpdateInterval, - String location, - boolean auto) { } - - record TruststoreConfig( - String path, - String password) { } - - @PostConstruct - private void configureSwitcher() { - configure(ContextBuilder.builder() - .contextLocation(SwitcherFeatures.class.getName()) - .url(url) - .apiKey(apikey) - .domain(domain) - .environment(environment) - .component(component) - .local(local) - .silentMode(silent) - .snapshotLocation(StringUtils.isNotBlank(snapshot.location()) ? snapshot.location() : null) - .snapshotAutoLoad(snapshot.auto()) - .truststorePath(StringUtils.isNotBlank(truststore.path()) ? FileUtil.getFilePathFromResource(truststore.path()) : null) - .truststorePassword(StringUtils.isNotBlank(truststore.password()) ? truststore.password() : null)); - - initializeClient(); - scheduleSnapshotAutoUpdate(snapshot.autoUpdateInterval(), this); - } - - @Override - public void onSnapshotUpdate(long version) { - log.info("Snapshot updated: {}", version); - } - -} diff --git a/src/main/java/com/github/switcherapi/ac/config/SwitcherFeatures.java b/src/main/java/com/github/switcherapi/ac/config/SwitcherFeatures.java index 3f5f6f3..a9b18e6 100644 --- a/src/main/java/com/github/switcherapi/ac/config/SwitcherFeatures.java +++ b/src/main/java/com/github/switcherapi/ac/config/SwitcherFeatures.java @@ -1,11 +1,39 @@ package com.github.switcherapi.ac.config; +import com.github.switcherapi.ac.util.FileUtil; +import com.github.switcherapi.client.SnapshotCallback; import com.github.switcherapi.client.SwitcherContextBase; import com.github.switcherapi.client.SwitcherKey; +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Slf4j +@Getter +@Setter +@ConfigurationProperties(prefix = "switcher") +public class SwitcherFeatures extends SwitcherContextBase implements SnapshotCallback { -public class SwitcherFeatures extends SwitcherContextBase { - @SwitcherKey public static final String SWITCHER_AC_ADM = "SWITCHER_AC_ADM"; + private String relayCode; + + @PostConstruct + @Override + protected void configureClient() { + super.truststore.setPath(FileUtil.getFilePathFromResource(truststore.getPath())); + super.configureClient(); + + scheduleSnapshotAutoUpdate(snapshot.getUpdateInterval(), this); + checkSwitchers(); + } + + @Override + public void onSnapshotUpdate(long version) { + log.info("Snapshot updated: {}", version); + } + } diff --git a/src/main/java/com/github/switcherapi/ac/controller/SwitcherRelayController.java b/src/main/java/com/github/switcherapi/ac/controller/SwitcherRelayController.java index 27b395a..408c8ee 100644 --- a/src/main/java/com/github/switcherapi/ac/controller/SwitcherRelayController.java +++ b/src/main/java/com/github/switcherapi/ac/controller/SwitcherRelayController.java @@ -1,12 +1,12 @@ package com.github.switcherapi.ac.controller; -import com.github.switcherapi.ac.config.SwitcherConfig; +import com.github.switcherapi.ac.config.SwitcherFeatures; import com.github.switcherapi.ac.model.domain.FeaturePayload; import com.github.switcherapi.ac.model.dto.RequestRelayDTO; import com.github.switcherapi.ac.model.dto.ResponseRelayDTO; import com.github.switcherapi.ac.service.AccountService; -import com.github.switcherapi.ac.service.ValidatorService; -import com.github.switcherapi.ac.service.validator.ValidatorFactory; +import com.github.switcherapi.ac.service.ValidatorBasicService; +import com.github.switcherapi.ac.service.validator.ValidatorBuilderService; import com.google.gson.Gson; import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.ResponseEntity; @@ -25,26 +25,26 @@ public class SwitcherRelayController { private final AccountService accountService; - private final ValidatorFactory validatorFactory; + private final ValidatorBuilderService validatorBuilderService; - private final ValidatorService validatorService; + private final ValidatorBasicService validatorBasicService; - private final SwitcherConfig switcherConfig; + private final SwitcherFeatures switcherConfig; public SwitcherRelayController( - AccountService accountService, - ValidatorFactory validatorFactory, - ValidatorService validatorService, - SwitcherConfig switcherConfig) { + AccountService accountService, + ValidatorBuilderService validatorBuilderService, + ValidatorBasicService validatorBasicService, + SwitcherFeatures switcherConfig) { this.accountService = accountService; - this.validatorFactory = validatorFactory; - this.validatorService = validatorService; + this.validatorBuilderService = validatorBuilderService; + this.validatorBasicService = validatorBasicService; this.switcherConfig = switcherConfig; } @GetMapping(value = "/verify") public ResponseEntity verify() { - return ResponseEntity.ok(Map.of("code", switcherConfig.relayCode())); + return ResponseEntity.ok(Map.of("code", switcherConfig.getRelayCode())); } @Operation(summary = "Load new account to Switcher AC") @@ -78,7 +78,7 @@ public ResponseEntity limiter(@RequestParam String value) { .owner(value) .build(); - return ResponseEntity.ok(validatorFactory.runValidator(request)); + return ResponseEntity.ok(validatorBuilderService.runValidator(request)); } catch (ResponseStatusException e) { return ResponseEntity.status(e.getStatusCode()).body(ResponseRelayDTO.fail(e.getMessage())); } @@ -89,7 +89,7 @@ public ResponseEntity limiter(@RequestParam String value) { public ResponseEntity validate(@RequestBody RequestRelayDTO request) { try { var featureRequest = gson.fromJson(request.payload(), FeaturePayload.class); - return ResponseEntity.ok(validatorService.execute(featureRequest)); + return ResponseEntity.ok(validatorBasicService.execute(featureRequest)); } catch (ResponseStatusException e) { return ResponseEntity.status(e.getStatusCode()).body(ResponseRelayDTO.fail(e.getMessage())); } diff --git a/src/main/java/com/github/switcherapi/ac/model/domain/Plan.java b/src/main/java/com/github/switcherapi/ac/model/domain/Plan.java index 8ba1da7..95fbff4 100644 --- a/src/main/java/com/github/switcherapi/ac/model/domain/Plan.java +++ b/src/main/java/com/github/switcherapi/ac/model/domain/Plan.java @@ -15,6 +15,7 @@ import java.util.List; import static com.github.switcherapi.ac.model.domain.Feature.*; +import static com.github.switcherapi.ac.model.domain.PlanDefaults.*; @Generated @JsonInclude(Include.NON_NULL) @@ -24,16 +25,6 @@ @AllArgsConstructor public class Plan { - public static final int DEFAULT_DOMAIN = 1; - public static final int DEFAULT_GROUP = 2; - public static final int DEFAULT_SWITCHER = 3; - public static final int DEFAULT_ENVIRONMENT = 2; - public static final int DEFAULT_COMPONENT = 2; - public static final int DEFAULT_TEAM = 1; - public static final int DEFAULT_RATE_LIMIT = 100; - public static final boolean DEFAULT_HISTORY = false; - public static final boolean DEFAULT_METRICS = false; - @Id private String id; @@ -66,15 +57,15 @@ public static Plan loadDefault() { return Plan.builder() .name(PlanType.DEFAULT.name()) .attributes(Arrays.asList( - PlanAttribute.builder().feature(DOMAIN.getValue()).value(DEFAULT_DOMAIN).build(), - PlanAttribute.builder().feature(GROUP.getValue()).value(DEFAULT_GROUP).build(), - PlanAttribute.builder().feature(SWITCHER.getValue()).value(DEFAULT_SWITCHER).build(), - PlanAttribute.builder().feature(ENVIRONMENT.getValue()).value(DEFAULT_ENVIRONMENT).build(), - PlanAttribute.builder().feature(COMPONENT.getValue()).value(DEFAULT_COMPONENT).build(), - PlanAttribute.builder().feature(TEAM.getValue()).value(DEFAULT_TEAM).build(), - PlanAttribute.builder().feature(RATE_LIMIT.getValue()).value(DEFAULT_RATE_LIMIT).build(), - PlanAttribute.builder().feature(HISTORY.getValue()).value(DEFAULT_HISTORY).build(), - PlanAttribute.builder().feature(METRICS.getValue()).value(DEFAULT_METRICS).build() + PlanAttribute.builder().feature(DOMAIN.getValue()).value(DEFAULT_DOMAIN.getIntValue()).build(), + PlanAttribute.builder().feature(GROUP.getValue()).value(DEFAULT_GROUP.getIntValue()).build(), + PlanAttribute.builder().feature(SWITCHER.getValue()).value(DEFAULT_SWITCHER.getIntValue()).build(), + PlanAttribute.builder().feature(ENVIRONMENT.getValue()).value(DEFAULT_ENVIRONMENT.getIntValue()).build(), + PlanAttribute.builder().feature(COMPONENT.getValue()).value(DEFAULT_COMPONENT.getIntValue()).build(), + PlanAttribute.builder().feature(TEAM.getValue()).value(DEFAULT_TEAM.getIntValue()).build(), + PlanAttribute.builder().feature(RATE_LIMIT.getValue()).value(DEFAULT_RATE_LIMIT.getIntValue()).build(), + PlanAttribute.builder().feature(HISTORY.getValue()).value(DEFAULT_HISTORY.getValue()).build(), + PlanAttribute.builder().feature(METRICS.getValue()).value(DEFAULT_METRICS.getValue()).build() )).build(); } diff --git a/src/main/java/com/github/switcherapi/ac/model/domain/PlanDefaults.java b/src/main/java/com/github/switcherapi/ac/model/domain/PlanDefaults.java new file mode 100644 index 0000000..1d066ce --- /dev/null +++ b/src/main/java/com/github/switcherapi/ac/model/domain/PlanDefaults.java @@ -0,0 +1,26 @@ +package com.github.switcherapi.ac.model.domain; + +import lombok.Getter; + +@Getter +public enum PlanDefaults { + DEFAULT_DOMAIN(1), + DEFAULT_GROUP(2), + DEFAULT_SWITCHER(3), + DEFAULT_ENVIRONMENT(2), + DEFAULT_COMPONENT(2), + DEFAULT_TEAM(1), + DEFAULT_RATE_LIMIT(100), + DEFAULT_HISTORY(false), + DEFAULT_METRICS(false); + + private final Object value; + + PlanDefaults(Object value) { + this.value = value; + } + + public Integer getIntValue() { + return (Integer) value; + } +} diff --git a/src/main/java/com/github/switcherapi/ac/model/domain/PlanType.java b/src/main/java/com/github/switcherapi/ac/model/domain/PlanType.java index 29042e0..194373a 100644 --- a/src/main/java/com/github/switcherapi/ac/model/domain/PlanType.java +++ b/src/main/java/com/github/switcherapi/ac/model/domain/PlanType.java @@ -1,8 +1,5 @@ package com.github.switcherapi.ac.model.domain; -import lombok.Generated; - -@Generated public enum PlanType { DEFAULT } diff --git a/src/main/java/com/github/switcherapi/ac/service/ValidatorService.java b/src/main/java/com/github/switcherapi/ac/service/ValidatorBasicService.java similarity index 95% rename from src/main/java/com/github/switcherapi/ac/service/ValidatorService.java rename to src/main/java/com/github/switcherapi/ac/service/ValidatorBasicService.java index b6c5cb0..fee9d81 100644 --- a/src/main/java/com/github/switcherapi/ac/service/ValidatorService.java +++ b/src/main/java/com/github/switcherapi/ac/service/ValidatorBasicService.java @@ -16,9 +16,9 @@ import static com.github.switcherapi.ac.util.Constants.*; @Service -public class ValidatorService extends AbstractValidatorService { +public class ValidatorBasicService extends AbstractValidatorService { - protected ValidatorService(AccountDao accountDao) { + protected ValidatorBasicService(AccountDao accountDao) { super(accountDao); } diff --git a/src/main/java/com/github/switcherapi/ac/service/facades/GitHubFacade.java b/src/main/java/com/github/switcherapi/ac/service/facades/GitHubFacade.java index ab553d5..67fdba4 100644 --- a/src/main/java/com/github/switcherapi/ac/service/facades/GitHubFacade.java +++ b/src/main/java/com/github/switcherapi/ac/service/facades/GitHubFacade.java @@ -1,7 +1,6 @@ package com.github.switcherapi.ac.service.facades; import com.github.switcherapi.ac.model.GitHubDetail; -import com.github.switcherapi.ac.util.Sanitizer; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; diff --git a/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorBuilderService.java b/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorBuilderService.java new file mode 100644 index 0000000..5170dd5 --- /dev/null +++ b/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorBuilderService.java @@ -0,0 +1,33 @@ +package com.github.switcherapi.ac.service.validator; + +import com.github.switcherapi.ac.model.domain.FeaturePayload; +import com.github.switcherapi.ac.model.dto.ResponseRelayDTO; +import com.github.switcherapi.ac.repository.AccountDao; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +import java.util.HashMap; +import java.util.Map; + +public abstract class ValidatorBuilderService { + + protected final Map validatorHandlers = new HashMap<>(); + + protected final AccountDao accountDao; + + protected ValidatorBuilderService(AccountDao accountDao) { + this.accountDao = accountDao; + } + + public ResponseRelayDTO runValidator(FeaturePayload request) { + if (!validatorHandlers.containsKey(request.feature())) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, String.format("Invalid validator: %s", request.feature())); + } + + return validatorHandlers.get(request.feature()).execute(request); + } + + protected abstract void initializeValidators(); + +} diff --git a/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorNativeBuilder.java b/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorNativeBuilder.java new file mode 100644 index 0000000..2dac634 --- /dev/null +++ b/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorNativeBuilder.java @@ -0,0 +1,26 @@ +package com.github.switcherapi.ac.service.validator; + +import com.github.switcherapi.ac.repository.AccountDao; +import com.github.switcherapi.ac.service.validator.beans.ValidateRateLimit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import static com.github.switcherapi.ac.service.validator.beans.ValidateRateLimit.RATE_LIMIT_VALIDATOR; + +@Slf4j +@Component +@ConditionalOnProperty(value = "service.validators.native", havingValue = "true") +public class ValidatorNativeBuilder extends ValidatorBuilderService { + + public ValidatorNativeBuilder(AccountDao accountDao) { + super(accountDao); + this.initializeValidators(); + } + + @Override + protected void initializeValidators() { + validatorHandlers.put(RATE_LIMIT_VALIDATOR, new ValidateRateLimit(accountDao)); + } + +} diff --git a/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorFactory.java b/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorRuntimeBuilder.java similarity index 66% rename from src/main/java/com/github/switcherapi/ac/service/validator/ValidatorFactory.java rename to src/main/java/com/github/switcherapi/ac/service/validator/ValidatorRuntimeBuilder.java index 7241b03..50d168e 100644 --- a/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorFactory.java +++ b/src/main/java/com/github/switcherapi/ac/service/validator/ValidatorRuntimeBuilder.java @@ -1,40 +1,33 @@ package com.github.switcherapi.ac.service.validator; -import com.github.switcherapi.ac.model.domain.FeaturePayload; -import com.github.switcherapi.ac.model.dto.ResponseRelayDTO; import com.github.switcherapi.ac.repository.AccountDao; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; -import org.springframework.web.server.ResponseStatusException; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; -@Component @Slf4j -public class ValidatorFactory { +@Component +@ConditionalOnProperty(value = "service.validators.native", havingValue = "false") +public class ValidatorRuntimeBuilder extends ValidatorBuilderService { private static final String VALIDATORS_PATH = "com.github.switcherapi.ac.service.validator.beans"; private final AutowireCapableBeanFactory autowireCapableBeanFactory; - private final AccountDao accountDao; - - private final Map validatorHandlers = new HashMap<>(); - - public ValidatorFactory(AutowireCapableBeanFactory autowireCapableBeanFactory, AccountDao accountDao) { + public ValidatorRuntimeBuilder(AutowireCapableBeanFactory autowireCapableBeanFactory, AccountDao accountDao) { + super(accountDao); this.autowireCapableBeanFactory = autowireCapableBeanFactory; - this.accountDao = accountDao; - this.scanValidators(); + this.initializeValidators(); } - private void scanValidators() { + @Override + protected void initializeValidators() { final var provider = new ClassPathScanningCandidateComponentProvider(true); provider.addIncludeFilter(new AnnotationTypeFilter(SwitcherValidator.class, false, true)); var beans = provider.findCandidateComponents(VALIDATORS_PATH); @@ -61,15 +54,5 @@ private void cacheValidator(String controllerClassName) { log.error("Failed to initialize validator - {}", e.getMessage()); } } - - public ResponseRelayDTO runValidator(FeaturePayload request) { - if (!validatorHandlers.containsKey(request.feature())) { - throw new ResponseStatusException( - HttpStatus.BAD_REQUEST, String.format("Invalid validator: %s", request.feature())); - } - - final var validatorService = validatorHandlers.get(request.feature()); - return validatorService.execute(request); - } } diff --git a/src/main/java/com/github/switcherapi/ac/service/validator/beans/ValidateRateLimit.java b/src/main/java/com/github/switcherapi/ac/service/validator/beans/ValidateRateLimit.java index de02fe9..0e62919 100644 --- a/src/main/java/com/github/switcherapi/ac/service/validator/beans/ValidateRateLimit.java +++ b/src/main/java/com/github/switcherapi/ac/service/validator/beans/ValidateRateLimit.java @@ -1,16 +1,17 @@ package com.github.switcherapi.ac.service.validator.beans; import com.github.switcherapi.ac.model.domain.Account; +import com.github.switcherapi.ac.model.domain.Feature; import com.github.switcherapi.ac.model.dto.Metadata; import com.github.switcherapi.ac.model.dto.ResponseRelayDTO; import com.github.switcherapi.ac.repository.AccountDao; import com.github.switcherapi.ac.service.validator.SwitcherValidator; -import static com.github.switcherapi.ac.model.domain.Feature.RATE_LIMIT; - -@SwitcherValidator("rate_limit") +@SwitcherValidator(ValidateRateLimit.RATE_LIMIT_VALIDATOR) public class ValidateRateLimit extends AbstractActiveCheckValidator { + public static final String RATE_LIMIT_VALIDATOR = "rate_limit"; + public ValidateRateLimit(AccountDao accountDao) { super(accountDao); } @@ -18,7 +19,7 @@ public ValidateRateLimit(AccountDao accountDao) { @Override protected ResponseRelayDTO executeValidator(final Account account) { final var plan = account.getPlan(); - final var max = Integer.parseInt(plan.getFeature(RATE_LIMIT).getValue().toString()); + final var max = Integer.parseInt(plan.getFeature(Feature.RATE_LIMIT).getValue().toString()); return ResponseRelayDTO.success(Metadata.builder().rateLimit(max).build()); } diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..fc5ca62 --- /dev/null +++ b/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,14 @@ +[ + { + "name": "com.github.switcherapi.ac.model.domain.FeaturePayload", + "allPublicConstructors": true, + "allDeclaredFields": true, + "allPublicMethods": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } +] \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index cef8fd7..ae9c963 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -2,8 +2,8 @@ server.port=${PORT:8070} server.error.include-message=always spring.data.mongodb.uri=${MONGODB_URL} -spring.security.user.name=${ADMIN_USER} -spring.security.user.password=${ADMIN_PASS} +spring.security.user.name=${ADMIN_USER:admin} +spring.security.user.password=${ADMIN_PASS:admin} # API settings service.api.secret=${API_SECRET} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e3e746f..7acaa39 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,6 +2,7 @@ spring.profiles.active=@spring.profiles.active@ # Logging logging.level.com.github.switcherapi.ac=INFO +logging.level.com.github.switcherapi.client=INFO # Server settings spring.application.name=switcher-ac @@ -12,7 +13,7 @@ server.tomcat.relaxed-query-chars=\\ server.ssl.enabled=${SSL_ENABLED:false} server.ssl.key-store-type=JKS server.ssl.key-store=${SSL_KEY_STORE:classpath:keystore/keystore.jks} -server.ssl.key-store-password=${SSL_KEY_PASSWORD:change} +server.ssl.key-store-password=${SSL_KEY_PASSWORD:changeit} server.ssl.key-alias=${SSL_KEY_ALIAS:switcherapi} # API docs @@ -30,21 +31,25 @@ service.docs.contact.email=switcher.project@gmail.com service.github.url.access=${GITHUB_URL_ACCESS:https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s} service.github.url.detail=${GITHUB_URL_DETAIL:https://api.github.com/user} +# Validators settings +service.validators.native=${VALIDATORS_NATIVE:true} + # Actuator settings management.endpoints.web.exposure.include=* management.endpoint.health.group.custom.show-components=always management.endpoint.health.group.custom.show-details=always # Switcher API settings +switcher.relay_code=${SWITCHER_RELAY_CODE:[relay_code]} +switcher.contextLocation=com.github.switcherapi.ac.config.SwitcherFeatures switcher.url=${SWITCHER_URL} switcher.apikey=${SWITCHER_KEY} switcher.domain=Switcher API switcher.component=switcher-ac -switcher.snapshot.autoUpdateInterval=${SWITCHER_SNAPSHOT_AUTO_UPDATE_INTERVAL:1m} +switcher.snapshot.updateInterval=${SWITCHER_SNAPSHOT_AUTO_UPDATE_INTERVAL:1m} switcher.snapshot.location=${SWITCHER_SNAPSHOT_LOCATION:} switcher.snapshot.auto=${SWITCHER_SNAPSHOT_AUTO:true} switcher.local=${SWITCHER_LOCAL:true} switcher.silent=${SWITCHER_SILENT:5m} -switcher.relay_code=${SWITCHER_RELAY_CODE:[relay_code]} switcher.truststore.path=${SWITCHER_TRUSTSTORE:} switcher.truststore.password=${SWITCHER_TRUSTSTORE_PASSWORD:} \ No newline at end of file diff --git a/src/test/java/com/github/switcherapi/ac/controller/SwitcherRelayControllerErrorTests.java b/src/test/java/com/github/switcherapi/ac/controller/SwitcherRelayControllerErrorTests.java index e471a0f..d14bc27 100644 --- a/src/test/java/com/github/switcherapi/ac/controller/SwitcherRelayControllerErrorTests.java +++ b/src/test/java/com/github/switcherapi/ac/controller/SwitcherRelayControllerErrorTests.java @@ -1,10 +1,10 @@ package com.github.switcherapi.ac.controller; -import com.github.switcherapi.ac.config.SwitcherConfig; +import com.github.switcherapi.ac.config.SwitcherFeatures; import com.github.switcherapi.ac.model.dto.RequestRelayDTO; import com.github.switcherapi.ac.service.AccountService; -import com.github.switcherapi.ac.service.ValidatorService; -import com.github.switcherapi.ac.service.validator.ValidatorFactory; +import com.github.switcherapi.ac.service.ValidatorBasicService; +import com.github.switcherapi.ac.service.validator.ValidatorBuilderService; import com.google.gson.Gson; import jakarta.ws.rs.core.HttpHeaders; import org.junit.jupiter.api.BeforeEach; @@ -31,9 +31,9 @@ class SwitcherRelayControllerErrorTests { @Mock private AccountService mockAccountService; - @Mock private ValidatorFactory mockValidatorFactory; - @Mock private ValidatorService mockValidatorService; - @Mock private SwitcherConfig mockSwitcherConfig; + @Mock private ValidatorBuilderService mockValidatorBuilderService; + @Mock private ValidatorBasicService mockValidatorBasicService; + @Mock private SwitcherFeatures mockSwitcherConfig; private MockMvc mockMvc; @@ -41,7 +41,7 @@ class SwitcherRelayControllerErrorTests { void setup() { MockitoAnnotations.openMocks(this); final var switcherRelayController = new SwitcherRelayController( - mockAccountService, mockValidatorFactory, mockValidatorService, mockSwitcherConfig); + mockAccountService, mockValidatorBuilderService, mockValidatorBasicService, mockSwitcherConfig); mockMvc = MockMvcBuilders.standaloneSetup(switcherRelayController).build(); } diff --git a/src/test/java/com/github/switcherapi/ac/service/ValidatorServiceTest.java b/src/test/java/com/github/switcherapi/ac/service/ValidatorBasicServiceTest.java similarity index 75% rename from src/test/java/com/github/switcherapi/ac/service/ValidatorServiceTest.java rename to src/test/java/com/github/switcherapi/ac/service/ValidatorBasicServiceTest.java index b22d9f0..da7e63b 100644 --- a/src/test/java/com/github/switcherapi/ac/service/ValidatorServiceTest.java +++ b/src/test/java/com/github/switcherapi/ac/service/ValidatorBasicServiceTest.java @@ -16,26 +16,27 @@ import java.util.stream.Stream; import static com.github.switcherapi.ac.model.domain.Feature.*; -import static com.github.switcherapi.ac.model.domain.Plan.*; +import static com.github.switcherapi.ac.model.domain.PlanDefaults.*; import static com.github.switcherapi.ac.util.Constants.*; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest -class ValidatorServiceTest { +class ValidatorBasicServiceTest { - @Autowired ValidatorService validatorService; + @Autowired + ValidatorBasicService validatorBasicService; @Autowired AccountService accountService; @Autowired PlanService planService; static Stream validatorInput() { return Stream.of( - Arguments.of(DOMAIN, DEFAULT_DOMAIN - 1), - Arguments.of(GROUP, DEFAULT_GROUP - 1), - Arguments.of(SWITCHER, DEFAULT_SWITCHER - 1), - Arguments.of(ENVIRONMENT, DEFAULT_ENVIRONMENT - 1), - Arguments.of(COMPONENT, DEFAULT_COMPONENT - 1), - Arguments.of(TEAM, DEFAULT_TEAM - 1), - Arguments.of(RATE_LIMIT, DEFAULT_RATE_LIMIT - 1) + Arguments.of(DOMAIN, DEFAULT_DOMAIN.getIntValue() - 1), + Arguments.of(GROUP, DEFAULT_GROUP.getIntValue() - 1), + Arguments.of(SWITCHER, DEFAULT_SWITCHER.getIntValue() - 1), + Arguments.of(ENVIRONMENT, DEFAULT_ENVIRONMENT.getIntValue() - 1), + Arguments.of(COMPONENT, DEFAULT_COMPONENT.getIntValue() - 1), + Arguments.of(TEAM, DEFAULT_TEAM.getIntValue() - 1), + Arguments.of(RATE_LIMIT, DEFAULT_RATE_LIMIT.getIntValue() - 1) ); } @@ -47,21 +48,21 @@ void shouldValidateFromRequest(Feature feature, Integer total) { var request = givenRequest(feature.getValue(), "adminid_validate", total); //test - var responseRelayDTO = validatorService.execute(request); + var responseRelayDTO = validatorBasicService.execute(request); assertTrue(responseRelayDTO.result()); } static Stream validatorLimitInput() { return Stream.of( - Arguments.of(DOMAIN, DEFAULT_DOMAIN), - Arguments.of(GROUP, DEFAULT_GROUP), - Arguments.of(SWITCHER, DEFAULT_SWITCHER), - Arguments.of(ENVIRONMENT, DEFAULT_ENVIRONMENT), - Arguments.of(COMPONENT, DEFAULT_COMPONENT), - Arguments.of(TEAM, DEFAULT_TEAM), + Arguments.of(DOMAIN, DEFAULT_DOMAIN.getIntValue()), + Arguments.of(GROUP, DEFAULT_GROUP.getIntValue()), + Arguments.of(SWITCHER, DEFAULT_SWITCHER.getIntValue()), + Arguments.of(ENVIRONMENT, DEFAULT_ENVIRONMENT.getIntValue()), + Arguments.of(COMPONENT, DEFAULT_COMPONENT.getIntValue()), + Arguments.of(TEAM, DEFAULT_TEAM.getIntValue()), Arguments.of(HISTORY, null), Arguments.of(METRICS, null), - Arguments.of(RATE_LIMIT, DEFAULT_RATE_LIMIT) + Arguments.of(RATE_LIMIT, DEFAULT_RATE_LIMIT.getIntValue()) ); } @@ -73,7 +74,7 @@ void shouldNotValidateFromRequest(Feature feature, Integer total) { var request = givenRequest(feature.getValue(), "adminid_not_validate", total); //test - var responseRelayDTO = validatorService.execute(request); + var responseRelayDTO = validatorBasicService.execute(request); assertFalse(responseRelayDTO.result()); assertEquals(MSG_FEATURE_LIMIT_REACHED.getValue(), responseRelayDTO.message()); } @@ -86,7 +87,7 @@ void shouldNotValidateFromRequest_invalidFeature() { //test final var exception = - assertThrows(ResponseStatusException.class, () -> validatorService.execute(request)); + assertThrows(ResponseStatusException.class, () -> validatorBasicService.execute(request)); assertEquals(MSG_INVALID_FEATURE.getValue() + ": invalid_feature", exception.getReason()); } @@ -98,7 +99,7 @@ void shouldNotValidateFromRequest_emptyFeature() { //test final var exception = - assertThrows(ResponseStatusException.class, () -> validatorService.execute(request)); + assertThrows(ResponseStatusException.class, () -> validatorBasicService.execute(request)); assertEquals(MSG_FEATURE_MISSING.getValue(), exception.getReason()); } @@ -110,7 +111,7 @@ void shouldValidateFromRequest_undeterminedValue() { var request = givenRequest(DOMAIN.getValue(), "adminid_undetermined", 999); //test - var responseRelayDTO = validatorService.execute(request); + var responseRelayDTO = validatorBasicService.execute(request); assertTrue(responseRelayDTO.result()); } @@ -123,7 +124,7 @@ void shouldNotValidateFromRequest_invalidPlanValueType() { //test final var exception = - assertThrows(ResponseStatusException.class, () -> validatorService.execute(request)); + assertThrows(ResponseStatusException.class, () -> validatorBasicService.execute(request)); assertEquals(MSG_PLAN_INVALID_VALUE.getValue(), exception.getReason()); } diff --git a/src/test/java/com/github/switcherapi/ac/service/ValidatorFactoryTest.java b/src/test/java/com/github/switcherapi/ac/service/ValidatorNativeBuilderServiceTest.java similarity index 66% rename from src/test/java/com/github/switcherapi/ac/service/ValidatorFactoryTest.java rename to src/test/java/com/github/switcherapi/ac/service/ValidatorNativeBuilderServiceTest.java index 308278e..7776d3b 100644 --- a/src/test/java/com/github/switcherapi/ac/service/ValidatorFactoryTest.java +++ b/src/test/java/com/github/switcherapi/ac/service/ValidatorNativeBuilderServiceTest.java @@ -1,7 +1,7 @@ package com.github.switcherapi.ac.service; import com.github.switcherapi.ac.model.domain.FeaturePayload; -import com.github.switcherapi.ac.service.validator.ValidatorFactory; +import com.github.switcherapi.ac.service.validator.ValidatorBuilderService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -11,16 +11,17 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; -@SpringBootTest -class ValidatorFactoryTest { +@SpringBootTest(properties = {"service.validators.native=true"}) +class ValidatorNativeBuilderServiceTest { - @Autowired ValidatorFactory validatorFactory; + @Autowired + ValidatorBuilderService validatorBuilderService; @Autowired AccountService accountService; @Test void shouldThrowError_requestIsEmpty() { var request = FeaturePayload.builder().build(); - assertThrows(ResponseStatusException.class, () -> validatorFactory.runValidator(request)); + assertThrows(ResponseStatusException.class, () -> validatorBuilderService.runValidator(request)); } @Test @@ -29,7 +30,7 @@ void shouldThrowError_missingAdminId() { .feature(RATE_LIMIT.getValue()) .build(); - assertThrows(ResponseStatusException.class, () -> validatorFactory.runValidator(request)); + assertThrows(ResponseStatusException.class, () -> validatorBuilderService.runValidator(request)); } @Test @@ -41,7 +42,7 @@ void shouldNotThrowError() { .owner("adminid") .build(); - assertDoesNotThrow(() -> validatorFactory.runValidator(request)); + assertDoesNotThrow(() -> validatorBuilderService.runValidator(request)); } } diff --git a/src/test/java/com/github/switcherapi/ac/service/ValidatorRuntimeBuilderServiceTest.java b/src/test/java/com/github/switcherapi/ac/service/ValidatorRuntimeBuilderServiceTest.java new file mode 100644 index 0000000..ed74f5d --- /dev/null +++ b/src/test/java/com/github/switcherapi/ac/service/ValidatorRuntimeBuilderServiceTest.java @@ -0,0 +1,48 @@ +package com.github.switcherapi.ac.service; + +import com.github.switcherapi.ac.model.domain.FeaturePayload; +import com.github.switcherapi.ac.service.validator.ValidatorBuilderService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.web.server.ResponseStatusException; + +import static com.github.switcherapi.ac.model.domain.Feature.RATE_LIMIT; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(properties = {"service.validators.native=false"}) +class ValidatorRuntimeBuilderServiceTest { + + @Autowired + ValidatorBuilderService validatorBuilderService; + @Autowired AccountService accountService; + + @Test + void shouldThrowError_requestIsEmpty() { + var request = FeaturePayload.builder().build(); + assertThrows(ResponseStatusException.class, () -> validatorBuilderService.runValidator(request)); + } + + @Test + void shouldThrowError_missingAdminId() { + var request = FeaturePayload.builder() + .feature(RATE_LIMIT.getValue()) + .build(); + + assertThrows(ResponseStatusException.class, () -> validatorBuilderService.runValidator(request)); + } + + @Test + void shouldNotThrowError() { + accountService.createAccount("adminid"); + + var request = FeaturePayload.builder() + .feature(RATE_LIMIT.getValue()) + .owner("adminid") + .build(); + + assertDoesNotThrow(() -> validatorBuilderService.runValidator(request)); + } + +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 8768fda..68b51da 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -21,7 +21,11 @@ service.docs.contact.email=switcher.project@gmail.com service.github.url.access=https://localhost:8080/api/v1/github/access service.github.url.detail=https://localhost:8080/api/v1/github/detail +# Validators settings +service.validators.native=${VALIDATORS_NATIVE:true} + # Switcher API settings +switcher.relay_code=${SWITCHER_RELAY_CODE:[relay_code]} switcher.url=https://api.switcherapi.com switcher.apikey=${SWITCHER_KEY} switcher.component=switcher-ac @@ -29,6 +33,5 @@ switcher.environment=test switcher.domain=Switcher API switcher.local=${SWITCHER_LOCAL:true} switcher.snapshot.location=src/test/resources/snapshots -switcher.relay_code=${SWITCHER_RELAY_CODE:[relay_code]} switcher.truststore.path=${SWITCHER_TRUSTSTORE:} switcher.truststore.password=${SWITCHER_TRUSTSTORE_PASSWORD:} \ No newline at end of file