7 minute read

극혐그 자체..

override fun desired(wordpressApp: WordpressApp, context: Context<WordpressApp>): Deployment {  
    val labels = context.managedDependentResourceContext().getMandatory(  
        LABELS_CONTEXT_KEY, Map::class.java  
    ) as Map<String, String>  
    val name = wordpressApp.metadata.name  
    val spec = wordpressApp.spec  
    val wpImageSpec = spec.wpImageSpec  
    val nginxImageSpec = spec.nginxImageSpec  
    val env = spec.env  
  
    val wordpressDeploy =  
        DeploymentBuilder().withMetadata(createMetadata(wordpressApp, labels)).withNewSpec().withNewSelector()            .withMatchLabels<Any, Any>(labels).endSelector().withNewTemplate().withNewMetadata()            .withLabels<Any, Any>(labels).endMetadata().withNewSpec()            .addNewInitContainer().withName("init").withImage(wpImageSpec.getFullImageName()).addToCommand(  
                "/bin/sh",  
                "-c",  
                "until nc -z ${spec.dbSpec.host} 3306; do echo waiting for mysql; sleep 2; done"            ).endInitContainer()  
            .addNewContainer().withName("nginx").withImage(nginxImageSpec.getFullImageName()).endContainer()  
            .addNewContainer().withName(name).withImage(wpImageSpec.getFullImageName())  
    wordpressDeploy.addNewEnv()        .withName("WORDPRESS_DB_HOST").withValue(spec.dbSpec.host)  
        .withName("WORDPRESS_DB_USER").withValue(spec.dbSpec.username)  
        .withName("WORDPRESS_DB_PASSWORD").withValue(spec.dbSpec.password)  
        .withName("WORDPRESS_DB_NAME").withValue(spec.dbSpec.dbname)  
        .withName("WORDPRESS_TABLE_PREFIX").withValue(spec.dbSpec.dbPrefix)  
  
    // add env variables  
    env.forEach { (key: String, value: String) ->  
        wordpressDeploy.addNewEnv().withName(key.uppercase(Locale.getDefault())).withValue(value).endEnv()  
    }  
  
    return wordpressDeploy.addNewPort().withName("http").withProtocol("TCP").withContainerPort(8080).endPort()  
        .endContainer().endSpec().endTemplate().endSpec().build()}

이 쓸모없는 빌더를 만들기 위한 코드가…?

apps
ControllerRevision
ControllerRevisionBuilder
ControllerRevisionFluent
ControllerRevisionList
ControllerRevisionListBuilder
ControllerRevisionListFluent
DaemonSet
DaemonSetBuilder
DaemonSetCondition
DaemonSetConditionBuilder
DaemonSetConditionFluent
DaemonSetFluent
DaemonSetList
DaemonSetListBuilder
DaemonSetListFluent
DaemonSetSpec
DaemonSetSpecBuilder
DaemonSetSpecFluent
DaemonSetStatus
DaemonSetStatusBuilder
DaemonSetStatusFluent
DaemonSetUpdateStrategy
DaemonSetUpdateStrategyBuilder
DaemonSetUpdateStrategyFluent
Deployment
DeploymentBuilder
DeploymentCondition
DeploymentConditionBuilder
DeploymentConditionFluent
DeploymentFluent
MetadataNested
SpecNested
StatusNested
DeploymentList
DeploymentListBuilder
DeploymentListFluent
DeploymentSpec
DeploymentSpecBuilder
DeploymentSpecFluent
DeploymentStatus
DeploymentStatusBuilder
DeploymentStatusFluent
DeploymentStrategy
DeploymentStrategyBuilder
DeploymentStrategyFluent
ReplicaSet
ReplicaSetBuilder
ReplicaSetCondition
ReplicaSetConditionBuilder
ReplicaSetConditionFluent
ReplicaSetFluent
ReplicaSetList
ReplicaSetListBuilder
ReplicaSetListFluent
ReplicaSetSpec
ReplicaSetSpecBuilder
ReplicaSetSpecFluent
ReplicaSetStatus
ReplicaSetStatusBuilder
ReplicaSetStatusFluent
RollingUpdateDaemonSet
RollingUpdateDaemonSetBuilder
RollingUpdateDaemonSetFluent
RollingUpdateDeployment
RollingUpdateDeploymentBuilder
RollingUpdateDeploymentFluent
RollingUpdateStatefulSetStrategy
RollingUpdateStatefulSetStrategyBuilder
RollingUpdateStatefulSetStrategyFluent
StatefulSet
StatefulSetBuilder
StatefulSetCondition
StatefulSetConditionBuilder
StatefulSetConditionFluent
StatefulSetFluent
StatefulSetList
StatefulSetListBuilder
StatefulSetListFluent
StatefulSetOrdinals
StatefulSetOrdinalsBuilder
StatefulSetOrdinalsFluent
StatefulSetPersistentVolumeClaimRetentionPolicy
StatefulSetPersistentVolumeClaimRetentionPolicyBuilder
StatefulSetPersistentVolumeClaimRetentionPolicyFluent
StatefulSetSpec
StatefulSetSpecBuilder
StatefulSetSpecFluent
StatefulSetStatus
StatefulSetStatusBuilder
StatefulSetStatusFluent
StatefulSetUpdateStrategy
StatefulSetUpdateStrategyBuilder
StatefulSetUpdateStrategyFluent

