Browse Source

BE: Opt out of version check (#3570)

* Build & commit info added to /api/info endpoint
Ilya Kuramshin 2 years ago
parent
commit
5c357f94fd

+ 3 - 10
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/ApplicationConfigController.java

@@ -13,12 +13,12 @@ import com.provectus.kafka.ui.model.ClusterConfigValidationDTO;
 import com.provectus.kafka.ui.model.RestartRequestDTO;
 import com.provectus.kafka.ui.model.UploadedFileInfoDTO;
 import com.provectus.kafka.ui.model.rbac.AccessContext;
+import com.provectus.kafka.ui.service.ApplicationInfoService;
 import com.provectus.kafka.ui.service.KafkaClusterFactory;
 import com.provectus.kafka.ui.service.rbac.AccessControlService;
 import com.provectus.kafka.ui.util.ApplicationRestarter;
 import com.provectus.kafka.ui.util.DynamicConfigOperations;
 import com.provectus.kafka.ui.util.DynamicConfigOperations.PropertiesStructure;
-import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
 import lombok.RequiredArgsConstructor;
@@ -53,18 +53,11 @@ public class ApplicationConfigController implements ApplicationConfigApi {
   private final DynamicConfigOperations dynamicConfigOperations;
   private final ApplicationRestarter restarter;
   private final KafkaClusterFactory kafkaClusterFactory;
-
+  private final ApplicationInfoService applicationInfoService;
 
   @Override
   public Mono<ResponseEntity<ApplicationInfoDTO>> getApplicationInfo(ServerWebExchange exchange) {
-    return Mono.just(
-        new ApplicationInfoDTO()
-            .enabledFeatures(
-                dynamicConfigOperations.dynamicConfigEnabled()
-                    ? List.of(ApplicationInfoDTO.EnabledFeaturesEnum.DYNAMIC_CONFIG)
-                    : List.of()
-            )
-    ).map(ResponseEntity::ok);
+    return Mono.just(applicationInfoService.getApplicationInfo()).map(ResponseEntity::ok);
   }
 
   @Override

+ 76 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ApplicationInfoService.java

@@ -0,0 +1,76 @@
+package com.provectus.kafka.ui.service;
+
+import static com.provectus.kafka.ui.model.ApplicationInfoDTO.EnabledFeaturesEnum;
+
+import com.provectus.kafka.ui.model.ApplicationInfoBuildDTO;
+import com.provectus.kafka.ui.model.ApplicationInfoDTO;
+import com.provectus.kafka.ui.model.ApplicationInfoLatestReleaseDTO;
+import com.provectus.kafka.ui.util.DynamicConfigOperations;
+import com.provectus.kafka.ui.util.GithubReleaseInfo;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.boot.info.GitProperties;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ApplicationInfoService {
+
+  private final GithubReleaseInfo githubReleaseInfo = new GithubReleaseInfo();
+
+  private final DynamicConfigOperations dynamicConfigOperations;
+  private final BuildProperties buildProperties;
+  private final GitProperties gitProperties;
+
+  public ApplicationInfoService(DynamicConfigOperations dynamicConfigOperations,
+                                @Autowired(required = false) BuildProperties buildProperties,
+                                @Autowired(required = false) GitProperties gitProperties) {
+    this.dynamicConfigOperations = dynamicConfigOperations;
+    this.buildProperties = Optional.ofNullable(buildProperties).orElse(new BuildProperties(new Properties()));
+    this.gitProperties = Optional.ofNullable(gitProperties).orElse(new GitProperties(new Properties()));
+  }
+
+  public ApplicationInfoDTO getApplicationInfo() {
+    var releaseInfo = githubReleaseInfo.get();
+    return new ApplicationInfoDTO()
+        .build(getBuildInfo(releaseInfo))
+        .enabledFeatures(getEnabledFeatures())
+        .latestRelease(convert(releaseInfo));
+  }
+
+  private ApplicationInfoLatestReleaseDTO convert(GithubReleaseInfo.GithubReleaseDto releaseInfo) {
+    return new ApplicationInfoLatestReleaseDTO()
+        .htmlUrl(releaseInfo.html_url())
+        .publishedAt(releaseInfo.published_at())
+        .versionTag(releaseInfo.tag_name());
+  }
+
+  private ApplicationInfoBuildDTO getBuildInfo(GithubReleaseInfo.GithubReleaseDto release) {
+    return new ApplicationInfoBuildDTO()
+        .isLatestRelease(release.tag_name() != null && release.tag_name().equals(buildProperties.getVersion()))
+        .commitId(gitProperties.getShortCommitId())
+        .version(buildProperties.getVersion())
+        .buildTime(buildProperties.getTime() != null
+            ? DateTimeFormatter.ISO_INSTANT.format(buildProperties.getTime()) : null);
+  }
+
+  private List<EnabledFeaturesEnum> getEnabledFeatures() {
+    var enabledFeatures = new ArrayList<EnabledFeaturesEnum>();
+    if (dynamicConfigOperations.dynamicConfigEnabled()) {
+      enabledFeatures.add(EnabledFeaturesEnum.DYNAMIC_CONFIG);
+    }
+    return enabledFeatures;
+  }
+
+  // updating on startup and every hour
+  @Scheduled(fixedRateString = "${github-release-info-update-rate:3600000}")
+  public void updateGithubReleaseInfo() {
+    githubReleaseInfo.refresh().block();
+  }
+
+}

+ 53 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/GithubReleaseInfo.java

@@ -0,0 +1,53 @@
+package com.provectus.kafka.ui.util;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.time.Duration;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+public class GithubReleaseInfo {
+
+  private static final String GITHUB_LATEST_RELEASE_RETRIEVAL_URL =
+      "https://api.github.com/repos/provectus/kafka-ui/releases/latest";
+
+  private static final Duration GITHUB_API_MAX_WAIT_TIME = Duration.ofSeconds(2);
+
+  public record GithubReleaseDto(String html_url, String tag_name, String published_at) {
+
+    static GithubReleaseDto empty() {
+      return new GithubReleaseDto(null, null, null);
+    }
+  }
+
+  private volatile GithubReleaseDto release = GithubReleaseDto.empty();
+
+  private final Mono<Void> refreshMono;
+
+  public GithubReleaseInfo() {
+    this(GITHUB_LATEST_RELEASE_RETRIEVAL_URL);
+  }
+
+  @VisibleForTesting
+  GithubReleaseInfo(String url) {
+    this.refreshMono = WebClient.create()
+        .get()
+        .uri(url)
+        .exchangeToMono(resp -> resp.bodyToMono(GithubReleaseDto.class))
+        .timeout(GITHUB_API_MAX_WAIT_TIME)
+        .doOnError(th -> log.trace("Error getting latest github release info", th))
+        .onErrorResume(th -> true, th -> Mono.just(GithubReleaseDto.empty()))
+        .doOnNext(release -> this.release = release)
+        .then();
+  }
+
+  public GithubReleaseDto get() {
+    return release;
+  }
+
+  public Mono<Void> refresh() {
+    return refreshMono;
+  }
+
+}

+ 54 - 0
kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/GithubReleaseInfoTest.java

@@ -0,0 +1,54 @@
+package com.provectus.kafka.ui.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.time.Duration;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import reactor.test.StepVerifier;
+
+class GithubReleaseInfoTest {
+
+  private final MockWebServer mockWebServer = new MockWebServer();
+
+  @BeforeEach
+  void startMockServer() throws IOException {
+    mockWebServer.start();
+  }
+
+  @AfterEach
+  void stopMockServer() throws IOException {
+    mockWebServer.close();
+  }
+
+  @Test
+  void test() {
+    mockWebServer.enqueue(new MockResponse()
+        .addHeader("content-type: application/json")
+        .setBody("""
+            {
+              "published_at": "2023-03-09T16:11:31Z",
+              "tag_name": "v0.6.0",
+              "html_url": "https://github.com/provectus/kafka-ui/releases/tag/v0.6.0",
+              "some_unused_prop": "ololo"
+            }
+            """));
+    var url = mockWebServer.url("repos/provectus/kafka-ui/releases/latest").toString();
+
+    var infoHolder = new GithubReleaseInfo(url);
+    infoHolder.refresh().block();
+
+    var i = infoHolder.get();
+    assertThat(i.html_url())
+        .isEqualTo("https://github.com/provectus/kafka-ui/releases/tag/v0.6.0");
+    assertThat(i.published_at())
+        .isEqualTo("2023-03-09T16:11:31Z");
+    assertThat(i.tag_name())
+        .isEqualTo("v0.6.0");
+  }
+
+}

+ 20 - 0
kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml

@@ -1917,6 +1917,26 @@ components:
             type: string
             enum:
               - DYNAMIC_CONFIG
+        build:
+          type: object
+          properties:
+            commitId:
+              type: string
+            version:
+              type: string
+            buildTime:
+              type: string
+            isLatestRelease:
+              type: boolean
+        latestRelease:
+          type: object
+          properties:
+            versionTag:
+              type: string
+            publishedAt:
+              type: string
+            htmlUrl:
+              type: string
 
     Cluster:
       type: object