Skip to content

Commit

Permalink
Crossplane helm (#305)
Browse files Browse the repository at this point in the history
* Adding the crossplane helm release CRD to the list of the urls to be converted to java classes

Signed-off-by: Charles Moulliard <[email protected]>

* Create a release-postgresql.yml to install the postgresql Helm chart. Updating the documentation of crossplane.md

Signed-off-by: Charles Moulliard <[email protected]>

* WIP. Add a methof to generate the crossplane Release

Signed-off-by: Charles Moulliard <[email protected]>

* WIP. Instantiate the different builders needed

Signed-off-by: Charles Moulliard <[email protected]>

* WIP. Top be improved as code looks horrible suing builders

Signed-off-by: Charles Moulliard <[email protected]>

* Refactored the code of the Crossplane Release Builder

Signed-off-by: Charles Moulliard <[email protected]>

* Format the code. change the name of the Services from Register to Services catalog. Set the missing properties

Signed-off-by: Charles Moulliard <[email protected]>

* Reviewed the wording of the home page

Signed-off-by: Charles Moulliard <[email protected]>

* WIP. Adding a second column to the form. Still have issue with the fields validation

Signed-off-by: Charles Moulliard <[email protected]>

* Added missing fields of the ServiceRequest form

Signed-off-by: Charles Moulliard <[email protected]>

* Rename <div to <form to fix the issue. Fix #309

Signed-off-by: Charles Moulliard <[email protected]>

* Add a TODO about how to get for a Service the cluster object which is needed to create the kubernetesClient

Signed-off-by: Charles Moulliard <[email protected]>

* Implementing the call to the method to deploy the service if installable is true. To be tested with crossplane

Signed-off-by: Charles Moulliard <[email protected]>

* Including the source generated dir

Signed-off-by: Charles Moulliard <[email protected]>

* Add usage to primaza.sh script. Removing the function to install kind. Remove the hardcoded registry name

Signed-off-by: Charles Moulliard <[email protected]>

* Updating the instructions to install crossplane

Signed-off-by: Charles Moulliard <[email protected]>

* Reformat the exception

Signed-off-by: Charles Moulliard <[email protected]>

* Removing non needed module

Signed-off-by: Charles Moulliard <[email protected]>

* Removing the 2 builder classes that we dont use anymore

Signed-off-by: Charles Moulliard <[email protected]>

* remove not needed

* Rename usage to primazaUsage

Signed-off-by: Charles Moulliard <[email protected]>

* Renamed the label from installable to To be provisioned. Fix some errors with primaza script and update README

Signed-off-by: Charles Moulliard <[email protected]>

* Change the column size from 2 to 5

Signed-off-by: Charles Moulliard <[email protected]>

* Deploy atomic fruits using its helm chart

* Add the non neded namespace. Pass the env vars to configure the VAUKT URL for localdeploy

Signed-off-by: Charles Moulliard <[email protected]>

* Set the Release Chart fields using the Service object

Signed-off-by: Charles Moulliard <[email protected]>

* store secret data in form of key, value

Related to #298

* Fixing differnt issues to install crossplane and helm provider

Signed-off-by: Charles Moulliard <[email protected]>

* Changing order to delete resources

Signed-off-by: Charles Moulliard <[email protected]>

* Format java class

Signed-off-by: Charles Moulliard <[email protected]>

* Enable the debug for helm provider

Signed-off-by: Charles Moulliard <[email protected]>

* Created a new project to play with crossplane composite/composition

Signed-off-by: Charles Moulliard <[email protected]>

* Updated the script to also install the kubernetes provider

Signed-off-by: Charles Moulliard <[email protected]>

* Renaming the name from postgresql-db to postgresql

Signed-off-by: Charles Moulliard <[email protected]>

* Removing the Kubernetest providerconfig as this is not needed

Signed-off-by: Charles Moulliard <[email protected]>

* Renaming the resource from password to secret like also the providerConfigref of kubernetes as non needed

* Still no luck to base64 the fields

Signed-off-by: Charles Moulliard <[email protected]>

* Set the value to selected if the service.installable is true

Signed-off-by: Charles Moulliard <[email protected]>

* Fixing the issue as the boolean of the request was not saved due to uncorrect type used: boolan -> string

Signed-off-by: Charles Moulliard <[email protected]>

* Added helm information to the service to be deployed

Signed-off-by: Charles Moulliard <[email protected]>

* Adding the missing space

Signed-off-by: Charles Moulliard <[email protected]>

* Add new namespaces to be excluded by default for the cluster

Signed-off-by: Charles Moulliard <[email protected]>

* Moving the code before to test if service is null, passing th namespace which is required to the Helm Release CR

Signed-off-by: Charles Moulliard <[email protected]>

* Removing the command to install the DB as we will provision it using crossplane. Disable the quarkus fruits helm chart to install also th DB.

* Use the cluster coming from the Service or Application

* Remove the step to upload to kind the docker image as it is uploaded to the docker registry

Signed-off-by: Charles Moulliard <[email protected]>

* Adding more logging as binding is failing as url is null !

* Fixing with hard coded valued the binding

Signed-off-by: Charles Moulliard <[email protected]>

* Use the same chart version as tested whn we install manually the chart

Signed-off-by: Charles Moulliard <[email protected]>

* Increase vault slep time to let vault to be started. Implement the code to delete the Relasewhen we unbind. Fix the issue as the Helm chart values were not set properly. Rename the tile of the claim UI

Signed-off-by: Charles Moulliard <[email protected]>

* Review the wording about the service available

Signed-off-by: Charles Moulliard <[email protected]>

* Updatd the code to support to create a claim before to claim and to use the modal window

Signed-off-by: Charles Moulliard <[email protected]>

* Add if check to delete the Release only if the service installable is true. Be more verbose about what we log to scan/find services. Test within the listDiscoveredTable.html if the service.cluster exists like service.cluster.name

Signed-off-by: Charles Moulliard <[email protected]>

* Creating a new class to collect the discovered services

Signed-off-by: Charles Moulliard <[email protected]>

* Fixing wrong findAll call

Signed-off-by: Charles Moulliard <[email protected]>

* Mapping the kubernetes svc discovered withlistdiscoveredTable - HTML

Signed-off-by: Charles Moulliard <[email protected]>

* Add a test to chck if claim.service.installable is not null

Signed-off-by: Charles Moulliard <[email protected]>

* Reformat the code

Signed-off-by: Charles Moulliard <[email protected]>

---------

Signed-off-by: Charles Moulliard <[email protected]>
Co-authored-by: Auri Munoz <[email protected]>
Co-authored-by: Aurea Muñoz Hernández <[email protected]>
  • Loading branch information
3 people authored May 9, 2023
1 parent 61aa60d commit 1a75023
Show file tree
Hide file tree
Showing 30 changed files with 852 additions and 306 deletions.
69 changes: 28 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,18 @@ but will also run different containers: database (h2) & vault secret engine if

You can discover the [quarkus dev services](https://quarkus.io/guides/dev-services) and injected config by pressing on the key `c` within your terminal.

If you plan to play with a quarkus demo application and bind it to a service, then install a kind cluster locally
```bash
VM_IP=<IP_OR_HOSTNAME_OF_THE_MACHINE/VM> // e.g. VM_IP=127.0.0.1
curl -s -L "https://raw.githubusercontent.com/snowdrop/k8s-infra/main/kind/kind-reg-ingress.sh" | bash -s y latest kind 0 ${VM_IP}
```
and next follow then the instructions of the [Demo time](#demo-time) section :-)
Next follow then the instructions of the [Demo time](#demo-time) section :-)

### Using Primaza on a k8s cluster

In order to use Primaza on kubernetes, it is needed first to setup a cluster (kind, minikube, etc) and to install an ingress controller.
To simplify this process, you can use the following bash script able to set up such environment using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) and [helm](https://helm.sh/docs/helm/helm_install/).

You can use the following script able to install using kind a kubernetes cluster locally:
```bash
VM_IP=<IP_OR_HOSTNAME_OF_THE_MACHINE/VM>
curl -s -L "https://raw.githubusercontent.com/snowdrop/k8s-infra/main/kind/kind-reg-ingress.sh" | bash -s y latest kind 0 ${VM_IP}
curl -s -L "https://raw.githubusercontent.com/snowdrop/k8s-infra/main/kind/kind.sh" | bash -s install
```
**Remark**: The kubernetes's version can be changed if you replace `latest` with one of the version supported by kind `1.23 .. 1.25`
> **Remark**: To see all the options proposed by the script, use the command `curl -s -L "https://raw.githubusercontent.com/snowdrop/k8s-infra/main/kind/kind.sh" | bash -s -h`
Install vault using the following script `./scripts/vault.sh`. We recommend to use this script as it is needed to perform different steps
If the cluster is up and running, install vault using the following script `./scripts/vault.sh`. We recommend to use this script as it is needed to perform different steps
post vault installation such as:
- unseal,
- store root token within the local folder `.vault/cluster-keys.json`,
Expand All @@ -133,7 +126,13 @@ post vault installation such as:
> **Note**: If creation of the vault's pod is taking more than 60s as the container image must be downloaded, then the process will stop.
In this case, remove the helm chart `./scripts/vault.sh remove` and repeat the operation.

> **Tip**: Notice the messages displayed within the console as they told you how to get the root token and where they are stored, where to access the keys, etc !
> **Tip**: Notice the messages displayed within the terminal as they told you how to get the root token and where they are stored, where to access the keys, etc !
We can now install Crossplane and its Helm provider
```bash
./scripts/crossplane.sh
```
> **Tip**: Script usage is available using the `-h` parameter
Create the primaza namespace
```bash
Expand All @@ -151,7 +150,7 @@ helm install \
primaza-app \
primaza-app \
-n primaza \
--set app.image=quay.io/halkyonio/primaza-app:latest \
--set app.image=<CONTAINER_REGISTRY>/<ORG>/primaza-app:latest \
--set app.host=primaza.${VM_IP}.nip.io \
--set app.envs.vault.url=${VAULT_URL}
```
Expand All @@ -160,12 +159,15 @@ helm install \
If you prefer to install everything all-in-one, use our bash scripts on a `kind` k8s cluster:
```bash
VM_IP=<VM_IP>
VAULT_URL=http://vault-internal.vault:8200
export VAULT_URL=http://vault-internal.vault:8200
export PRIMAZA_IMAGE_NAME=kind-registry:5000/local/primaza-app
$(pwd)/scripts/vault.sh
$(pwd)/scripts/primaza.sh
$(pwd)/scripts/crossplane.sh
$(pwd)/scripts/primaza.sh build
$(pwd)/scripts/primaza.sh localdeploy
```

> **Note**: Before to execute the `./primaza.sh` script, check the latest image pushed on quay.io as set the version to the one you want to test using the variable `export GIT_SHA_COMMIT=` !
> **Note**: If you prefer to use the helm chart pushed on [Halkyon repository](https://github.com/halkyonio/helm-charts), don't use the parameters `build` and `localdeploy`
And now, you can demo it ;-)

Expand All @@ -188,29 +190,14 @@ To play with Primaza, you can use the following scenario:

Everything is in place to claim a Service using the following commands:

- Install the `fruits` postgresql DB that the Quarkus Fruits application will access
```bash
DB_USERNAME=healthy
DB_PASSWORD=healthy
DB_DATABASE=fruits_database
RELEASE_NAME=postgresql
VERSION=11.9.13
helm uninstall postgresql -n db
kubectl delete pvc -lapp.kubernetes.io/name=$RELEASE_NAME -n db

helm install $RELEASE_NAME bitnami/postgresql \
--version $VERSION \
--set auth.username=$DB_USERNAME \
--set auth.password=$DB_PASSWORD \
--set auth.database=$DB_DATABASE \
--create-namespace \
-n db
```
- Deploy the Quarkus Fruits application within the namespace `app`
```bash
kubectl create ns app
kubectl delete -f $(pwd)/scripts/data/atomic-fruits.yml
kubectl apply -f $(pwd)/scripts/data/atomic-fruits.yml
helm install fruits-app halkyonio/fruits-app \
-n app --create-namespace \
--set app.image=quay.io/halkyonio/atomic-fruits:latest \
--set app.host=atomic-fruits.<VM_IP>.nip.io \
--set app.serviceBinding.enabled=false \
--set db.enabled=false
```
- Create an entry within the secret store engine at the path `primaza/fruits`. This path will be used to configure the credentials to access the `fruits_database`.
```bash
Expand All @@ -222,8 +209,8 @@ Everything is in place to claim a Service using the following commands:
export VAULT_TOKEN=root
export VAULT_ADDR=http://localhost:<VAULT_PORT>

// Next create a key
vault kv put -mount=secret primaza/fruits healthy=healthy
// Next create the key that we need to access the Postgresql fruits db
vault kv put -mount=secret primaza/fruits username=healthy password=healthy database=fruits_database
vault kv get -mount=secret primaza/fruits
```

Expand All @@ -237,7 +224,7 @@ Everything is in place to claim a Service using the following commands:

// To be executed when steps are done manually or when using quarkus:dev
export KIND_URL=$(kubectl config view -o json | jq -r --arg ctx kind-kind '.clusters[] | select(.name == $ctx) | .cluster.server')
$(pwd)/scripts/data/cluster.sh
$(pwd)/scripts/data/cluster.sh

// Common steps
$(pwd)/scripts/data/services.sh
Expand Down
3 changes: 3 additions & 0 deletions app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,15 @@
<configuration>
<extraAnnotations>true</extraAnnotations>
<urls>
<!-- Primaza CRDs -->
<url>https://raw.githubusercontent.com/primaza/primaza/main/config/crd/bases/primaza.io_clusterenvironments.yaml</url>
<url>https://raw.githubusercontent.com/primaza/primaza/main/config/crd/bases/primaza.io_registeredservices.yaml</url>
<url>https://raw.githubusercontent.com/primaza/primaza/main/config/crd/bases/primaza.io_servicebindings.yaml</url>
<url>https://raw.githubusercontent.com/primaza/primaza/main/config/crd/bases/primaza.io_servicecatalogs.yaml</url>
<url>https://raw.githubusercontent.com/primaza/primaza/main/config/crd/bases/primaza.io_serviceclaims.yaml</url>
<url>https://raw.githubusercontent.com/primaza/primaza/main/config/crd/bases/primaza.io_serviceclasses.yaml</url>
<!-- Crossplane Helm release able to deploy a helm chart -->
<url>https://raw.githubusercontent.com/crossplane-contrib/provider-helm/master/package/crds/helm.crossplane.io_releases.yaml</url>
</urls>
</configuration>
</plugin>
Expand Down
11 changes: 4 additions & 7 deletions app/src/main/java/io/halkyon/Templates.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
import java.util.List;
import java.util.Map;

import io.halkyon.model.Application;
import io.halkyon.model.Claim;
import io.halkyon.model.Cluster;
import io.halkyon.model.Credential;
import io.halkyon.model.Service;
import io.halkyon.model.*;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

Expand Down Expand Up @@ -40,9 +36,10 @@ public static native TemplateInstance list(String title, List<Service> services,

public static native TemplateInstance form(String title, Service service);

public static native TemplateInstance listDiscovered(String title, List<Service> services, long items);
public static native TemplateInstance listDiscovered(String title, List<ServiceDiscovered> services,
long items);

public static native TemplateInstance listDiscoveredTable(List<Service> services, long items);
public static native TemplateInstance listDiscoveredTable(List<ServiceDiscovered> services, long items);
}

@CheckedTemplate(basePath = "credentials", requireTypeSafeExpressions = false)
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/io/halkyon/model/Claim.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.halkyon.model;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -60,6 +61,9 @@ public static List<Claim> listAll() {
}

public static List<Claim> listAvailable() {
return find("status=:status", Collections.singletonMap("status", ClaimStatus.BINDABLE.toString())).list();
// TODO: To be reviewed to support to display claims when status is pending or bindable
// return find("status=:status", Collections.singletonMap("status", ClaimStatus.BINDABLE.toString())).list();
return find("status in :statuses", Collections.singletonMap("statuses",
Arrays.asList(ClaimStatus.PENDING.toString(), ClaimStatus.BINDABLE.toString()))).list();
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/io/halkyon/model/Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public class Service extends PanacheEntityBase {
*/
public String externalEndpoint;
public Boolean available;
public Boolean installable;
public String helmRepo;
public String helmChart;
public String helmChartVersion;
@CreationTimestamp
public Date created;
@UpdateTimestamp
Expand Down Expand Up @@ -98,6 +102,12 @@ public static List<Service> listAll() {
}

public static List<Service> findAvailableServices() {
// TODO. This code should be reviewed as currently we check if a Service
// part of the catalog as the property available = true
// instead of checking if a service is running within the cluster(s).
// This service must check using the cache, the available services
// old code -->
// return Service.findAll(Sort.ascending("name")).list();
return Service.find("available=true").list();
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/io/halkyon/model/ServiceDiscovered.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.halkyon.model;

public class ServiceDiscovered {
public String namespace;
public String clusterName;
public String kubernetesSvcName;
public Service serviceIdentity;
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,25 @@ public Response doClaimApplication(@PathParam("id") long applicationId, @FormPar
if (claim.service == null) {
throw new NotAcceptableException(String.format("Claim %s has no services available", claimId));
}
if (claim.service.installable) {
try {
System.out.println("Service is installable using crossplane. Let's do it :-)");
bindService.createCrossplaneHelmRelease(application.cluster, claim.service);
} catch (ClusterConnectException ex) {
throw new InternalServerErrorException(
"Can't deploy the service with the cluster " + ex.getCluster() + ". Cause: " + ex.getMessage());
}
}
if (claim.service.credentials == null || claim.service.credentials.isEmpty()) {
throw new NotAcceptableException(String.format("Service %s has no credentials", claim.service.name));
}
claim.application = application;
try {
// TODO: Do a temporary workaround and hard code the values :-(
claim.service.cluster = claim.application.cluster;
claim.service.name = "postgresql";
claim.service.namespace = "db";
claim.persist();
bindService.bindApplication(claim);
claim.persist();
return Response.ok().build();
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/io/halkyon/resource/page/ClaimResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.halkyon.resource.requests.ClaimRequest;
import io.halkyon.services.BindApplicationService;
import io.halkyon.services.ClaimStatus;
import io.halkyon.services.KubernetesClientService;
import io.halkyon.services.UpdateClaimJob;
import io.halkyon.utils.AcceptedResponseBuilder;
import io.halkyon.utils.FilterableQueryBuilder;
Expand All @@ -51,6 +52,9 @@ public class ClaimResource {
private final UpdateClaimJob claimingService;
private final BindApplicationService bindService;

@Inject
KubernetesClientService kubernetesClientService;

@Inject
public ClaimResource(Validator validator, UpdateClaimJob claimingService, BindApplicationService bindService) {
this.validator = validator;
Expand Down Expand Up @@ -210,8 +214,35 @@ private void doUpdateClaim(Claim claim, ClaimRequest request) {

claimingService.updateClaim(claim);

// TODO: Logic to be reviewed
if (claim.service.installable != null && claim.service.installable && claim.application != null) {
try {
System.out.println("Service is installable using crossplane. Let's do it :-)");
bindService.createCrossplaneHelmRelease(claim.application.cluster, claim.service);
if (kubernetesClientService.getServiceInCluster(claim.application.cluster, claim.service.getProtocol(),
claim.service.getPort()).isPresent()) {
claim.service.cluster = claim.application.cluster;
}
} catch (ClusterConnectException ex) {
throw new InternalServerErrorException(
"Can't deploy the service with the cluster " + ex.getCluster() + ". Cause: " + ex.getMessage());
}
}

// TODO: We must find the new service created (= name & namespace + port), otherwise the url returned by
// generateUrlByClaimService(claim) will be null
LOG.infof("Service name: %s", claim.service.name == null ? "" : claim.service.name);
LOG.infof("Service namespace: %s", claim.service.namespace == null ? "" : claim.service.namespace);
LOG.infof("Service port: %s", claim.service.getPort() == null ? "" : claim.service.getPort());
LOG.infof("Service protocol: %s", claim.service.getProtocol() == null ? "" : claim.service.getProtocol());

if (claim.service != null && claim.service.credentials != null && claim.application != null) {
try {
// TODO: Do a temporary workaround and hard code the values :-(
claim.service.cluster = claim.application.cluster;
claim.service.name = "postgresql";
claim.service.namespace = "db";
claim.persist();
bindService.bindApplication(claim);
} catch (ClusterConnectException e) {
LOG.error("Could bind application because there was connection errors. Cause: " + e.getMessage());
Expand Down
28 changes: 22 additions & 6 deletions app/src/main/java/io/halkyon/resource/page/ServiceResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
import org.jboss.resteasy.annotations.Form;

import io.halkyon.Templates;
import io.halkyon.exceptions.ClusterConnectException;
import io.halkyon.model.Service;
import io.halkyon.model.ServiceDiscovered;
import io.halkyon.resource.requests.ServiceRequest;
import io.halkyon.services.KubernetesClientService;
import io.halkyon.services.ServiceDiscoveryJob;
import io.halkyon.utils.AcceptedResponseBuilder;
import io.halkyon.utils.FilterableQueryBuilder;
Expand All @@ -44,6 +47,9 @@ public class ServiceResource {
@Inject
ServiceDiscoveryJob serviceDiscoveryJob;

@Inject
KubernetesClientService kubernetesClientService;

@GET
@Path("/new")
@Produces(MediaType.TEXT_HTML)
Expand Down Expand Up @@ -190,18 +196,20 @@ public io.halkyon.model.Service findByNameAndVersion(@PathParam("name") String n
@Produces(MediaType.TEXT_HTML)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/discovered")
public TemplateInstance listDiscoveredServices() {
List<Service> discoveredServices = Service.findAvailableServices();
return Templates.Services.listDiscovered("Services available", discoveredServices, discoveredServices.size());
public TemplateInstance listDiscoveredServices() throws ClusterConnectException {
List<ServiceDiscovered> servicesDiscovered = kubernetesClientService.discoverServicesInCluster();
return Templates.Services.listDiscovered("Services available", servicesDiscovered, servicesDiscovered.size());
// List<Service> services = Service.findAvailableServices();
// return Templates.Services.listDiscoveredTable(services, services.size());
}

@GET
@Produces(MediaType.TEXT_HTML)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/discovered/polling")
public TemplateInstance pollingDiscoveredServices() {
List<Service> discoveredServices = Service.findAvailableServices();
return Templates.Services.listDiscoveredTable(discoveredServices, discoveredServices.size());
public TemplateInstance pollingDiscoveredServices() throws ClusterConnectException {
List<ServiceDiscovered> servicesDiscovered = kubernetesClientService.discoverServicesInCluster();
return Templates.Services.listDiscovered("Services available", servicesDiscovered, servicesDiscovered.size());
}

private void doUpdateService(Service service, ServiceRequest request) {
Expand All @@ -210,6 +218,14 @@ private void doUpdateService(Service service, ServiceRequest request) {
service.type = request.type;
service.endpoint = request.endpoint;
service.externalEndpoint = request.externalEndpoint;
if (request.installable != null && request.installable.equals("on")) {
service.installable = true;
} else {
service.installable = false;
}
service.helmRepo = request.helmRepo;
service.helmChart = request.helmChart;
service.helmChartVersion = request.helmChartVersion;

if (StringUtils.isNotEmpty(service.externalEndpoint)) {
service.available = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ public class ServiceRequest {
public String endpoint;
@FormParam
public String externalEndpoint;
@FormParam
public String installable;
@FormParam
public String helmRepo;
@FormParam
public String helmChart;
@FormParam
public String helmChartVersion;
}
Loading

0 comments on commit 1a75023

Please sign in to comment.