그중 한개를 열어보면?

package io.fabric8.kubernetes.api.model.apps;  
  
import io.fabric8.kubernetes.api.model.ObjectMeta;  
import java.lang.SuppressWarnings;  
import io.fabric8.kubernetes.api.builder.Nested;  
import java.lang.String;  
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;  
import java.util.LinkedHashMap;  
import io.fabric8.kubernetes.api.model.ObjectMetaFluent;  
import io.fabric8.kubernetes.api.builder.BaseFluent;  
import java.lang.Object;  
import java.util.Map;  
  
/**  
 * Generated */@SuppressWarnings("unchecked")  
public class DeploymentFluent<A extends DeploymentFluent<A>> extends BaseFluent<A>{  
  public DeploymentFluent() {  
  }  public DeploymentFluent(Deployment instance) {  
    this.copyInstance(instance);  
  }  private String apiVersion;  
  private String kind;  
  private ObjectMetaBuilder metadata;  
  private DeploymentSpecBuilder spec;  
  private DeploymentStatusBuilder status;  
  private Map<String,Object> additionalProperties;  
  protected void copyInstance(Deployment instance) {  
    instance = (instance != null ? instance : new Deployment());  
    if (instance != null) {  
          this.withApiVersion(instance.getApiVersion());  
          this.withKind(instance.getKind());  
          this.withMetadata(instance.getMetadata());  
          this.withSpec(instance.getSpec());  
          this.withStatus(instance.getStatus());  
          this.withAdditionalProperties(instance.getAdditionalProperties());  
        }  }  public String getApiVersion() {  
    return this.apiVersion;  
  }  public A withApiVersion(String apiVersion) {  
    this.apiVersion = apiVersion;  
    return (A) this;  
  }  public boolean hasApiVersion() {  
    return this.apiVersion != null;  
  }  public String getKind() {  
    return this.kind;  
  }  public A withKind(String kind) {  
    this.kind = kind;  
    return (A) this;  
  }  public boolean hasKind() {  
    return this.kind != null;  
  }  public ObjectMeta buildMetadata() {  
    return this.metadata != null ? this.metadata.build() : null;  
  }  public A withMetadata(ObjectMeta metadata) {  
    this._visitables.remove("metadata");  
    if (metadata != null) {  
        this.metadata = new ObjectMetaBuilder(metadata);  
        this._visitables.get("metadata").add(this.metadata);  
    } else {  
        this.metadata = null;  
        this._visitables.get("metadata").remove(this.metadata);  
    }    return (A) this;  
  }  public boolean hasMetadata() {  
    return this.metadata != null;  
  }  public MetadataNested<A> withNewMetadata() {  
    return new MetadataNested(null);  
  }  public MetadataNested<A> withNewMetadataLike(ObjectMeta item) {  
    return new MetadataNested(item);  
  }  public MetadataNested<A> editMetadata() {  
    return withNewMetadataLike(java.util.Optional.ofNullable(buildMetadata()).orElse(null));  
  }  public MetadataNested<A> editOrNewMetadata() {  
    return withNewMetadataLike(java.util.Optional.ofNullable(buildMetadata()).orElse(new ObjectMetaBuilder().build()));  
  }  public MetadataNested<A> editOrNewMetadataLike(ObjectMeta item) {  
    return withNewMetadataLike(java.util.Optional.ofNullable(buildMetadata()).orElse(item));  
  }  public DeploymentSpec buildSpec() {  
    return this.spec != null ? this.spec.build() : null;  
  }  public A withSpec(DeploymentSpec spec) {  
    this._visitables.remove("spec");  
    if (spec != null) {  
        this.spec = new DeploymentSpecBuilder(spec);  
        this._visitables.get("spec").add(this.spec);  
    } else {  
        this.spec = null;  
        this._visitables.get("spec").remove(this.spec);  
    }    return (A) this;  
  }  public boolean hasSpec() {  
    return this.spec != null;  
  }  public SpecNested<A> withNewSpec() {  
    return new SpecNested(null);  
  }  public SpecNested<A> withNewSpecLike(DeploymentSpec item) {  
    return new SpecNested(item);  
  }  public SpecNested<A> editSpec() {  
    return withNewSpecLike(java.util.Optional.ofNullable(buildSpec()).orElse(null));  
  }  public SpecNested<A> editOrNewSpec() {  
    return withNewSpecLike(java.util.Optional.ofNullable(buildSpec()).orElse(new DeploymentSpecBuilder().build()));  
  }  public SpecNested<A> editOrNewSpecLike(DeploymentSpec item) {  
    return withNewSpecLike(java.util.Optional.ofNullable(buildSpec()).orElse(item));  
  }  public DeploymentStatus buildStatus() {  
    return this.status != null ? this.status.build() : null;  
  }  public A withStatus(DeploymentStatus status) {  
    this._visitables.remove("status");  
    if (status != null) {  
        this.status = new DeploymentStatusBuilder(status);  
        this._visitables.get("status").add(this.status);  
    } else {  
        this.status = null;  
        this._visitables.get("status").remove(this.status);  
    }    return (A) this;  
  }  public boolean hasStatus() {  
    return this.status != null;  
  }  public StatusNested<A> withNewStatus() {  
    return new StatusNested(null);  
  }  public StatusNested<A> withNewStatusLike(DeploymentStatus item) {  
    return new StatusNested(item);  
  }  public StatusNested<A> editStatus() {  
    return withNewStatusLike(java.util.Optional.ofNullable(buildStatus()).orElse(null));  
  }  public StatusNested<A> editOrNewStatus() {  
    return withNewStatusLike(java.util.Optional.ofNullable(buildStatus()).orElse(new DeploymentStatusBuilder().build()));  
  }  public StatusNested<A> editOrNewStatusLike(DeploymentStatus item) {  
    return withNewStatusLike(java.util.Optional.ofNullable(buildStatus()).orElse(item));  
  }  public A addToAdditionalProperties(String key,Object value) {  
    if(this.additionalProperties == null && key != null && value != null) { this.additionalProperties = new LinkedHashMap(); }  
    if(key != null && value != null) {this.additionalProperties.put(key, value);} return (A)this;  
  }  public A addToAdditionalProperties(Map<String,Object> map) {  
    if(this.additionalProperties == null && map != null) { this.additionalProperties = new LinkedHashMap(); }  
    if(map != null) { this.additionalProperties.putAll(map);} return (A)this;  
  }  public A removeFromAdditionalProperties(String key) {  
    if(this.additionalProperties == null) { return (A) this; }  
    if(key != null && this.additionalProperties != null) {this.additionalProperties.remove(key);} return (A)this;  
  }  public A removeFromAdditionalProperties(Map<String,Object> map) {  
    if(this.additionalProperties == null) { return (A) this; }  
    if(map != null) { for(Object key : map.keySet()) {if (this.additionalProperties != null){this.additionalProperties.remove(key);}}} return (A)this;  
  }  public Map<String,Object> getAdditionalProperties() {  
    return this.additionalProperties;  
  }  public <K,V>A withAdditionalProperties(Map<String,Object> additionalProperties) {  
    if (additionalProperties == null) {  
      this.additionalProperties = null;  
    } else {  
      this.additionalProperties = new LinkedHashMap(additionalProperties);  
    }    return (A) this;  
  }  public boolean hasAdditionalProperties() {  
    return this.additionalProperties != null;  
  }  public boolean equals(Object o) {  
    if (this == o) return true;  
    if (o == null || getClass() != o.getClass()) return false;  
    if (!super.equals(o)) return false;  
    DeploymentFluent that = (DeploymentFluent) o;    if (!java.util.Objects.equals(apiVersion, that.apiVersion)) return false;  
    if (!java.util.Objects.equals(kind, that.kind)) return false;  
    if (!java.util.Objects.equals(metadata, that.metadata)) return false;  
    if (!java.util.Objects.equals(spec, that.spec)) return false;  
    if (!java.util.Objects.equals(status, that.status)) return false;  
    if (!java.util.Objects.equals(additionalProperties, that.additionalProperties)) return false;  
    return true;  
  }  public int hashCode() {  
    return java.util.Objects.hash(apiVersion,  kind,  metadata,  spec,  status,  additionalProperties,  super.hashCode());  
  }  public String toString() {  
    StringBuilder sb = new StringBuilder();  
    sb.append("{");  
    if (apiVersion != null) { sb.append("apiVersion:"); sb.append(apiVersion + ","); }  
    if (kind != null) { sb.append("kind:"); sb.append(kind + ","); }  
    if (metadata != null) { sb.append("metadata:"); sb.append(metadata + ","); }  
    if (spec != null) { sb.append("spec:"); sb.append(spec + ","); }  
    if (status != null) { sb.append("status:"); sb.append(status + ","); }  
    if (additionalProperties != null && !additionalProperties.isEmpty()) { sb.append("additionalProperties:"); sb.append(additionalProperties); }  
    sb.append("}");  
    return sb.toString();  
  }  public class MetadataNested<N> extends ObjectMetaFluent<MetadataNested<N>> implements Nested<N>{  
    MetadataNested(ObjectMeta item) {  
      this.builder = new ObjectMetaBuilder(this, item);  
    }    ObjectMetaBuilder builder;  
    public N and() {  
      return (N) DeploymentFluent.this.withMetadata(builder.build());  
    }    public N endMetadata() {  
      return and();  
    }        
  }  
  public class SpecNested<N> extends DeploymentSpecFluent<SpecNested<N>> implements Nested<N>{  
    SpecNested(DeploymentSpec item) {  
      this.builder = new DeploymentSpecBuilder(this, item);  
    }    DeploymentSpecBuilder builder;  
    public N and() {  
      return (N) DeploymentFluent.this.withSpec(builder.build());  
    }    public N endSpec() {  
      return and();  
    }        
  }  
  public class StatusNested<N> extends DeploymentStatusFluent<StatusNested<N>> implements Nested<N>{  
    StatusNested(DeploymentStatus item) {  
      this.builder = new DeploymentStatusBuilder(this, item);  
    }    DeploymentStatusBuilder builder;  
    public N and() {  
      return (N) DeploymentFluent.this.withStatus(builder.build());  
    }    public N endStatus() {  
      return and();  
    }        
  }  
  
}

