diff --git a/.gitignore b/.gitignore index 8a09c996a..ea7768ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ __pycache__ .pytest_cache *.pyc + +*bin/ +.DS_Store +.vscode/ diff --git a/Documentation/operator.md b/Documentation/operator.md index 55eed9695..919e21766 100644 --- a/Documentation/operator.md +++ b/Documentation/operator.md @@ -157,9 +157,14 @@ for the Gerrit instances it manages. The network routing rules ensure that reque will be routed to the intended GerritCluster component, e.g. in case a primary Gerrit and a Gerrit Replica exist in the cluster, git fetch/clone requests will be sent to the Gerrit Replica and all other requests to the primary Gerrit. -The Gerrit Operator currently supports the following Ingress providers, which -can be configured for each -[GerritCluster](operator-api-reference.md#gerritclusteringressconfig): + +You may specify the ingress provider by setting the `INGRESS` environment +variable in the operator Deployment manifest. That is, the choice of an ingress +provider is an operator-level setting. However, you may specify some ingress +configuration options (host, tls, etc) at the `GerritCluster` level, via +[GerritClusterIngressConfig](operator-api-reference.md#gerritclusteringressconfig). + +The Gerrit Operator currently supports the following Ingress providers: - **NONE** @@ -186,6 +191,15 @@ can be configured for each The operator supports the use of [Istio](https://istio.io/) as a service mesh. An example on how to set up Istio can be found [here](../istio/gerrit.profile.yaml). +- **AMBASSADOR** + + The operator also supports [Ambassador](https://www.getambassador.io/) for + setting up ingress to the Gerrits deployed by the operator. If you use + Ambassador's "Edge Stack" or "Emissary Ingress" to provide ingress to your k8s + services, you should set INGRESS=AMBASSADOR. Currently, SSH is not directly + supported when using INGRESS=AMBASSADOR. + + ## Deploy You will need to have admin privileges for your k8s cluster in order to be able to deploy the following resources. @@ -280,7 +294,8 @@ kubectl apply -f operator/k8s/operator.yaml `k8s/operator.yaml` contains a basic deployment of the operator. Resources, docker image name etc. might have to be adapted. For example, the ingress provider has to be configured by setting the `INGRESS` environment variable -in `operator/k8s/operator.yaml` to either `NONE`, `INGRESS` or `ISTIO`. +in `operator/k8s/operator.yaml` to either `NONE`, `INGRESS`, `ISTIO`, or +`AMBASSADOR`. ## CustomResources diff --git a/LICENSE b/LICENSE index 5e173a389..27bdfb655 100644 --- a/LICENSE +++ b/LICENSE @@ -255,6 +255,11 @@ https://github.com/SeleniumHQ/selenium \ Copyright 2022 Software Freedom Conservancy (SFC) \ Apache 2 license (https://github.com/SeleniumHQ/selenium/blob/trunk/LICENSE) +Ambassador \ +https://github.com/emissary-ingress/emissary \ +Copyright 2021 Ambassador Labs \ +Apache 2 license (https://github.com/emissary-ingress/emissary/blob/master/LICENSE) + --- ## The MIT License (MIT) diff --git a/operator/pom.xml b/operator/pom.xml index ce7a44cbf..3697fbc9c 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -19,6 +19,7 @@ 0.7.4 4.3.3 11.0.15 + 1.18.28 11 11 docker.io @@ -171,6 +172,17 @@ ${fabric8.version} provided + + io.fabric8 + generator-annotations + ${fabric8.version} + + + org.projectlombok + lombok + provided + ${lombok.version} + org.eclipse.jetty jetty-server @@ -244,6 +256,23 @@ + + io.fabric8 + java-generator-maven-plugin + ${fabric8.version} + + ${project.basedir}/src/main/resources/crd/emissary-crds.yaml + + true + + + + + generate + + + + com.spotify.fmt fmt-maven-plugin diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplate.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplate.java index 4c1fa12f6..602431ace 100644 --- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplate.java +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplate.java @@ -33,7 +33,7 @@ @Buildable( editableEnabled = false, validationEnabled = false, - generateBuilderPackage = true, + generateBuilderPackage = false, lazyCollectionInitEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder") public class GerritTemplate implements KubernetesResource { diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/Constants.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/Constants.java new file mode 100644 index 000000000..1a7f3b20a --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/Constants.java @@ -0,0 +1,22 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License.package com.google.gerrit.k8s.operator.network; + +package com.google.gerrit.k8s.operator.network; + +public class Constants { + public static String UPLOAD_PACK_URL_PATTERN = "/.*/git-upload-pack"; + public static String INFO_REFS_PATTERN = "/.*/info/refs"; + public static String RECEIVE_PACK_URL_PATTERN = "/.*/git-receive-pack"; + public static String PROJECTS_URL_PATTERN = "/a/projects/.*"; +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/GerritNetworkReconcilerProvider.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/GerritNetworkReconcilerProvider.java index 5390bd31c..5d68e81d3 100644 --- a/operator/src/main/java/com/google/gerrit/k8s/operator/network/GerritNetworkReconcilerProvider.java +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/GerritNetworkReconcilerProvider.java @@ -14,6 +14,7 @@ package com.google.gerrit.k8s.operator.network; +import com.google.gerrit.k8s.operator.network.ambassador.GerritAmbassadorReconciler; import com.google.gerrit.k8s.operator.network.ingress.GerritIngressReconciler; import com.google.gerrit.k8s.operator.network.istio.GerritIstioReconciler; import com.google.gerrit.k8s.operator.network.model.GerritNetwork; @@ -38,6 +39,8 @@ public Reconciler get() { return new GerritIngressReconciler(); case ISTIO: return new GerritIstioReconciler(); + case AMBASSADOR: + return new GerritAmbassadorReconciler(); default: return new GerritNoIngressReconciler(); } diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/IngressType.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/IngressType.java index a4af09e02..9c5383c44 100644 --- a/operator/src/main/java/com/google/gerrit/k8s/operator/network/IngressType.java +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/IngressType.java @@ -17,5 +17,6 @@ public enum IngressType { NONE, INGRESS, - ISTIO + ISTIO, + AMBASSADOR } diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/GerritAmbassadorReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/GerritAmbassadorReconciler.java new file mode 100644 index 000000000..2b9f594fc --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/GerritAmbassadorReconciler.java @@ -0,0 +1,156 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador; + +import static com.google.gerrit.k8s.operator.network.ambassador.GerritAmbassadorReconciler.MAPPING_EVENT_SOURCE; +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMapping.GERRIT_MAPPING; +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingGETReplica.GERRIT_MAPPING_GET_REPLICA; +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingPOSTReplica.GERRIT_MAPPING_POST_REPLICA; +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingPrimary.GERRIT_MAPPING_PRIMARY; +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingReceiver.GERRIT_MAPPING_RECEIVER; +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingReceiverGET.GERRIT_MAPPING_RECEIVER_GET; + +import com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMapping; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingGETReplica; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingPOSTReplica; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingPrimary; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingReceiver; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingReceiverGET; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.LoadBalanceCondition; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.ReceiverMappingCondition; +import com.google.gerrit.k8s.operator.network.ambassador.dependent.SingleMappingCondition; +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import com.google.inject.Singleton; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.HashMap; +import java.util.Map; + +/** + * Provides an Ambassador-based implementation for GerritNetworkReconciler. + * + *

Creates and manages Ambassador Custom Resources using the "managed dependent resources" + * approach in josdk. Since multiple dependent resources of the same type (`Mapping`) need to be + * created, "resource discriminators" are used for each of the different Mapping dependent + * resources. + * + *

Ambassador custom resource POJOs are generated via the `java-generator-maven-plugin` in the + * fabric8 project. + * + *

Mapping logic + * + *

The Mappings are created based on the composition of Gerrit instances in the GerritCluster. + * + *

There are three cases: + * + *

1. 0 Primary 1 Replica + * + *

Direct all traffic (read/write) to the Replica + * + *

2. 1 Primary 0 Replica + * + *

Direct all traffic (read/write) to the Primary + * + *

3. 1 Primary 1 Replica + * + *

Direct write traffic to Primary and read traffic to Replica. To capture this requirement, + * three different Mappings have to be created. + * + *

Note: git fetch/clone operations result in two HTTP requests to the git server. The first is + * of the form `GET /my-test-repo/info/refs?service=git-upload-pack` and the second is of the form + * `POST /my-test-repo/git-upload-pack`. + * + *

Note: git push operations result in two HTTP requests to the git server. The first is of the + * form `GET /my-test-repo/info/refs?service=git-receive-pack` and the second is of the form `POST + * /my-test-repo/git-receive-pack`. + * + *

If a Receiver is part of the GerritCluster, additional mappings are created such that all + * requests that the replication plugin sends to the `adminUrl` [1] are routed to the Receiver. This + * includes `git push` related `GET` and `POST` requests, and requests to the `/projects` REST API + * endpoints. + * + *

[1] + * https://gerrit.googlesource.com/plugins/replication/+/refs/heads/master/src/main/resources/Documentation/config.md + */ +@Singleton +@ControllerConfiguration( + // namespaces = "gerrit-operator", + dependents = { + @Dependent( + name = GERRIT_MAPPING, + type = GerritClusterMapping.class, + // Cluster has only either Primary or Replica instance + reconcilePrecondition = SingleMappingCondition.class, + useEventSourceWithName = MAPPING_EVENT_SOURCE), + @Dependent( + name = GERRIT_MAPPING_POST_REPLICA, + type = GerritClusterMappingPOSTReplica.class, + // Cluster has both Primary and Replica instances + reconcilePrecondition = LoadBalanceCondition.class, + useEventSourceWithName = MAPPING_EVENT_SOURCE), + @Dependent( + name = GERRIT_MAPPING_GET_REPLICA, + type = GerritClusterMappingGETReplica.class, + reconcilePrecondition = LoadBalanceCondition.class, + useEventSourceWithName = MAPPING_EVENT_SOURCE), + @Dependent( + name = GERRIT_MAPPING_PRIMARY, + type = GerritClusterMappingPrimary.class, + reconcilePrecondition = LoadBalanceCondition.class, + useEventSourceWithName = MAPPING_EVENT_SOURCE), + @Dependent( + name = GERRIT_MAPPING_RECEIVER, + type = GerritClusterMappingReceiver.class, + reconcilePrecondition = ReceiverMappingCondition.class, + useEventSourceWithName = MAPPING_EVENT_SOURCE), + @Dependent( + name = GERRIT_MAPPING_RECEIVER_GET, + type = GerritClusterMappingReceiverGET.class, + reconcilePrecondition = ReceiverMappingCondition.class, + useEventSourceWithName = MAPPING_EVENT_SOURCE) + }) +public class GerritAmbassadorReconciler + implements Reconciler, EventSourceInitializer { + + public static final String MAPPING_EVENT_SOURCE = "mapping-event-source"; + + // Because we have multiple dependent resources of the same type `Mapping`, we need to specify + // a named event source. + @Override + public Map prepareEventSources(EventSourceContext context) { + InformerEventSource mappingEventSource = + new InformerEventSource<>( + InformerConfiguration.from(Mapping.class, context).build(), context); + + Map eventSources = new HashMap<>(); + eventSources.put(MAPPING_EVENT_SOURCE, mappingEventSource); + return eventSources; + } + + @Override + public UpdateControl reconcile( + GerritNetwork resource, Context context) throws Exception { + return UpdateControl.noUpdate(); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/AbstractAmbassadorDependentResource.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/AbstractAmbassadorDependentResource.java new file mode 100644 index 000000000..b6438eee6 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/AbstractAmbassadorDependentResource.java @@ -0,0 +1,62 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.cluster.model.GerritCluster; +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.getambassador.v2.MappingSpec; +import io.getambassador.v2.MappingSpecBuilder; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import java.util.List; + +public abstract class AbstractAmbassadorDependentResource + extends CRUDKubernetesDependentResource { + + public AbstractAmbassadorDependentResource(Class dependentResourceClass) { + super(dependentResourceClass); + } + + public ObjectMeta getCommonMetadata(GerritNetwork gerritnetwork, String name, String className) { + ObjectMeta metadata = + new ObjectMetaBuilder() + .withName(name) + .withNamespace(gerritnetwork.getMetadata().getNamespace()) + .withLabels( + GerritCluster.getLabels(gerritnetwork.getMetadata().getName(), name, className)) + .build(); + return metadata; + } + + public MappingSpec getCommonSpec(GerritNetwork gerritnetwork, String serviceName) { + MappingSpec spec = + new MappingSpecBuilder() + .withAmbassadorId(getAmbassadorIds(gerritnetwork)) + .withHost(gerritnetwork.getSpec().getIngress().getHost()) + .withPrefix("/") + .withService(serviceName) + .withBypassAuth(true) + .withRewrite("") // important - so the prefix doesn't get overwritten to "/" + .build(); + return spec; + } + + public List getAmbassadorIds(GerritNetwork gerritnetwork) { + // TODO: Allow users to configure ambassador_id + return null; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMapping.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMapping.java new file mode 100644 index 000000000..e6f63edbf --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMapping.java @@ -0,0 +1,53 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import com.google.gerrit.k8s.operator.network.model.NetworkMemberWithSsh; +import io.getambassador.v2.Mapping; +import io.getambassador.v2.MappingBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent(resourceDiscriminator = GerritClusterMappingDiscriminator.class) +public class GerritClusterMapping extends AbstractAmbassadorDependentResource + implements MappingDependentResourceInterface { + + public static final String GERRIT_MAPPING = "gerrit-mapping"; + + public GerritClusterMapping() { + super(Mapping.class); + } + + @Override + public Mapping desired(GerritNetwork gerritNetwork, Context context) { + + // If only one Gerrit instance in GerritCluster, send all git-over-https requests to it + NetworkMemberWithSsh gerrit = + gerritNetwork.hasGerritReplica() + ? gerritNetwork.getSpec().getGerritReplica() + : gerritNetwork.getSpec().getPrimaryGerrit(); + String serviceName = gerrit.getName() + ":" + gerrit.getHttpPort(); + Mapping mapping = + new MappingBuilder() + .withNewMetadataLike( + getCommonMetadata(gerritNetwork, GERRIT_MAPPING, this.getClass().getSimpleName())) + .endMetadata() + .withNewSpecLike(getCommonSpec(gerritNetwork, serviceName)) + .endSpec() + .build(); + return mapping; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingDiscriminator.java new file mode 100644 index 000000000..e4bd776ed --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingDiscriminator.java @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMapping.GERRIT_MAPPING; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.Optional; + +public class GerritClusterMappingDiscriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish( + Class resource, GerritNetwork network, Context context) { + InformerEventSource ies = + (InformerEventSource) + context.eventSourceRetriever().getResourceEventSourceFor(Mapping.class); + return ies.get(new ResourceID(GERRIT_MAPPING, network.getMetadata().getNamespace())); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingGETReplica.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingGETReplica.java new file mode 100644 index 000000000..6619c7520 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingGETReplica.java @@ -0,0 +1,68 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.Constants.INFO_REFS_PATTERN; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.getambassador.v2.MappingBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import java.util.HashMap; + +@KubernetesDependent(resourceDiscriminator = GerritClusterMappingGETReplicaDiscriminator.class) +public class GerritClusterMappingGETReplica extends AbstractAmbassadorDependentResource + implements MappingDependentResourceInterface { + + public static final String GERRIT_MAPPING_GET_REPLICA = "gerrit-mapping-get-replica"; + + public GerritClusterMappingGETReplica() { + super(Mapping.class); + } + + @Override + public Mapping desired(GerritNetwork gerritNetwork, Context context) { + + String replicaServiceName = + gerritNetwork.getSpec().getGerritReplica().getName() + + ":" + + gerritNetwork.getSpec().getGerritReplica().getHttpPort(); + + // Send fetch/clone GET requests to the Replica + Mapping mapping = + new MappingBuilder() + .withNewMetadataLike( + getCommonMetadata( + gerritNetwork, GERRIT_MAPPING_GET_REPLICA, this.getClass().getSimpleName())) + .endMetadata() + .withNewSpecLike(getCommonSpec(gerritNetwork, replicaServiceName)) + .withNewV2QueryParameters() + .withAdditionalProperties( + new HashMap() { + { + put("service", "git-upload-pack"); + } + }) + .endV2QueryParameters() + .withMethod("GET") + .withPrefix(INFO_REFS_PATTERN) + .withPrefixRegex(true) + .endSpec() + .build(); + + return mapping; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingGETReplicaDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingGETReplicaDiscriminator.java new file mode 100644 index 000000000..42dd429fb --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingGETReplicaDiscriminator.java @@ -0,0 +1,38 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingGETReplica.GERRIT_MAPPING_GET_REPLICA; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.Optional; + +public class GerritClusterMappingGETReplicaDiscriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish( + Class resource, GerritNetwork network, Context context) { + InformerEventSource ies = + (InformerEventSource) + context.eventSourceRetriever().getResourceEventSourceFor(Mapping.class); + return ies.get( + new ResourceID(GERRIT_MAPPING_GET_REPLICA, network.getMetadata().getNamespace())); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPOSTReplica.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPOSTReplica.java new file mode 100644 index 000000000..5274f5096 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPOSTReplica.java @@ -0,0 +1,58 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.Constants.UPLOAD_PACK_URL_PATTERN; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.getambassador.v2.MappingBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent(resourceDiscriminator = GerritClusterMappingPOSTReplicaDiscriminator.class) +public class GerritClusterMappingPOSTReplica extends AbstractAmbassadorDependentResource + implements MappingDependentResourceInterface { + + public static final String GERRIT_MAPPING_POST_REPLICA = "gerrit-mapping-post-replica"; + + public GerritClusterMappingPOSTReplica() { + super(Mapping.class); + } + + @Override + public Mapping desired(GerritNetwork gerritNetwork, Context context) { + + String replicaServiceName = + gerritNetwork.getSpec().getGerritReplica().getName() + + ":" + + gerritNetwork.getSpec().getGerritReplica().getHttpPort(); + + // Send fetch/clone POST requests to the Replica + Mapping mapping = + new MappingBuilder() + .withNewMetadataLike( + getCommonMetadata( + gerritNetwork, GERRIT_MAPPING_POST_REPLICA, this.getClass().getSimpleName())) + .endMetadata() + .withNewSpecLike(getCommonSpec(gerritNetwork, replicaServiceName)) + .withPrefix(UPLOAD_PACK_URL_PATTERN) + .withPrefixRegex(true) + .withMethod("POST") + .endSpec() + .build(); + return mapping; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPOSTReplicaDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPOSTReplicaDiscriminator.java new file mode 100644 index 000000000..cb1b8d968 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPOSTReplicaDiscriminator.java @@ -0,0 +1,38 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingPOSTReplica.GERRIT_MAPPING_POST_REPLICA; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.Optional; + +public class GerritClusterMappingPOSTReplicaDiscriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish( + Class resource, GerritNetwork network, Context context) { + InformerEventSource ies = + (InformerEventSource) + context.eventSourceRetriever().getResourceEventSourceFor(Mapping.class); + return ies.get( + new ResourceID(GERRIT_MAPPING_POST_REPLICA, network.getMetadata().getNamespace())); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPrimary.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPrimary.java new file mode 100644 index 000000000..0c936836f --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPrimary.java @@ -0,0 +1,55 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.getambassador.v2.MappingBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent(resourceDiscriminator = GerritClusterMappingPrimaryDiscriminator.class) +public class GerritClusterMappingPrimary extends AbstractAmbassadorDependentResource + implements MappingDependentResourceInterface { + + public static final String GERRIT_MAPPING_PRIMARY = "gerrit-mapping-primary"; + + public GerritClusterMappingPrimary() { + super(Mapping.class); + } + + @Override + public Mapping desired(GerritNetwork gerritNetwork, Context context) { + + String primaryServiceName = + gerritNetwork.getSpec().getPrimaryGerrit().getName() + + ":" + + gerritNetwork.getSpec().getPrimaryGerrit().getHttpPort(); + + // Send all write traffic (non git fetch/clone traffic) to the Primary. + // Emissary evaluates more constrained Mappings first. + Mapping mapping = + new MappingBuilder() + .withNewMetadataLike( + getCommonMetadata( + gerritNetwork, GERRIT_MAPPING_PRIMARY, this.getClass().getSimpleName())) + .endMetadata() + .withNewSpecLike(getCommonSpec(gerritNetwork, primaryServiceName)) + .endSpec() + .build(); + + return mapping; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPrimaryDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPrimaryDiscriminator.java new file mode 100644 index 000000000..abb7b9089 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingPrimaryDiscriminator.java @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingPrimary.GERRIT_MAPPING_PRIMARY; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.Optional; + +public class GerritClusterMappingPrimaryDiscriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish( + Class resource, GerritNetwork network, Context context) { + InformerEventSource ies = + (InformerEventSource) + context.eventSourceRetriever().getResourceEventSourceFor(Mapping.class); + return ies.get(new ResourceID(GERRIT_MAPPING_PRIMARY, network.getMetadata().getNamespace())); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiver.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiver.java new file mode 100644 index 000000000..a35ac1ffb --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiver.java @@ -0,0 +1,58 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.Constants.PROJECTS_URL_PATTERN; +import static com.google.gerrit.k8s.operator.network.Constants.RECEIVE_PACK_URL_PATTERN; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import com.google.gerrit.k8s.operator.receiver.dependent.ReceiverService; +import io.getambassador.v2.Mapping; +import io.getambassador.v2.MappingBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent(resourceDiscriminator = GerritClusterMappingReceiverDiscriminator.class) +public class GerritClusterMappingReceiver extends AbstractAmbassadorDependentResource + implements MappingDependentResourceInterface { + + public static final String GERRIT_MAPPING_RECEIVER = "gerrit-mapping-receiver"; + + public GerritClusterMappingReceiver() { + super(Mapping.class); + } + + @Override + public Mapping desired(GerritNetwork gerritNetwork, Context context) { + + String receiverServiceName = + ReceiverService.getName(gerritNetwork.getSpec().getReceiver().getName()) + + ":" + + gerritNetwork.getSpec().getReceiver().getHttpPort(); + + Mapping mapping = + new MappingBuilder() + .withNewMetadataLike( + getCommonMetadata( + gerritNetwork, GERRIT_MAPPING_RECEIVER, this.getClass().getSimpleName())) + .endMetadata() + .withNewSpecLike(getCommonSpec(gerritNetwork, receiverServiceName)) + .withPrefix(PROJECTS_URL_PATTERN + "|" + RECEIVE_PACK_URL_PATTERN) + .withPrefixRegex(true) + .endSpec() + .build(); + return mapping; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverDiscriminator.java new file mode 100644 index 000000000..f5aa07fed --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverDiscriminator.java @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingReceiver.GERRIT_MAPPING_RECEIVER; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.Optional; + +public class GerritClusterMappingReceiverDiscriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish( + Class resource, GerritNetwork network, Context context) { + InformerEventSource ies = + (InformerEventSource) + context.eventSourceRetriever().getResourceEventSourceFor(Mapping.class); + return ies.get(new ResourceID(GERRIT_MAPPING_RECEIVER, network.getMetadata().getNamespace())); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverGET.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverGET.java new file mode 100644 index 000000000..7f5948823 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverGET.java @@ -0,0 +1,67 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.Constants.INFO_REFS_PATTERN; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import com.google.gerrit.k8s.operator.receiver.dependent.ReceiverService; +import io.getambassador.v2.Mapping; +import io.getambassador.v2.MappingBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import java.util.HashMap; + +@KubernetesDependent(resourceDiscriminator = GerritClusterMappingReceiverGETDiscriminator.class) +public class GerritClusterMappingReceiverGET extends AbstractAmbassadorDependentResource + implements MappingDependentResourceInterface { + + public static final String GERRIT_MAPPING_RECEIVER_GET = "gerrit-mapping-receiver-get"; + + public GerritClusterMappingReceiverGET() { + super(Mapping.class); + } + + @Override + public Mapping desired(GerritNetwork gerritNetwork, Context context) { + + String receiverServiceName = + ReceiverService.getName(gerritNetwork.getSpec().getReceiver().getName()) + + ":" + + gerritNetwork.getSpec().getReceiver().getHttpPort(); + + Mapping mapping = + new MappingBuilder() + .withNewMetadataLike( + getCommonMetadata( + gerritNetwork, GERRIT_MAPPING_RECEIVER_GET, this.getClass().getSimpleName())) + .endMetadata() + .withNewSpecLike(getCommonSpec(gerritNetwork, receiverServiceName)) + .withNewV2QueryParameters() + .withAdditionalProperties( + new HashMap() { + { + put("service", "git-receive-pack"); + } + }) + .endV2QueryParameters() + .withMethod("GET") + .withPrefix(INFO_REFS_PATTERN) + .withPrefixRegex(true) + .endSpec() + .build(); + return mapping; + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverGETDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverGETDiscriminator.java new file mode 100644 index 000000000..27b36e85c --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterMappingReceiverGETDiscriminator.java @@ -0,0 +1,38 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.gerrit.k8s.operator.network.ambassador.dependent.GerritClusterMappingReceiverGET.GERRIT_MAPPING_RECEIVER_GET; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import java.util.Optional; + +public class GerritClusterMappingReceiverGETDiscriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish( + Class resource, GerritNetwork network, Context context) { + InformerEventSource ies = + (InformerEventSource) + context.eventSourceRetriever().getResourceEventSourceFor(Mapping.class); + return ies.get( + new ResourceID(GERRIT_MAPPING_RECEIVER_GET, network.getMetadata().getNamespace())); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/LoadBalanceCondition.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/LoadBalanceCondition.java new file mode 100644 index 000000000..6f9b3b1d6 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/LoadBalanceCondition.java @@ -0,0 +1,35 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; + +public class LoadBalanceCondition implements Condition { + + @Override + public boolean isMet( + DependentResource dependentResource, + GerritNetwork gerritNetwork, + Context context) { + + return gerritNetwork.getSpec().getIngress().isEnabled() + && gerritNetwork.hasPrimaryGerrit() + && gerritNetwork.hasGerritReplica(); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/MappingDependentResourceInterface.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/MappingDependentResourceInterface.java new file mode 100644 index 000000000..097e97242 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/MappingDependentResourceInterface.java @@ -0,0 +1,23 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +public interface MappingDependentResourceInterface { + public Mapping desired(GerritNetwork gerritNetwork, Context context); +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/ReceiverMappingCondition.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/ReceiverMappingCondition.java new file mode 100644 index 000000000..b9f88fd1f --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/ReceiverMappingCondition.java @@ -0,0 +1,33 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; + +public class ReceiverMappingCondition implements Condition { + + @Override + public boolean isMet( + DependentResource dependentResource, + GerritNetwork gerritNetwork, + Context context) { + + return gerritNetwork.getSpec().getIngress().isEnabled() && gerritNetwork.hasReceiver(); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/SingleMappingCondition.java b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/SingleMappingCondition.java new file mode 100644 index 000000000..07606c596 --- /dev/null +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/SingleMappingCondition.java @@ -0,0 +1,34 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; + +public class SingleMappingCondition implements Condition { + + @Override + public boolean isMet( + DependentResource dependentResource, + GerritNetwork gerritNetwork, + Context context) { + + return gerritNetwork.getSpec().getIngress().isEnabled() + && (gerritNetwork.hasPrimaryGerrit() ^ gerritNetwork.hasGerritReplica()); + } +} diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java index 5c4332a38..eb932eb98 100644 --- a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java +++ b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java @@ -33,7 +33,7 @@ @Buildable( editableEnabled = false, validationEnabled = false, - generateBuilderPackage = true, + generateBuilderPackage = false, lazyCollectionInitEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder") public class ReceiverTemplate implements KubernetesResource { diff --git a/operator/src/main/resources/crd/emissary-crds.yaml b/operator/src/main/resources/crd/emissary-crds.yaml new file mode 100644 index 000000000..5ead04e27 --- /dev/null +++ b/operator/src/main/resources/crd/emissary-crds.yaml @@ -0,0 +1,1952 @@ +# This file is downloaded from the Emissary repository on GitHub: +# https://github.com/emissary-ingress/emissary/blob/master/manifests/emissary/emissary-crds.yaml.in +# +# Several modifications have been manually made: +# 1. Only the `Mapping` and `TLSContext` CRDs have been kept from the source file. The source file +# defines many CRDs that are not required by this operator project so the unnecessary CRDs have +# been deleted. +# 2. `v2ExplicitTLS` field has been removed from the Mapping CRD `v3alpha1` version. This is because +# the "crd-to-java" generator plugin we use has a bug (https://github.com/fabric8io/kubernetes-client/issues/5457) +# while converting enum types and the bug is triggered by the `v2ExplicitTLS` field. This field +# may be added back in once we upgrade our fabric8 version to 6.8.x, where this bug is resolved. +# 3. `ambassador_id` property is added to `Mapping` and `TLSContext` CRD version `v2`, by copying it +# over from `v3`. +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + labels: + app.kubernetes.io/instance: emissary-apiext + app.kubernetes.io/managed-by: kubectl_apply_-f_emissary-apiext.yaml + app.kubernetes.io/name: emissary-apiext + app.kubernetes.io/part-of: emissary-apiext + name: mappings.getambassador.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: emissary-apiext + namespace: emissary-system + conversionReviewVersions: + - v1 + group: getambassador.io + names: + categories: + - ambassador-crds + kind: Mapping + listKind: MappingList + plural: mappings + singular: mapping + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.host + name: Source Host + type: string + - jsonPath: .spec.prefix + name: Source Prefix + type: string + - jsonPath: .spec.service + name: Dest Service + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.reason + name: Reason + type: string + name: v1 + schema: + openAPIV3Schema: + description: Mapping is the Schema for the mappings API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MappingSpec defines the desired state of Mapping + properties: + add_linkerd_headers: + type: boolean + add_request_headers: + type: object + x-kubernetes-preserve-unknown-fields: true + add_response_headers: + type: object + x-kubernetes-preserve-unknown-fields: true + allow_upgrade: + description: "A case-insensitive list of the non-HTTP protocols to + allow \"upgrading\" to from HTTP via the \"Connection: upgrade\" + mechanism[1]. After the upgrade, Ambassador does not interpret + the traffic, and behaves similarly to how it does for TCPMappings. + \n [1]: https://tools.ietf.org/html/rfc7230#section-6.7 \n For example, + if your upstream service supports WebSockets, you would write \n + allow_upgrade: - websocket \n Or if your upstream service supports + upgrading from HTTP to SPDY (as the Kubernetes apiserver does for + `kubectl exec` functionality), you would write \n allow_upgrade: + - spdy/3.1" + items: + type: string + type: array + auth_context_extensions: + additionalProperties: + type: string + type: object + auto_host_rewrite: + type: boolean + bypass_auth: + type: boolean + bypass_error_response_overrides: + description: If true, bypasses any `error_response_overrides` set + on the Ambassador module. + type: boolean + case_sensitive: + type: boolean + circuit_breakers: + items: + properties: + max_connections: + type: integer + max_pending_requests: + type: integer + max_requests: + type: integer + max_retries: + type: integer + priority: + enum: + - default + - high + type: string + type: object + type: array + cluster_idle_timeout_ms: + type: integer + cluster_max_connection_lifetime_ms: + type: integer + cluster_tag: + type: string + connect_timeout_ms: + type: integer + cors: + properties: + credentials: + type: boolean + max_age: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + dns_type: + type: string + docs: + description: DocsInfo provides some extra information about the docs + for the Mapping (used by the Dev Portal) + properties: + display_name: + type: string + ignored: + type: boolean + path: + type: string + timeout_ms: + type: integer + url: + type: string + type: object + enable_ipv4: + type: boolean + enable_ipv6: + type: boolean + envoy_override: + type: object + x-kubernetes-preserve-unknown-fields: true + error_response_overrides: + description: Error response overrides for this Mapping. Replaces all + of the `error_response_overrides` set on the Ambassador module, + if any. + items: + description: A response rewrite for an HTTP error response + properties: + body: + description: The new response body + properties: + content_type: + description: The content type to set on the error response + body when using text_format or text_format_source. Defaults + to 'text/plain'. + type: string + json_format: + additionalProperties: + type: string + description: 'A JSON response with content-type: application/json. + The values can contain format text like in text_format.' + type: object + text_format: + description: A format string representing a text response + body. Content-Type can be set using the `content_type` + field below. + type: string + text_format_source: + description: A format string sourced from a file on the + Ambassador container. Useful for larger response bodies + that should not be placed inline in configuration. + properties: + filename: + description: The name of a file on the Ambassador pod + that contains a format text string. + type: string + type: object + type: object + on_status_code: + description: The status code to match on -- not a pointer because + it's required. + maximum: 599 + minimum: 400 + type: integer + required: + - body + - on_status_code + type: object + minItems: 1 + type: array + grpc: + type: boolean + headers: + type: object + x-kubernetes-preserve-unknown-fields: true + host: + type: string + host_redirect: + type: boolean + host_regex: + type: boolean + host_rewrite: + type: string + idle_timeout_ms: + type: integer + keepalive: + properties: + idle_time: + type: integer + interval: + type: integer + probes: + type: integer + type: object + labels: + additionalProperties: + description: A MappingLabelGroupsArray is an array of MappingLabelGroups. + I know, complex. + items: + description: 'A MappingLabelGroup is a single element of a MappingLabelGroupsArray: + a second map, where the key is a human-readable name that identifies + the group.' + maxProperties: 1 + minProperties: 1 + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + description: A DomainMap is the overall Mapping.spec.Labels type. + It maps domains (kind of like namespaces for Mapping labels) to + arrays of label groups. + type: object + load_balancer: + properties: + cookie: + properties: + name: + type: string + path: + type: string + ttl: + type: string + required: + - name + type: object + header: + type: string + policy: + enum: + - round_robin + - ring_hash + - maglev + - least_request + type: string + source_ip: + type: boolean + required: + - policy + type: object + method: + type: string + method_regex: + type: boolean + modules: + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + outlier_detection: + type: string + path_redirect: + description: Path replacement to use when generating an HTTP redirect. + Used with `host_redirect`. + type: string + precedence: + type: integer + prefix: + type: string + prefix_exact: + type: boolean + prefix_redirect: + description: Prefix rewrite to use when generating an HTTP redirect. + Used with `host_redirect`. + type: string + prefix_regex: + type: boolean + priority: + type: string + query_parameters: + type: object + x-kubernetes-preserve-unknown-fields: true + redirect_response_code: + description: The response code to use when generating an HTTP redirect. + Defaults to 301. Used with `host_redirect`. + enum: + - 301 + - 302 + - 303 + - 307 + - 308 + type: integer + regex_headers: + additionalProperties: + type: string + type: object + regex_query_parameters: + additionalProperties: + type: string + type: object + regex_redirect: + description: Prefix regex rewrite to use when generating an HTTP redirect. + Used with `host_redirect`. + properties: + pattern: + type: string + substitution: + type: string + type: object + regex_rewrite: + properties: + pattern: + type: string + substitution: + type: string + type: object + resolver: + type: string + respect_dns_ttl: + type: boolean + retry_policy: + properties: + num_retries: + type: integer + per_try_timeout: + type: string + retry_on: + enum: + - 5xx + - gateway-error + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + type: string + type: object + rewrite: + type: string + service: + type: string + shadow: + type: boolean + timeout_ms: + description: The timeout for requests that use this Mapping. Overrides + `cluster_request_timeout_ms` set on the Ambassador Module, if it + exists. + type: integer + use_websocket: + description: 'use_websocket is deprecated, and is equivlaent to setting + `allow_upgrade: ["websocket"]`' + type: boolean + v3StatsName: + type: string + v3health_checks: + items: + description: HealthCheck specifies settings for performing active + health checking on upstreams + properties: + health_check: + description: Configuration for where the healthcheck request + should be made to + maxProperties: 1 + minProperties: 1 + properties: + grpc: + description: HealthCheck for gRPC upstreams. Only one of + grpc_health_check or http_health_check may be specified + properties: + authority: + description: The value of the :authority header in the + gRPC health check request. If left empty the upstream + name will be used. + type: string + upstream_name: + description: The upstream name parameter which will + be sent to gRPC service in the health check message + type: string + required: + - upstream_name + type: object + http: + description: HealthCheck for HTTP upstreams. Only one of + http_health_check or grpc_health_check may be specified + properties: + add_request_headers: + additionalProperties: + properties: + append: + type: boolean + v2Representation: + enum: + - "" + - string + - "null" + type: string + value: + type: string + type: object + type: object + expected_statuses: + items: + description: A range of response statuses from Start + to End inclusive + properties: + max: + description: End of the statuses to include. Must + be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + min: + description: Start of the statuses to include. + Must be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + required: + - max + - min + type: object + type: array + hostname: + type: string + path: + type: string + remove_request_headers: + items: + type: string + type: array + required: + - path + type: object + type: object + healthy_threshold: + description: Number of expected responses for the upstream to + be considered healthy. Defaults to 1. + type: integer + interval: + description: Interval between health checks. Defaults to every + 5 seconds. + type: string + timeout: + description: Timeout for connecting to the health checking endpoint. + Defaults to 3 seconds. + type: string + unhealthy_threshold: + description: Number of non-expected responses for the upstream + to be considered unhealthy. A single 503 will mark the upstream + as unhealthy regardless of the threshold. Defaults to 2. + type: integer + required: + - health_check + type: object + minItems: 1 + type: array + weight: + type: integer + required: + - prefix + - service + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: MappingStatus defines the observed state of Mapping + properties: + reason: + type: string + state: + enum: + - "" + - Inactive + - Running + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.host + name: Source Host + type: string + - jsonPath: .spec.prefix + name: Source Prefix + type: string + - jsonPath: .spec.service + name: Dest Service + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.reason + name: Reason + type: string + name: v2 + schema: + openAPIV3Schema: + description: Mapping is the Schema for the mappings API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MappingSpec defines the desired state of Mapping + properties: + add_linkerd_headers: + type: boolean + add_request_headers: + type: object + x-kubernetes-preserve-unknown-fields: true + add_response_headers: + type: object + x-kubernetes-preserve-unknown-fields: true + allow_upgrade: + description: "A case-insensitive list of the non-HTTP protocols to + allow \"upgrading\" to from HTTP via the \"Connection: upgrade\" + mechanism[1]. After the upgrade, Ambassador does not interpret + the traffic, and behaves similarly to how it does for TCPMappings. + \n [1]: https://tools.ietf.org/html/rfc7230#section-6.7 \n For example, + if your upstream service supports WebSockets, you would write \n + allow_upgrade: - websocket \n Or if your upstream service supports + upgrading from HTTP to SPDY (as the Kubernetes apiserver does for + `kubectl exec` functionality), you would write \n allow_upgrade: + - spdy/3.1" + items: + type: string + type: array + # [operator] added manually by coping over from v3alpha1 + ambassador_id: + description: "AmbassadorID declares which Ambassador instances should + pay attention to this resource. If no value is provided, the default + is: \n ambassador_id: - \"default\"" + items: + type: string + type: array + auth_context_extensions: + additionalProperties: + type: string + type: object + auto_host_rewrite: + type: boolean + bypass_auth: + type: boolean + bypass_error_response_overrides: + description: If true, bypasses any `error_response_overrides` set + on the Ambassador module. + type: boolean + case_sensitive: + type: boolean + circuit_breakers: + items: + properties: + max_connections: + type: integer + max_pending_requests: + type: integer + max_requests: + type: integer + max_retries: + type: integer + priority: + enum: + - default + - high + type: string + type: object + type: array + cluster_idle_timeout_ms: + type: integer + cluster_max_connection_lifetime_ms: + type: integer + cluster_tag: + type: string + connect_timeout_ms: + type: integer + cors: + properties: + credentials: + type: boolean + max_age: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + dns_type: + type: string + docs: + description: DocsInfo provides some extra information about the docs + for the Mapping (used by the Dev Portal) + properties: + display_name: + type: string + ignored: + type: boolean + path: + type: string + timeout_ms: + type: integer + url: + type: string + type: object + enable_ipv4: + type: boolean + enable_ipv6: + type: boolean + envoy_override: + type: object + x-kubernetes-preserve-unknown-fields: true + error_response_overrides: + description: Error response overrides for this Mapping. Replaces all + of the `error_response_overrides` set on the Ambassador module, + if any. + items: + description: A response rewrite for an HTTP error response + properties: + body: + description: The new response body + properties: + content_type: + description: The content type to set on the error response + body when using text_format or text_format_source. Defaults + to 'text/plain'. + type: string + json_format: + additionalProperties: + type: string + description: 'A JSON response with content-type: application/json. + The values can contain format text like in text_format.' + type: object + text_format: + description: A format string representing a text response + body. Content-Type can be set using the `content_type` + field below. + type: string + text_format_source: + description: A format string sourced from a file on the + Ambassador container. Useful for larger response bodies + that should not be placed inline in configuration. + properties: + filename: + description: The name of a file on the Ambassador pod + that contains a format text string. + type: string + type: object + type: object + on_status_code: + description: The status code to match on -- not a pointer because + it's required. + maximum: 599 + minimum: 400 + type: integer + required: + - body + - on_status_code + type: object + minItems: 1 + type: array + grpc: + type: boolean + headers: + type: object + x-kubernetes-preserve-unknown-fields: true + host: + type: string + host_redirect: + type: boolean + host_regex: + type: boolean + host_rewrite: + type: string + idle_timeout_ms: + type: integer + keepalive: + properties: + idle_time: + type: integer + interval: + type: integer + probes: + type: integer + type: object + labels: + additionalProperties: + description: A MappingLabelGroupsArray is an array of MappingLabelGroups. + I know, complex. + items: + description: 'A MappingLabelGroup is a single element of a MappingLabelGroupsArray: + a second map, where the key is a human-readable name that identifies + the group.' + maxProperties: 1 + minProperties: 1 + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + description: A DomainMap is the overall Mapping.spec.Labels type. + It maps domains (kind of like namespaces for Mapping labels) to + arrays of label groups. + type: object + load_balancer: + properties: + cookie: + properties: + name: + type: string + path: + type: string + ttl: + type: string + required: + - name + type: object + header: + type: string + policy: + enum: + - round_robin + - ring_hash + - maglev + - least_request + type: string + source_ip: + type: boolean + required: + - policy + type: object + method: + type: string + method_regex: + type: boolean + modules: + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + outlier_detection: + type: string + path_redirect: + description: Path replacement to use when generating an HTTP redirect. + Used with `host_redirect`. + type: string + precedence: + type: integer + prefix: + type: string + prefix_exact: + type: boolean + prefix_redirect: + description: Prefix rewrite to use when generating an HTTP redirect. + Used with `host_redirect`. + type: string + prefix_regex: + type: boolean + priority: + type: string + query_parameters: + type: object + x-kubernetes-preserve-unknown-fields: true + redirect_response_code: + description: The response code to use when generating an HTTP redirect. + Defaults to 301. Used with `host_redirect`. + enum: + - 301 + - 302 + - 303 + - 307 + - 308 + type: integer + regex_headers: + additionalProperties: + type: string + type: object + regex_query_parameters: + additionalProperties: + type: string + type: object + regex_redirect: + description: Prefix regex rewrite to use when generating an HTTP redirect. + Used with `host_redirect`. + properties: + pattern: + type: string + substitution: + type: string + type: object + regex_rewrite: + properties: + pattern: + type: string + substitution: + type: string + type: object + resolver: + type: string + respect_dns_ttl: + type: boolean + retry_policy: + properties: + num_retries: + type: integer + per_try_timeout: + type: string + retry_on: + enum: + - 5xx + - gateway-error + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + type: string + type: object + rewrite: + type: string + service: + type: string + shadow: + type: boolean + timeout_ms: + description: The timeout for requests that use this Mapping. Overrides + `cluster_request_timeout_ms` set on the Ambassador Module, if it + exists. + type: integer + use_websocket: + description: 'use_websocket is deprecated, and is equivlaent to setting + `allow_upgrade: ["websocket"]`' + type: boolean + v3StatsName: + type: string + v3health_checks: + items: + description: HealthCheck specifies settings for performing active + health checking on upstreams + properties: + health_check: + description: Configuration for where the healthcheck request + should be made to + maxProperties: 1 + minProperties: 1 + properties: + grpc: + description: HealthCheck for gRPC upstreams. Only one of + grpc_health_check or http_health_check may be specified + properties: + authority: + description: The value of the :authority header in the + gRPC health check request. If left empty the upstream + name will be used. + type: string + upstream_name: + description: The upstream name parameter which will + be sent to gRPC service in the health check message + type: string + required: + - upstream_name + type: object + http: + description: HealthCheck for HTTP upstreams. Only one of + http_health_check or grpc_health_check may be specified + properties: + add_request_headers: + additionalProperties: + properties: + append: + type: boolean + v2Representation: + enum: + - "" + - string + - "null" + type: string + value: + type: string + type: object + type: object + expected_statuses: + items: + description: A range of response statuses from Start + to End inclusive + properties: + max: + description: End of the statuses to include. Must + be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + min: + description: Start of the statuses to include. + Must be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + required: + - max + - min + type: object + type: array + hostname: + type: string + path: + type: string + remove_request_headers: + items: + type: string + type: array + required: + - path + type: object + type: object + healthy_threshold: + description: Number of expected responses for the upstream to + be considered healthy. Defaults to 1. + type: integer + interval: + description: Interval between health checks. Defaults to every + 5 seconds. + type: string + timeout: + description: Timeout for connecting to the health checking endpoint. + Defaults to 3 seconds. + type: string + unhealthy_threshold: + description: Number of non-expected responses for the upstream + to be considered unhealthy. A single 503 will mark the upstream + as unhealthy regardless of the threshold. Defaults to 2. + type: integer + required: + - health_check + type: object + minItems: 1 + type: array + weight: + type: integer + required: + - prefix + - service + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: MappingStatus defines the observed state of Mapping + properties: + reason: + type: string + state: + enum: + - "" + - Inactive + - Running + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.host + name: Source Host + type: string + - jsonPath: .spec.prefix + name: Source Prefix + type: string + - jsonPath: .spec.service + name: Dest Service + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.reason + name: Reason + type: string + name: v3alpha1 + schema: + openAPIV3Schema: + description: Mapping is the Schema for the mappings API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MappingSpec defines the desired state of Mapping + properties: + add_linkerd_headers: + type: boolean + add_request_headers: + additionalProperties: + properties: + append: + type: boolean + v2Representation: + enum: + - "" + - string + - "null" + type: string + value: + type: string + type: object + type: object + add_response_headers: + additionalProperties: + properties: + append: + type: boolean + v2Representation: + enum: + - "" + - string + - "null" + type: string + value: + type: string + type: object + type: object + allow_upgrade: + description: "A case-insensitive list of the non-HTTP protocols to + allow \"upgrading\" to from HTTP via the \"Connection: upgrade\" + mechanism[1]. After the upgrade, Ambassador does not interpret + the traffic, and behaves similarly to how it does for TCPMappings. + \n [1]: https://tools.ietf.org/html/rfc7230#section-6.7 \n For example, + if your upstream service supports WebSockets, you would write \n + allow_upgrade: - websocket \n Or if your upstream service supports + upgrading from HTTP to SPDY (as the Kubernetes apiserver does for + `kubectl exec` functionality), you would write \n allow_upgrade: + - spdy/3.1" + items: + type: string + type: array + ambassador_id: + description: "AmbassadorID declares which Ambassador instances should + pay attention to this resource. If no value is provided, the default + is: \n ambassador_id: - \"default\"" + items: + type: string + type: array + auth_context_extensions: + additionalProperties: + type: string + type: object + auto_host_rewrite: + type: boolean + bypass_auth: + type: boolean + bypass_error_response_overrides: + description: If true, bypasses any `error_response_overrides` set + on the Ambassador module. + type: boolean + case_sensitive: + type: boolean + circuit_breakers: + items: + properties: + max_connections: + type: integer + max_pending_requests: + type: integer + max_requests: + type: integer + max_retries: + type: integer + priority: + enum: + - default + - high + type: string + type: object + type: array + cluster_idle_timeout_ms: + type: integer + cluster_max_connection_lifetime_ms: + type: integer + cluster_tag: + type: string + connect_timeout_ms: + type: integer + cors: + properties: + credentials: + type: boolean + exposed_headers: + items: + type: string + type: array + headers: + items: + type: string + type: array + max_age: + type: string + methods: + items: + type: string + type: array + origins: + items: + type: string + type: array + v2CommaSeparatedOrigins: + type: boolean + type: object + dns_type: + type: string + docs: + description: DocsInfo provides some extra information about the docs + for the Mapping. Docs is used by both the agent and the DevPortal. + properties: + display_name: + type: string + ignored: + type: boolean + path: + type: string + timeout_ms: + type: integer + url: + type: string + type: object + enable_ipv4: + type: boolean + enable_ipv6: + type: boolean + envoy_override: + type: object + x-kubernetes-preserve-unknown-fields: true + error_response_overrides: + description: Error response overrides for this Mapping. Replaces all + of the `error_response_overrides` set on the Ambassador module, + if any. + items: + description: A response rewrite for an HTTP error response + properties: + body: + description: The new response body + properties: + content_type: + description: The content type to set on the error response + body when using text_format or text_format_source. Defaults + to 'text/plain'. + type: string + json_format: + additionalProperties: + type: string + description: 'A JSON response with content-type: application/json. + The values can contain format text like in text_format.' + type: object + text_format: + description: A format string representing a text response + body. Content-Type can be set using the `content_type` + field below. + type: string + text_format_source: + description: A format string sourced from a file on the + Ambassador container. Useful for larger response bodies + that should not be placed inline in configuration. + properties: + filename: + description: The name of a file on the Ambassador pod + that contains a format text string. + type: string + type: object + type: object + on_status_code: + description: The status code to match on -- not a pointer because + it's required. + maximum: 599 + minimum: 400 + type: integer + required: + - body + - on_status_code + type: object + minItems: 1 + type: array + grpc: + type: boolean + headers: + additionalProperties: + type: string + type: object + health_checks: + items: + description: HealthCheck specifies settings for performing active + health checking on upstreams + properties: + health_check: + description: Configuration for where the healthcheck request + should be made to + maxProperties: 1 + minProperties: 1 + properties: + grpc: + description: HealthCheck for gRPC upstreams. Only one of + grpc_health_check or http_health_check may be specified + properties: + authority: + description: The value of the :authority header in the + gRPC health check request. If left empty the upstream + name will be used. + type: string + upstream_name: + description: The upstream name parameter which will + be sent to gRPC service in the health check message + type: string + required: + - upstream_name + type: object + http: + description: HealthCheck for HTTP upstreams. Only one of + http_health_check or grpc_health_check may be specified + properties: + add_request_headers: + additionalProperties: + properties: + append: + type: boolean + v2Representation: + enum: + - "" + - string + - "null" + type: string + value: + type: string + type: object + type: object + expected_statuses: + items: + description: A range of response statuses from Start + to End inclusive + properties: + max: + description: End of the statuses to include. Must + be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + min: + description: Start of the statuses to include. + Must be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + required: + - max + - min + type: object + type: array + hostname: + type: string + path: + type: string + remove_request_headers: + items: + type: string + type: array + required: + - path + type: object + type: object + healthy_threshold: + description: Number of expected responses for the upstream to + be considered healthy. Defaults to 1. + type: integer + interval: + description: Interval between health checks. Defaults to every + 5 seconds. + type: string + timeout: + description: Timeout for connecting to the health checking endpoint. + Defaults to 3 seconds. + type: string + unhealthy_threshold: + description: Number of non-expected responses for the upstream + to be considered unhealthy. A single 503 will mark the upstream + as unhealthy regardless of the threshold. Defaults to 2. + type: integer + required: + - health_check + type: object + minItems: 1 + type: array + host: + description: "Exact match for the hostname of a request if HostRegex + is false; regex match for the hostname if HostRegex is true. \n + Host specifies both a match for the ':authority' header of a request, + as well as a match criterion for Host CRDs: a Mapping that specifies + Host will not associate with a Host that doesn't have a matching + Hostname. \n If both Host and Hostname are set, an error is logged, + Host is ignored, and Hostname is used. \n DEPRECATED: Host is either + an exact match or a regex, depending on HostRegex. Use HostName + instead." + type: string + host_redirect: + type: boolean + host_regex: + description: 'DEPRECATED: Host is either an exact match or a regex, + depending on HostRegex. Use HostName instead.' + type: boolean + host_rewrite: + type: string + hostname: + description: "Hostname is a DNS glob specifying the hosts to which + this Mapping applies. \n Hostname specifies both a match for the + ':authority' header of a request, as well as a match criterion for + Host CRDs: a Mapping that specifies Hostname will not associate + with a Host that doesn't have a matching Hostname. \n If both Host + and Hostname are set, an error is logged, Host is ignored, and Hostname + is used." + type: string + idle_timeout_ms: + type: integer + keepalive: + properties: + idle_time: + type: integer + interval: + type: integer + probes: + type: integer + type: object + labels: + additionalProperties: + description: A MappingLabelGroupsArray is an array of MappingLabelGroups. + I know, complex. + items: + additionalProperties: + description: 'A MappingLabelsArray is the value in the MappingLabelGroup: + an array of label specifiers.' + items: + description: "A MappingLabelSpecifier (finally!) defines a + single label. \n This mimics envoy/config/route/v3/route_components.proto:RateLimit:Action:action_specifier." + maxProperties: 1 + minProperties: 1 + properties: + destination_cluster: + description: Sets the label "destination_cluster=«Envoy + destination cluster name»". + properties: + key: + enum: + - destination_cluster + type: string + required: + - key + type: object + generic_key: + description: Sets the label "«key»=«value»" (where by + default «key» is "generic_key"). + properties: + key: + description: The default is "generic_key". + type: string + v2Shorthand: + type: boolean + value: + type: string + required: + - value + type: object + remote_address: + description: Sets the label "remote_address=«IP address + of the client»". + properties: + key: + enum: + - remote_address + type: string + required: + - key + type: object + request_headers: + description: If the «header_name» header is set, then + set the label "«key»=«Value of the «header_name» header»"; + otherwise skip applying this label group. + properties: + header_name: + type: string + key: + type: string + omit_if_not_present: + type: boolean + required: + - header_name + - key + type: object + source_cluster: + description: Sets the label "source_cluster=«Envoy source + cluster name»". + properties: + key: + enum: + - source_cluster + type: string + required: + - key + type: object + type: object + type: array + description: 'A MappingLabelGroup is a single element of a MappingLabelGroupsArray: + a second map, where the key is a human-readable name that identifies + the group.' + maxProperties: 1 + minProperties: 1 + type: object + type: array + description: A DomainMap is the overall Mapping.spec.Labels type. + It maps domains (kind of like namespaces for Mapping labels) to + arrays of label groups. + type: object + load_balancer: + properties: + cookie: + properties: + name: + type: string + path: + type: string + ttl: + type: string + required: + - name + type: object + header: + type: string + policy: + enum: + - round_robin + - ring_hash + - maglev + - least_request + type: string + source_ip: + type: boolean + required: + - policy + type: object + method: + type: string + method_regex: + type: boolean + modules: + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + outlier_detection: + type: string + path_redirect: + description: Path replacement to use when generating an HTTP redirect. + Used with `host_redirect`. + type: string + precedence: + type: integer + prefix: + type: string + prefix_exact: + type: boolean + prefix_redirect: + description: Prefix rewrite to use when generating an HTTP redirect. + Used with `host_redirect`. + type: string + prefix_regex: + type: boolean + priority: + type: string + query_parameters: + additionalProperties: + type: string + type: object + redirect_response_code: + description: The response code to use when generating an HTTP redirect. + Defaults to 301. Used with `host_redirect`. + enum: + - 301 + - 302 + - 303 + - 307 + - 308 + type: integer + regex_headers: + additionalProperties: + type: string + type: object + regex_query_parameters: + additionalProperties: + type: string + type: object + regex_redirect: + description: Prefix regex rewrite to use when generating an HTTP redirect. + Used with `host_redirect`. + properties: + pattern: + type: string + substitution: + type: string + type: object + regex_rewrite: + properties: + pattern: + type: string + substitution: + type: string + type: object + remove_request_headers: + items: + type: string + type: array + remove_response_headers: + items: + type: string + type: array + resolver: + type: string + respect_dns_ttl: + type: boolean + retry_policy: + properties: + num_retries: + type: integer + per_try_timeout: + type: string + retry_on: + enum: + - 5xx + - gateway-error + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + type: string + type: object + rewrite: + type: string + service: + type: string + shadow: + type: boolean + stats_name: + type: string + timeout_ms: + description: The timeout for requests that use this Mapping. Overrides + `cluster_request_timeout_ms` set on the Ambassador Module, if it + exists. + type: integer + tls: + type: string + use_websocket: + description: 'use_websocket is deprecated, and is equivlaent to setting + `allow_upgrade: ["websocket"]`' + type: boolean + v2BoolHeaders: + items: + type: string + type: array + v2BoolQueryParameters: + items: + type: string + type: array + # TODO: uncomment when [bug](https://github.com/fabric8io/kubernetes-client/issues/5457) is resolved + # v2ExplicitTLS: + # description: V2ExplicitTLS controls some vanity/stylistic elements + # when converting from v3alpha1 to v2. The values in an V2ExplicitTLS + # should not in any way affect the runtime operation of Emissary; + # except that it may affect internal names in the Envoy config, which + # may in turn affect stats names. But it should not affect any end-user + # observable behavior. + # properties: + # serviceScheme: + # description: "ServiceScheme specifies how to spell and capitalize + # the scheme-part of the service URL. \n Acceptable values are + # \"http://\" (case-insensitive), \"https://\" (case-insensitive), + # or \"\". The value is used if it agrees with whether or not + # this resource enables TLS origination, or if something else + # in the resource overrides the scheme." + # pattern: ^([hH][tT][tT][pP][sS]?://)?$ + # type: string + # tls: + # description: "TLS controls whether and how to represent the \"tls\" + # field when its value could be implied by the \"service\" field. + # \ In v2, there were a lot of different ways to spell an \"empty\" + # value, and this field specifies which way to spell it (and will + # therefore only be used if the value will indeed be empty). \n + # | Value | Representation | Meaning + # of representation | |--------------+---------------------------------------+------------------------------------| + # | \"\" | omit the field | defer + # to service (no TLSContext) | | \"null\" | store an explicit + # \"null\" in the field | defer to service (no TLSContext) | + # | \"string\" | store an empty string in the field | defer + # to service (no TLSContext) | | \"bool:false\" | store a Boolean + # \"false\" in the field | defer to service (no TLSContext) | + # | \"bool:true\" | store a Boolean \"true\" in the field | + # originate TLS (no TLSContext) | \n If the meaning of the + # representation contradicts anything else (if a TLSContext is + # to be used, or in the case of \"bool:true\" if TLS is not to + # be originated), then this field is ignored." + # enum: + # - "" + # - "null" + # - bool:true + # - bool:false + # - string + # type: string + # type: object + weight: + type: integer + required: + - prefix + - service + type: object + status: + description: MappingStatus defines the observed state of Mapping + properties: + reason: + type: string + state: + enum: + - "" + - Inactive + - Running + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + labels: + app.kubernetes.io/instance: emissary-apiext + app.kubernetes.io/managed-by: kubectl_apply_-f_emissary-apiext.yaml + app.kubernetes.io/name: emissary-apiext + app.kubernetes.io/part-of: emissary-apiext + name: tlscontexts.getambassador.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: emissary-apiext + namespace: emissary-system + conversionReviewVersions: + - v1 + group: getambassador.io + names: + categories: + - ambassador-crds + kind: TLSContext + listKind: TLSContextList + plural: tlscontexts + singular: tlscontext + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: TLSContext is the Schema for the tlscontexts API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSContextSpec defines the desired state of TLSContext + properties: + alpn_protocols: + type: string + ca_secret: + type: string + cacert_chain_file: + type: string + cert_chain_file: + type: string + cert_required: + type: boolean + cipher_suites: + items: + type: string + type: array + ecdh_curves: + items: + type: string + type: array + hosts: + items: + type: string + type: array + max_tls_version: + enum: + - v1.0 + - v1.1 + - v1.2 + - v1.3 + type: string + min_tls_version: + enum: + - v1.0 + - v1.1 + - v1.2 + - v1.3 + type: string + private_key_file: + type: string + redirect_cleartext_from: + type: integer + secret: + type: string + secret_namespacing: + type: boolean + sni: + type: string + v3CRLSecret: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + - name: v2 + schema: + openAPIV3Schema: + description: TLSContext is the Schema for the tlscontexts API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSContextSpec defines the desired state of TLSContext + properties: + alpn_protocols: + type: string + # [operator] added manually by coping over from v3alpha1 + ambassador_id: + description: "AmbassadorID declares which Ambassador instances should + pay attention to this resource. If no value is provided, the default + is: \n ambassador_id: - \"default\"" + items: + type: string + type: array + ca_secret: + type: string + cacert_chain_file: + type: string + cert_chain_file: + type: string + cert_required: + type: boolean + cipher_suites: + items: + type: string + type: array + ecdh_curves: + items: + type: string + type: array + hosts: + items: + type: string + type: array + max_tls_version: + enum: + - v1.0 + - v1.1 + - v1.2 + - v1.3 + type: string + min_tls_version: + enum: + - v1.0 + - v1.1 + - v1.2 + - v1.3 + type: string + private_key_file: + type: string + redirect_cleartext_from: + type: integer + secret: + type: string + secret_namespacing: + type: boolean + sni: + type: string + v3CRLSecret: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + - name: v3alpha1 + schema: + openAPIV3Schema: + description: TLSContext is the Schema for the tlscontexts API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSContextSpec defines the desired state of TLSContext + properties: + alpn_protocols: + type: string + ambassador_id: + description: "AmbassadorID declares which Ambassador instances should + pay attention to this resource. If no value is provided, the default + is: \n ambassador_id: - \"default\"" + items: + type: string + type: array + ca_secret: + type: string + cacert_chain_file: + type: string + cert_chain_file: + type: string + cert_required: + type: boolean + cipher_suites: + items: + type: string + type: array + crl_secret: + type: string + ecdh_curves: + items: + type: string + type: array + hosts: + items: + type: string + type: array + max_tls_version: + enum: + - v1.0 + - v1.1 + - v1.2 + - v1.3 + type: string + min_tls_version: + enum: + - v1.0 + - v1.1 + - v1.2 + - v1.3 + type: string + private_key_file: + type: string + redirect_cleartext_from: + type: integer + secret: + type: string + secret_namespacing: + type: boolean + sni: + type: string + type: object + type: object + served: true + storage: false diff --git a/operator/src/test/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterAmbassadorTest.java b/operator/src/test/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterAmbassadorTest.java new file mode 100644 index 000000000..f959eabe6 --- /dev/null +++ b/operator/src/test/java/com/google/gerrit/k8s/operator/network/ambassador/dependent/GerritClusterAmbassadorTest.java @@ -0,0 +1,100 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.k8s.operator.network.ambassador.dependent; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gerrit.k8s.operator.network.model.GerritNetwork; +import io.getambassador.v2.Mapping; +import io.javaoperatorsdk.operator.ReconcilerUtils; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class GerritClusterAmbassadorTest { + + @ParameterizedTest + @MethodSource("provideYamlManifests") + public void expectedGerritClusterAmbassadorComponentsCreated( + String inputFile, Map expectedOutputFileNames) + throws ClassNotFoundException, NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException { + GerritNetwork gerritNetwork = + ReconcilerUtils.loadYaml(GerritNetwork.class, this.getClass(), inputFile); + + for (Map.Entry entry : expectedOutputFileNames.entrySet()) { + String className = entry.getKey(); + String expectedOutputFile = entry.getValue(); + + Class clazz = Class.forName(className); + Object dependentObject = clazz.getDeclaredConstructor(new Class[] {}).newInstance(); + + if (dependentObject instanceof MappingDependentResourceInterface) { + MappingDependentResourceInterface dependent = + (MappingDependentResourceInterface) dependentObject; + Mapping result = dependent.desired(gerritNetwork, null); + Mapping expected = + ReconcilerUtils.loadYaml(Mapping.class, this.getClass(), expectedOutputFile); + assertThat(result.getSpec()).isEqualTo(expected.getSpec()); + } + } + } + + private static Stream provideYamlManifests() { + return Stream.of( + Arguments.of( + "../../gerritnetwork_primary_replica_tls.yaml", + Map.of( + GerritClusterMappingGETReplica.class.getName(), + "mappingGETReplica_primary_replica.yaml", + GerritClusterMappingPOSTReplica.class.getName(), + "mappingPOSTReplica_primary_replica.yaml", + GerritClusterMappingPrimary.class.getName(), "mappingPrimary_primary_replica.yaml" + // TODO: add tls + )), + Arguments.of( + "../../gerritnetwork_primary_replica.yaml", + Map.of( + GerritClusterMappingGETReplica.class.getName(), + "mappingGETReplica_primary_replica.yaml", + GerritClusterMappingPOSTReplica.class.getName(), + "mappingPOSTReplica_primary_replica.yaml", + GerritClusterMappingPrimary.class.getName(), + "mappingPrimary_primary_replica.yaml")), + Arguments.of( + "../../gerritnetwork_primary.yaml", + Map.of(GerritClusterMapping.class.getName(), "mapping_primary.yaml")), + Arguments.of( + "../../gerritnetwork_replica.yaml", + Map.of(GerritClusterMapping.class.getName(), "mapping_replica.yaml")), + Arguments.of( + "../../gerritnetwork_receiver_replica.yaml", + Map.of( + GerritClusterMapping.class.getName(), "mapping_replica.yaml", + GerritClusterMappingReceiver.class.getName(), "mapping_receiver.yaml", + GerritClusterMappingReceiverGET.class.getName(), "mappingGET_receiver.yaml")), + Arguments.of( + "../../gerritnetwork_receiver_replica_tls.yaml", + Map.of( + GerritClusterMapping.class.getName(), "mapping_replica.yaml", + GerritClusterMappingReceiver.class.getName(), "mapping_receiver.yaml", + GerritClusterMappingReceiverGET.class.getName(), "mappingGET_receiver.yaml" + // TODO: add tls + ))); + } +} diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingGETReplica_primary_replica.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingGETReplica_primary_replica.yaml new file mode 100644 index 000000000..f2d81278b --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingGETReplica_primary_replica.yaml @@ -0,0 +1,15 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping-get-replica + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + method: GET + prefix: /.*/info/refs + prefix_regex: true + query_parameters: + service: git-upload-pack + service: replica:48080 \ No newline at end of file diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingGET_receiver.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingGET_receiver.yaml new file mode 100644 index 000000000..c21948613 --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingGET_receiver.yaml @@ -0,0 +1,15 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping-receiver-get + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + method: GET + prefix: /.*/info/refs + prefix_regex: true + query_parameters: + service: git-receive-pack + service: receiver:48081 \ No newline at end of file diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingPOSTReplica_primary_replica.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingPOSTReplica_primary_replica.yaml new file mode 100644 index 000000000..7ebbaefe5 --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingPOSTReplica_primary_replica.yaml @@ -0,0 +1,13 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping-post-replica + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + method: POST + prefix: /.*/git-upload-pack + prefix_regex: true + service: replica:48080 \ No newline at end of file diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingPrimary_primary_replica.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingPrimary_primary_replica.yaml new file mode 100644 index 000000000..e727be040 --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mappingPrimary_primary_replica.yaml @@ -0,0 +1,11 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping-primary + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + prefix: / + service: primary:48080 \ No newline at end of file diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_primary.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_primary.yaml new file mode 100644 index 000000000..1269b1d07 --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_primary.yaml @@ -0,0 +1,11 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + prefix: / + service: primary:48080 \ No newline at end of file diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_receiver.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_receiver.yaml new file mode 100644 index 000000000..51a91cc98 --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_receiver.yaml @@ -0,0 +1,12 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping-receiver + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + prefix: /a/projects/.*|/.*/git-receive-pack + prefix_regex: true + service: receiver:48081 \ No newline at end of file diff --git a/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_replica.yaml b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_replica.yaml new file mode 100644 index 000000000..1080648f4 --- /dev/null +++ b/operator/src/test/resources/com/google/gerrit/k8s/operator/network/ambassador/dependent/mapping_replica.yaml @@ -0,0 +1,11 @@ +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: gerrit-mapping + namespace: gerrit +spec: + bypass_auth: true + rewrite: "" + host: example.com + prefix: / + service: replica:48080 \ No newline at end of file