diff --git a/src/main/java/io/ten1010/aipub/projectcontroller/controller/namespaced/ImageRegistrySecretReconciler.java b/src/main/java/io/ten1010/aipub/projectcontroller/controller/namespaced/ImageRegistrySecretReconciler.java index 77ce4c11..e62dbdbf 100644 --- a/src/main/java/io/ten1010/aipub/projectcontroller/controller/namespaced/ImageRegistrySecretReconciler.java +++ b/src/main/java/io/ten1010/aipub/projectcontroller/controller/namespaced/ImageRegistrySecretReconciler.java @@ -16,10 +16,12 @@ import io.ten1010.aipub.projectcontroller.domain.k8s.ImageRegistrySecretNameResolver; import io.ten1010.aipub.projectcontroller.domain.k8s.K8sApiProvider; import io.ten1010.aipub.projectcontroller.domain.k8s.KeyResolver; +import io.ten1010.aipub.projectcontroller.domain.k8s.LabelConstants; import io.ten1010.aipub.projectcontroller.domain.k8s.NamespaceNameResolver; import io.ten1010.aipub.projectcontroller.domain.k8s.ReconciliationService; import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1Project; import io.ten1010.aipub.projectcontroller.domain.k8s.util.K8sObjectUtils; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -103,15 +105,36 @@ protected Result reconcileInternal(Request request) throws ApiException { deleteSecret(secretOpt.get()); return new Result(false); } - Map reconciledData; - try { - reconciledData = this.reconciliationService.reconcileImageRegistrySecretData( - projectOpt.get()); - } catch (ImageHubNotConnectedException e) { + + Optional currentRobotIdOpt = this.reconciliationService.resolveImageRegistryRobotId( + projectOpt.get()); + if (currentRobotIdOpt.isEmpty()) { return logImageHubNotConnectedAndRequeue(projName); } + String currentRobotId = currentRobotIdOpt.get(); + String existingRobotId = K8sObjectUtils.getAnnotations(secretOpt.get()) + .get(LabelConstants.IMAGE_REGISTRY_ROBOT_ID_KEY); + + Map reconciledData; + if (currentRobotId.equals(existingRobotId)) { + // robot 이 그대로면 비밀번호도 여전히 유효하다 — refreshSecret 을 또 부를 필요 없이 + // 기존 Secret 데이터를 그대로 재사용한다. permission(bindingImageHubs) 변경은 robot + // 을 재생성하지 않으므로, 이 비교만으로 "재발급이 필요한가"를 판단할 수 있다. + reconciledData = secretOpt.get().getData(); + } else { + try { + reconciledData = this.reconciliationService.reconcileImageRegistrySecretData( + projectOpt.get()); + } catch (ImageHubNotConnectedException e) { + return logImageHubNotConnectedAndRequeue(projName); + } + } + Map reconciledAnnotations = new HashMap<>( + K8sObjectUtils.getAnnotations(secretOpt.get())); + reconciledAnnotations.put(LabelConstants.IMAGE_REGISTRY_ROBOT_ID_KEY, currentRobotId); + return reconcileExistingSecret(secretOpt.get(), reconciledReferences, reconciledType, - reconciledData, reconciledLabels); + reconciledData, reconciledLabels, reconciledAnnotations); } if (!K8sObjectUtils.isTerminating(projectOpt.get())) { @@ -124,8 +147,13 @@ protected Result reconcileInternal(Request request) throws ApiException { } Map reconciledLabels = this.reconciliationService.reconcileSecretLabels( namespaceOpt.get(), projectOpt.get()); + Map reconciledAnnotations = this.reconciliationService + .resolveImageRegistryRobotId(projectOpt.get()) + .map(id -> Map.of(LabelConstants.IMAGE_REGISTRY_ROBOT_ID_KEY, id)) + .orElse(Map.of()); return reconcileNoExistingSecret(request.getNamespace(), request.getName(), - reconciledReferences, reconciledType, reconciledData, reconciledLabels); + reconciledReferences, reconciledType, reconciledData, reconciledLabels, + reconciledAnnotations); } return new Result(false); @@ -143,13 +171,15 @@ private Result reconcileNoExistingSecret( List reconciledReferences, String reconciledType, Map reconciledData, - Map reconciledLabels) throws ApiException { + Map reconciledLabels, + Map reconciledAnnotations) throws ApiException { V1Secret secret = new V1SecretBuilder() .withNewMetadata() .withNamespace(namespace) .withName(objName) .withOwnerReferences(reconciledReferences) .withLabels(reconciledLabels) + .withAnnotations(reconciledAnnotations) .endMetadata() .withType(reconciledType) .withData(reconciledData) @@ -164,17 +194,20 @@ private Result reconcileExistingSecret( List reconciledReferences, String reconciledType, Map reconciledData, - Map reconciledLabels) throws ApiException { + Map reconciledLabels, + Map reconciledAnnotations) throws ApiException { if (Set.copyOf(K8sObjectUtils.getOwnerReferences(existing)) .equals(Set.copyOf(reconciledReferences)) && Objects.equals(existing.getType(), reconciledType) && - Objects.equals(existing.getData(), reconciledData)) { + Objects.equals(existing.getData(), reconciledData) && + Objects.equals(K8sObjectUtils.getAnnotations(existing), reconciledAnnotations)) { return new Result(false); } V1Secret edited = new V1SecretBuilder(existing) .editMetadata() .withOwnerReferences(reconciledReferences) .withLabels(reconciledLabels) + .withAnnotations(reconciledAnnotations) .endMetadata() .withType(reconciledType) .withData(reconciledData) diff --git a/src/main/java/io/ten1010/aipub/projectcontroller/domain/aipubbackend/AipubDockerConfigJsonResolver.java b/src/main/java/io/ten1010/aipub/projectcontroller/domain/aipubbackend/AipubDockerConfigJsonResolver.java index 90998d7c..64399586 100644 --- a/src/main/java/io/ten1010/aipub/projectcontroller/domain/aipubbackend/AipubDockerConfigJsonResolver.java +++ b/src/main/java/io/ten1010/aipub/projectcontroller/domain/aipubbackend/AipubDockerConfigJsonResolver.java @@ -65,6 +65,13 @@ public Map resolve(V1alpha1Project project) { return json; } + @Override + public Optional resolveImageRegistryRobotId(V1alpha1Project project) { + String username = this.imageRegistryRobotUsernameResolver.resolve( + K8sObjectUtils.getName(project)); + return findByUsername(username).map(ImageRegistryRobot::getId); + } + private String getPassword(ImageRegistryRobot robot) { Objects.requireNonNull(robot.getId()); ImageRegistryRobotSecret secret = this.imageRegistryRobotService.refreshSecret(robot.getId()); diff --git a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DefaultDockerConfigJsonResolver.java b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DefaultDockerConfigJsonResolver.java index 8622c6f0..3ad2841d 100644 --- a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DefaultDockerConfigJsonResolver.java +++ b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DefaultDockerConfigJsonResolver.java @@ -2,6 +2,7 @@ import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1Project; import java.util.Map; +import java.util.Optional; public class DefaultDockerConfigJsonResolver implements DockerConfigJsonResolver { @@ -10,4 +11,9 @@ public Map resolve(V1alpha1Project project) { return Map.of(); } + @Override + public Optional resolveImageRegistryRobotId(V1alpha1Project project) { + return Optional.empty(); + } + } diff --git a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DockerConfigJsonResolver.java b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DockerConfigJsonResolver.java index 730c6422..85a4b44f 100644 --- a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DockerConfigJsonResolver.java +++ b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/DockerConfigJsonResolver.java @@ -2,9 +2,16 @@ import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1Project; import java.util.Map; +import java.util.Optional; public interface DockerConfigJsonResolver { Map resolve(V1alpha1Project project); + /** + * project 에 연결된 image registry robot 의 id 를 조회한다. secret 을 새로 발급받지 않고 + * "지금 이 프로젝트의 robot 이 마지막으로 발급했던 robot 과 같은가"만 값싸게 확인하기 위한 용도. + */ + Optional resolveImageRegistryRobotId(V1alpha1Project project); + } diff --git a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/LabelConstants.java b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/LabelConstants.java index cde63ae4..c956b398 100644 --- a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/LabelConstants.java +++ b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/LabelConstants.java @@ -16,6 +16,8 @@ public final class LabelConstants { ProjectApiConstants.AIPUB_GROUP + "/" + "workload-name"; public static final String WORKLOAD_KIND_KEY = ProjectApiConstants.AIPUB_GROUP + "/" + "workload-kind"; + public static final String IMAGE_REGISTRY_ROBOT_ID_KEY = + ProjectApiConstants.PROJECT_GROUP + "/" + "image-registry-robot-id"; private LabelConstants() { } diff --git a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/ReconciliationService.java b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/ReconciliationService.java index aae21b57..d9274a3e 100644 --- a/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/ReconciliationService.java +++ b/src/main/java/io/ten1010/aipub/projectcontroller/domain/k8s/ReconciliationService.java @@ -914,6 +914,15 @@ public Map reconcileImageRegistrySecretData(V1alpha1Project proj } } + /** + * project 에 연결된 image registry robot 의 id 를 조회한다(secret 재발급 없이). 이미 반영된 + * Secret 의 {@link LabelConstants#IMAGE_REGISTRY_ROBOT_ID_KEY} annotation 과 비교해서, robot + * 이 그대로인지(=비밀번호도 그대로 유효한지) 판단하는 용도로 쓰인다. + */ + public Optional resolveImageRegistryRobotId(V1alpha1Project project) { + return this.dockerConfigJsonResolver.resolveImageRegistryRobotId(project); + } + public Map reconcileNodeLabels(V1Node existing) { Map existingLabels = K8sObjectUtils.getLabels(existing); Map reconciled = new HashMap<>(existingLabels);