그리고 이걸로 만든 결과물…

override fun desired(wordpressApp: WordpressApp, context: Context<WordpressApp>): Deployment {  
    val labels = context.managedDependentResourceContext().getMandatory(  
        LABELS_CONTEXT_KEY, Map::class.java  
    ) as Map<String, String>  
    val name = wordpressApp.metadata.name  
    val spec = wordpressApp.spec  
    val wpImageSpec = spec.wpImageSpec  
    val nginxImageSpec = spec.nginxImageSpec  
    val env = spec.env  
  
    val wordpressDeploy =  
        DeploymentBuilder().withMetadata(createMetadata(wordpressApp, labels)).withNewSpec().withNewSelector()            .withMatchLabels<Any, Any>(labels).endSelector().withNewTemplate().withNewMetadata()            .withLabels<Any, Any>(labels).endMetadata().withNewSpec()            .addNewInitContainer().withName("init").withImage(wpImageSpec.getFullImageName()).addToCommand(  
                "/bin/sh",  
                "-c",  
                "until nc -z ${spec.dbSpec.host} 3306; do echo waiting for mysql; sleep 2; done"            ).endInitContainer()  
            .addNewContainer().withName("nginx").withImage(nginxImageSpec.getFullImageName()).endContainer()  
            .addNewContainer().withName(name).withImage(wpImageSpec.getFullImageName())  
    wordpressDeploy.addNewEnv()        .withName("WORDPRESS_DB_HOST").withValue(spec.dbSpec.host)  
        .withName("WORDPRESS_DB_USER").withValue(spec.dbSpec.username)  
        .withName("WORDPRESS_DB_PASSWORD").withValue(spec.dbSpec.password)  
        .withName("WORDPRESS_DB_NAME").withValue(spec.dbSpec.dbname)  
        .withName("WORDPRESS_TABLE_PREFIX").withValue(spec.dbSpec.dbPrefix)  
  
    // add env variables  
    env.forEach { (key: String, value: String) ->  
        wordpressDeploy.addNewEnv().withName(key.uppercase(Locale.getDefault())).withValue(value).endEnv()  
    }  
  
    return wordpressDeploy.addNewPort().withName("http").withProtocol("TCP").withContainerPort(8080).endPort()  
        .endContainer().endSpec().endTemplate().endSpec().build()}

빌더 패턴은 쓰레기다 생성자에 이름을 쓸 수 없는 자바의 한계 때문에 생겨난 ..

계층화된 데이터를 빌더를 이용해 표현하니 계층도 알 수 없고 어디서 뭘 넣어 할지 알 수도 없게 돼 버린다.

이걸 생성자 게터세터로 변환하면 한결나아진다 (미완성코드지만)

override fun desired(wordpressApp: WordpressApp, context: Context<WordpressApp>): Deployment {  
    val wpLabels = context.managedDependentResourceContext().getMandatory(  
        LABELS_CONTEXT_KEY, Map::class.java  
    ) as Map<String, String>  
    val appSpec = wordpressApp.spec  
    val wpImageSpec = appSpec.wpImageSpec  
    val nginxImageSpec = appSpec.nginxImageSpec  
  
    val fragMetadata = ObjectMeta().apply {  
        name = wordpressApp.metadata.name  
        labels= wpLabels  
    }  
  
    val fragLabelSelector = LabelSelector().apply {  
        matchLabels = wpLabels  
    }  
  
    val initContainer = Container().apply {  
        name = "init"  
        image = wpImageSpec.getFullImageName()  
        command = listOf("/bin/sh", "-c", "until nc -z ${appSpec.dbSpec.host} 3306; do echo waiting for mysql; sleep 2; done")  
    }  
  
    val nginxContainer = Container().apply {  
        name = "nginx"  
        image = nginxImageSpec.getFullImageName()  
    }  
  
    val appContainer = Container().apply {  
        name = "app"  
        image = wpImageSpec.getFullImageName()  
        env.addAll(appSpec.env.map { (key, value) ->  
            EnvVar(key.uppercase(Locale.getDefault()), value, null)  
        })  
        env.add(EnvVar("WORDPRESS_DB_HOST", appSpec.dbSpec.host, null))  
        env.add(EnvVar("WORDPRESS_DB_USER", appSpec.dbSpec.username, null))  
        env.add(EnvVar("WORDPRESS_DB_PASSWORD", appSpec.dbSpec.password, null))  
        env.add(EnvVar("WORDPRESS_DB_NAME", appSpec.dbSpec.dbname, null))  
        env.add(EnvVar("WORDPRESS_TABLE_PREFIX", appSpec.dbSpec.dbPrefix, null))  
  
        ports = listOf(  
            ContainerPort().apply {  
                name = "http"  
                protocol = "TCP"  
                containerPort = 8080  
            }  
        )  
    }  
  
    val wpDeploy = Deployment().apply {  
        apiVersion = "apps/v1"  
        kind = "Deployment"  
  
        metadata = createMetadata(wordpressApp, wpLabels)  
        spec = DeploymentSpec().apply {  
            selector = fragLabelSelector  
            template = PodTemplateSpec().apply {  
                metadata = createMetadata(wordpressApp, wpLabels)  
                spec = PodSpec().apply {  
                    initContainers = listOf(initContainer)  
                    containers = listOf(nginxContainer, appContainer)  
                }  
            }        }        status = null  
        additionalProperties = null  
  
    }  
  
    return wpDeploy  
}

k8s yaml의 구조만 이해하고 있으면 이 코드가 무슨 의미인지 금방 알 수 있다.

ex2) ingress builder

return IngressBuilder()  
    .withMetadata(metadata)  
    .withNewSpec()    .addNewRule()    .withNewHttp()    .addNewPath()    .withPath("/")  
    .withPathType("Prefix")  
    .withNewBackend()    .withNewService()    .withName(metadata.name)  
    .withNewPort().withNumber(8080).endPort()  
    .endService()    .endBackend()    .endPath()    .endHttp()    .endRule()    .endSpec()    .build()

ingress constructor?뭐라고 해야할까

Ingress().apply {  
    apiVersion = "networking.k8s.io/v1"  
    kind = "Ingress"  
  
    metadata = fragMetadata  
    spec = IngressSpec().apply {  
        rules = listOf(  
            IngressRule().apply {  
                http = HTTPIngressRuleValue().apply {  
                    paths = listOf(  
                        HTTPIngressPath().apply {  
                            path = "/"  
                            pathType = "Prefix"  
                            backend = IngressBackend().apply {  
                                service = IngressServiceBackend().apply {  
                                    name = fragMetadata.name  
                                    port = ServiceBackendPort().apply {  
                                        number = 8080  
                                    }  
                                }                            }                        }                    )  
                }  
            }        )  
    }  
}

~

  • 자바 빌더 쓰지말자
  • 코틀린을 쓰자
  • Kotlin DSL 라이브러리를 별도로 쓸 필요는 없다(과도하게 복잡성이 추가됨)

Updated: