Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(discovery): enable OpenShift cross-namespace discovery #1290

Merged
merged 3 commits into from
Jan 12, 2023

Conversation

andrewazores
Copy link
Member

@andrewazores andrewazores commented Dec 14, 2022

Welcome to Cryostat! 👋

Before contributing, make sure you have:

  • Read the contributing guidelines
  • Linked a relevant issue which this PR resolves
  • Linked any other relevant issues, PR's, or documentation, if any
  • Resolved all conflicts, if any
  • Rebased your branch PR on top of the latest upstream main branch
  • Attached at least one of the following labels to the PR: [chore, ci, docs, feat, fix, test]
  • Signed the last commit: git commit --amend --signoff

Related to #1188
Related to #760
Related to cryostatio/cryostat-operator#501

Description of the change:

The primary change is a one-liner: k8sClient.endpoints().inNamespace(namespace) -> k8sClient.endpoints().inAnyNamespace(). This also does a bit of refactoring.
This updates the KubeApiPlatformClient class, which is the internal implementation that handles the Endpoints querying discovery behaviour, to not be restricted to only querying for Endpoints objects within the same Namespace that the Cryostat container is running within. A new CRYSOTAT_K8S_NAMESPACES environment variable is added. The variable's value is expected to be a comma-separated list of namespace names. The KubeApiPlatformClient sets up an Informer for each of these namespaces, and collates results from all of the informers when handling operations like listing targets or constructing the discovery tree. If this environment variable is unset/blank then the namespace Cryostat is running in is used as a singleton list value, which maintains the existing behaviour.

Motivation for the change:

If the Operator has deployed Cryostat in an AllNamespaces mode, then Cryostat's built-in OpenShift discovery mechanism should look in all namespaces, not only the one which Cryostat is deployed within. This is still subject to the hardcoded svc.port.name == 'jfr-jmx' || svc.port.number == 9091 behaviour as before. The previous Discovery Plugin API and the upcoming cryostat-agent address that. This PR also does not handle the "multi-tenant" case of multi-namespace installations, ie cases where not only are applications divided between different Namespaces but where users have differing permissions between namespaces. The combination of Operator AllNamespaces and this PR only enables an all-privileged model across multiple namespaces - any user with access to Cryostat has access to all of the recording data within Cryostat, regardless of where the data originated and what the user's permissions are within that origin (namespace).
The Operator may deploy Cryostat into its own namespace and expect it to monitor targets across multiple namespaces which all have the same or similar permissions levels (same levels of user and service account access etc). This PR does not add any handling or security considerations for varying access levels across the listed namespaces. If Cryostat's service account has access to those namespaces then it will be able to discover targets there, collect JFR data from those targets, and store it in its archives. Any user with access to Cryostat will then be able to read those files from the archives, even if they do not have permission to access the targets in the namespace where the data originated. See #1188 for work toward enforcing namespace separation. These two PRs together would enable a proper cluster-wide/AllNamespaces Cryostat installation.

TODO later, to hook in with #1188, is to support some syntax like CRYOSTAT_K8S_NAMESPACES='*', which would cause the KubeApiPlatformClient to install an Informer with .inAnyNamespace() for full cluster-wide Endpoints discovery. This is slightly trickier when it comes to constructing the discovery tree since the namespaces are not known in advance but found while creating the tree, so some transient caching of namespace EnvironmentNodes may be needed to ensure that different Endpoints objects and their ownership chains do not cause duplicate namespaces to be registered into the final tree.

How to manually test:

See comments below.

@andrewazores andrewazores added the feat New feature or request label Dec 14, 2022
@mergify mergify bot added the safe-to-test label Dec 14, 2022
@github-actions
Copy link
Contributor

Test image available:

$ CRYOSTAT_IMAGE=ghcr.io/cryostatio/cryostat:pr-1290-10badc8649178b2616a324b46bbf69271bf1962a sh smoketest.sh

@github-actions
Copy link
Contributor

Test image available:

$ CRYOSTAT_IMAGE=ghcr.io/cryostatio/cryostat:pr-1290-b227df342370bff4b42acc5902fe9b0181fc3ae7 sh smoketest.sh

@github-actions
Copy link
Contributor

Test image available:

$ CRYOSTAT_IMAGE=ghcr.io/cryostatio/cryostat:pr-1290-271c228cb067df5b106d97e2c1dcba128d649024 sh smoketest.sh

@andrewazores
Copy link
Member Author

@ebaron thoughts on this approach? I hacked together something quick for cryostat-helm to test this with:

https://github.com/andrewazores/cryostat-helm/tree/crossns

To test this out I used kind and created a cluster with the default namespace as well as the additional cryostat1 and cryostat2 namespaces. I left cryostat2 empty, and in cryostat1 I deployed the cryostat-helm with all default values so it's running the latest upstream CI image (2.3.0-snapshot)

In default I deployed the chart with my modifications, and a Cryostat core image built from this PR:

$ helm install --set core.image.repository=quay.io/andrewazores/cryostat --set core.image.tag="2.3.0-crossns" cryostat ./cryostat

I did the usual -helm post-config steps, as well as:

kubectl -n default set env deploy --containers=cryostat cryostat CRYOSTAT_K8S_NAMESPACES=default,cryostat1,cryostat2

This failed as expected with the following in the Cryostat container logs:

INFO: Started Endpoints SharedInformer for namespace "default"
Jan 11, 2023 9:56:07 PM io.cryostat.core.log.Logger error
SEVERE: FAILED to deploy io.cryostat.discovery.BuiltInDiscovery Verticle
io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://10.96.0.1/api/v1/namespaces/cryostat1/endpoints. Message: Forbidden!Configured service account doesn't have access. Service account may have been revoked. endpoints is forbidden: User "system:serviceaccount:default:cryostat" cannot list resource "endpoints" in API group "" in the namespace "cryostat1".
	at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.requestFailure(OperationSupport.java:709)
	at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.requestFailure(OperationSupport.java:689)
	at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.assertResponseCode(OperationSupport.java:638)
	at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.lambda$handleResponse$0(OperationSupport.java:576)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
	at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.lambda$retryWithExponentialBackoff$2(OperationSupport.java:618)
	at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
	at io.fabric8.kubernetes.client.okhttp.OkHttpClientImpl$4.onResponse(OkHttpClientImpl.java:277)
	at okhttp3.RealCall$AsyncCall.execute(RealCall.java:203)
	at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
	Suppressed: java.lang.Throwable: waiting here
		at io.fabric8.kubernetes.client.utils.Utils.waitUntilReady(Utils.java:173)
		at io.fabric8.kubernetes.client.utils.Utils.waitUntilReadyOrFail(Utils.java:184)
		at io.fabric8.kubernetes.client.informers.impl.DefaultSharedIndexInformer.run(DefaultSharedIndexInformer.java:170)
		at io.fabric8.kubernetes.client.dsl.internal.BaseOperation.inform(BaseOperation.java:958)
		at io.cryostat.platform.internal.KubeApiPlatformClient$1.lambda$initialize$0(KubeApiPlatformClient.java:108)
		at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
		at io.cryostat.platform.internal.KubeApiPlatformClient$1.initialize(KubeApiPlatformClient.java:101)
		at io.cryostat.platform.internal.KubeApiPlatformClient$1.initialize(KubeApiPlatformClient.java:91)
		at org.apache.commons.lang3.concurrent.LazyInitializer.get(LazyInitializer.java:106)
		at io.cryostat.platform.internal.KubeApiPlatformClient.safeGetInformers(KubeApiPlatformClient.java:201)
		at io.cryostat.platform.internal.KubeApiPlatformClient.getDiscoveryTree(KubeApiPlatformClient.java:161)
		at io.cryostat.discovery.BuiltInDiscovery.lambda$start$4(BuiltInDiscovery.java:95)
		at java.base/java.lang.Iterable.forEach(Iterable.java:75)
		at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1092)
		at io.cryostat.discovery.BuiltInDiscovery.start(BuiltInDiscovery.java:90)
		at io.vertx.core.impl.DeploymentManager.lambda$doDeploy$5(DeploymentManager.java:196)
		at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:264)
		at io.vertx.core.impl.WorkerContext.lambda$run$3(WorkerContext.java:106)
		at io.vertx.core.impl.WorkerContext.lambda$null$1(WorkerContext.java:92)
		at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
		at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
		at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
		... 1 more

so it is trying to start an Endpoints Informer for each of the listed namespaces, but it fails due to lack of permissions since -helm only sets up the SA/Role/RoleBinding for the single namespace that the chart is deployed into.

@github-actions
Copy link
Contributor

Test image available:

$ CRYOSTAT_IMAGE=ghcr.io/cryostatio/cryostat:pr-1290-97cbe7e29fe318afd1bd0780ceddf9e0174a8b1b sh smoketest.sh

@andrewazores
Copy link
Member Author

andrewazores commented Jan 12, 2023

I repeated the setup above, but this time I only created the additional cryostat1 namespace. In there I simply did helm install cryostat ./cryostat, just for the purpose of deploying Cryostat itself as a sample application easily.

In the default namespace I deployed the customized -helm I linked previously, running the image from this PR, and with CRYOSTAT_K8S_NAMESPACES=default,cryostat1.

Back in namespace cryostat1, I created the following two additional resources:

role1.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"rbac.authorization.k8s.io/v1","kind":"Role","metadata":{"annotations":{"meta.helm.sh/release-name":"cryostat","meta.helm.sh/release-namespace":"default"},"creationTimestamp":"2023-01-12T12:31:19Z","labels":{"app.kubernetes.io/instance":"cryostat","app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"cryostat","app.kubernetes.io/version":"latest","helm.sh/chart":"cryostat-0.2.0"},"name":"cryostat1","namespace":"cryostat1","resourceVersion":"1414","uid":"41a8748c-18e5-4846-8670-e28308d367f8"},"rules":[{"apiGroups":[""],"resources":["endpoints"],"verbs":["get","list","watch"]},{"apiGroups":[""],"resources":["pods","replicationcontrollers"],"verbs":["get"]},{"apiGroups":["apps"],"resources":["replicasets","deployments","daemonsets","statefulsets"],"verbs":["get"]},{"apiGroups":["apps.openshift.io"],"resources":["deploymentconfigs"],"verbs":["get"]}]}
    meta.helm.sh/release-name: cryostat
    meta.helm.sh/release-namespace: default
  creationTimestamp: "2023-01-12T12:40:03Z"
  labels:
    app.kubernetes.io/instance: cryostat
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: cryostat
    app.kubernetes.io/version: latest
    helm.sh/chart: cryostat-0.2.0
  name: cryostat1
  namespace: cryostat1
  resourceVersion: "2429"
  uid: 71ff90cc-5ca5-4a69-9f44-244d16266658
rules:
- apiGroups:
  - ""
  resources:
  - endpoints
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - pods
  - replicationcontrollers
  verbs:
  - get
- apiGroups:
  - apps
  resources:
  - replicasets
  - deployments
  - daemonsets
  - statefulsets
  verbs:
  - get
- apiGroups:
  - apps.openshift.io
  resources:
  - deploymentconfigs
  verbs:
  - get

rolebinding1.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"rbac.authorization.k8s.io/v1","kind":"RoleBinding","metadata":{"annotations":{"meta.helm.sh/release-name":"cryostat","meta.helm.sh/release-namespace":"default"},"creationTimestamp":"2023-01-12T12:31:19Z","labels":{"app.kubernetes.io/instance":"cryostat","app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"cryostat","app.kubernetes.io/version":"latest","helm.sh/chart":"cryostat-0.2.0"},"name":"cryostat1","namespace":"cryostat1","resourceVersion":"1415","uid":"62004294-924f-43fe-af43-72445d06aa51"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"Role","name":"cryostat1"},"subjects":[{"kind":"ServiceAccount","name":"cryostat","namespace":"default"}]}
    meta.helm.sh/release-name: cryostat
    meta.helm.sh/release-namespace: default
  creationTimestamp: "2023-01-12T12:40:09Z"
  labels:
    app.kubernetes.io/instance: cryostat
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: cryostat
    app.kubernetes.io/version: latest
    helm.sh/chart: cryostat-0.2.0
  name: cryostat1
  namespace: cryostat1
  resourceVersion: "2438"
  uid: a4785739-b463-4454-a00d-47b36ab235ec
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cryostat1
subjects:
- kind: ServiceAccount
  name: cryostat
  namespace: default

These are basically just copies of the existing role/rolebinding that the helm chart sets up, just modified to create a mirrored role and binding to the same serviceaccount within cryostat1.

After that, the logs show Cryostat starting successfully, and these messages appear:

...
INFO: Starting built-in discovery with KubeApiPlatformClient
Jan 12, 2023 12:40:16 PM io.cryostat.core.log.Logger info
INFO: Started Endpoints SharedInformer for namespace "default"
Jan 12, 2023 12:40:16 PM io.cryostat.core.log.Logger info
INFO: Started Endpoints SharedInformer for namespace "cryostat1"
Jan 12, 2023 12:40:16 PM io.cryostat.core.log.Logger info
INFO: Observing new target: io.cryostat.platform.ServiceRef@7283295c[alias=cryostat-74bfb57cff-gdzfl,annotations=io.cryostat.platform.ServiceRef$Annotations@81999da[cryostat={HOST=10.244.0.6, PORT=9091, NAMESPACE=default, POD_NAME=cryostat-74bfb57cff-gdzfl, REALM=KubernetesApi},platform={}],jvmId=<null>,labels={pod-template-hash=74bfb57cff, app.kubernetes.io/name=cryostat, app.kubernetes.io/instance=cryostat},serviceUri=service:jmx:rmi:///jndi/rmi://10.244.0.6:9091/jmxrmi]
...
INFO: Observing new target: io.cryostat.platform.ServiceRef@6809d375[alias=cryostat-ffb67db94-jpx68,annotations=io.cryostat.platform.ServiceRef$Annotations@66ce2004[cryostat={HOST=10.244.0.12, PORT=9091, NAMESPACE=cryostat1, POD_NAME=cryostat-ffb67db94-jpx68, REALM=KubernetesApi},platform={}],jvmId=<null>,labels={pod-template-hash=ffb67db94, app.kubernetes.io/name=cryostat, app.kubernetes.io/instance=cryostat},serviceUri=service:jmx:rmi:///jndi/rmi://10.244.0.12:9091/jmxrmi]

API responses show cross-namespace discovery working:

$ http :8080/api/v1/targets

[
  {
    "jvmId": "-WRGlnsyV__ze4pBW6eKUxvF62L6YVmGWTa7Xgf5Itk=",
    "connectUrl": "service:jmx:rmi:///jndi/rmi://10.244.0.12:9091/jmxrmi",
    "alias": "cryostat-ffb67db94-jpx68",
    "labels": {
      "pod-template-hash": "ffb67db94",
      "app.kubernetes.io/name": "cryostat",
      "app.kubernetes.io/instance": "cryostat"
    },
    "annotations": {
      "platform": {},
      "cryostat": {
        "HOST": "10.244.0.12",
        "PORT": "9091",
        "NAMESPACE": "cryostat1",
        "POD_NAME": "cryostat-ffb67db94-jpx68",
        "REALM": "KubernetesApi"
      }
    }
  },
  {
    "jvmId": "a-NTvJxVlVQ2MjH-aX_PRWL17_MabOPjj1nDNS9ypus=",
    "connectUrl": "service:jmx:rmi:///jndi/rmi://10.244.0.11:9091/jmxrmi",
    "alias": "cryostat-6f4c6b58fc-hr4jg",
    "labels": {
      "pod-template-hash": "6f4c6b58fc",
      "app.kubernetes.io/name": "cryostat",
      "app.kubernetes.io/instance": "cryostat"
    },
    "annotations": {
      "platform": {
        "kubectl.kubernetes.io/restartedAt": "2023-01-12T07:40:11-05:00"
      },
      "cryostat": {
        "HOST": "10.244.0.11",
        "PORT": "9091",
        "NAMESPACE": "default",
        "POD_NAME": "cryostat-6f4c6b58fc-hr4jg",
        "REALM": "KubernetesApi"
      }
    }
  }
]

$ http :8080/api/v2.1/discovery

{
  "meta": {
    "type": "application/json",
    "status": "OK"
  },
  "data": {
    "result": {
      "children": [
        {
          "name": "Custom Targets",
          "nodeType": "Realm",
          "labels": {
            "REALM": "a3308ab3-d16c-4473-9809-2041a956fba9"
          },
          "children": []
        },
        {
          "name": "KubernetesApi",
          "nodeType": "Realm",
          "labels": {
            "REALM": "b15783b3-34d0-4646-a99e-2f75e581eb1d"
          },
          "children": [
            {
              "name": "cryostat1",
              "nodeType": "Namespace",
              "labels": {},
              "children": [
                {
                  "name": "cryostat",
                  "nodeType": "Deployment",
                  "labels": {
                    "helm.sh/chart": "cryostat-0.2.0",
                    "app.kubernetes.io/managed-by": "Helm",
                    "app.kubernetes.io/name": "cryostat",
                    "app.kubernetes.io/instance": "cryostat",
                    "app.kubernetes.io/version": "latest"
                  },
                  "children": [
                    {
                      "name": "cryostat-ffb67db94",
                      "nodeType": "ReplicaSet",
                      "labels": {
                        "pod-template-hash": "ffb67db94",
                        "app.kubernetes.io/name": "cryostat",
                        "app.kubernetes.io/instance": "cryostat"
                      },
                      "children": [
                        {
                          "name": "cryostat-ffb67db94-jpx68",
                          "nodeType": "Pod",
                          "labels": {
                            "pod-template-hash": "ffb67db94",
                            "app.kubernetes.io/name": "cryostat",
                            "app.kubernetes.io/instance": "cryostat"
                          },
                          "children": [
                            {
                              "name": "service:jmx:rmi:///jndi/rmi://10.244.0.12:9091/jmxrmi",
                              "nodeType": "Endpoint",
                              "labels": {},
                              "target": {
                                "jvmId": "-WRGlnsyV__ze4pBW6eKUxvF62L6YVmGWTa7Xgf5Itk=",
                                "connectUrl": "service:jmx:rmi:///jndi/rmi://10.244.0.12:9091/jmxrmi",
                                "alias": "cryostat-ffb67db94-jpx68",
                                "labels": {
                                  "pod-template-hash": "ffb67db94",
                                  "app.kubernetes.io/name": "cryostat",
                                  "app.kubernetes.io/instance": "cryostat"
                                },
                                "annotations": {
                                  "platform": {},
                                  "cryostat": {
                                    "NAMESPACE": "cryostat1",
                                    "PORT": "9091",
                                    "REALM": "KubernetesApi",
                                    "HOST": "10.244.0.12",
                                    "POD_NAME": "cryostat-ffb67db94-jpx68"
                                  }
                                }
                              }
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            },
            {
              "name": "default",
              "nodeType": "Namespace",
              "labels": {},
              "children": [
                {
                  "name": "cryostat",
                  "nodeType": "Deployment",
                  "labels": {
                    "helm.sh/chart": "cryostat-0.2.0",
                    "app.kubernetes.io/managed-by": "Helm",
                    "app.kubernetes.io/name": "cryostat",
                    "app.kubernetes.io/instance": "cryostat",
                    "app.kubernetes.io/version": "latest"
                  },
                  "children": [
                    {
                      "name": "cryostat-6f4c6b58fc",
                      "nodeType": "ReplicaSet",
                      "labels": {
                        "pod-template-hash": "6f4c6b58fc",
                        "app.kubernetes.io/name": "cryostat",
                        "app.kubernetes.io/instance": "cryostat"
                      },
                      "children": [
                        {
                          "name": "cryostat-6f4c6b58fc-hr4jg",
                          "nodeType": "Pod",
                          "labels": {
                            "pod-template-hash": "6f4c6b58fc",
                            "app.kubernetes.io/name": "cryostat",
                            "app.kubernetes.io/instance": "cryostat"
                          },
                          "children": [
                            {
                              "name": "service:jmx:rmi:///jndi/rmi://10.244.0.11:9091/jmxrmi",
                              "nodeType": "Endpoint",
                              "labels": {},
                              "target": {
                                "jvmId": "a-NTvJxVlVQ2MjH-aX_PRWL17_MabOPjj1nDNS9ypus=",
                                "connectUrl": "service:jmx:rmi:///jndi/rmi://10.244.0.11:9091/jmxrmi",
                                "alias": "cryostat-6f4c6b58fc-hr4jg",
                                "labels": {
                                  "pod-template-hash": "6f4c6b58fc",
                                  "app.kubernetes.io/name": "cryostat",
                                  "app.kubernetes.io/instance": "cryostat"
                                },
                                "annotations": {
                                  "platform": {
                                    "kubectl.kubernetes.io/restartedAt": "2023-01-12T07:40:11-05:00"
                                  },
                                  "cryostat": {
                                    "NAMESPACE": "default",
                                    "PORT": "9091",
                                    "REALM": "KubernetesApi",
                                    "HOST": "10.244.0.11",
                                    "POD_NAME": "cryostat-6f4c6b58fc-hr4jg"
                                  }
                                }
                              }
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ],
      "name": "Universe",
      "nodeType": "Universe",
      "labels": {}
    }
  }
}

As expected, port-forwarding to expose the instance in default and then visiting the web-client shows both definitions in the target selection UI. If I select the instance running in cryostat1, confirmed by expanding the target selection to see details, I am able to interact with it as usual:

image

@andrewazores andrewazores marked this pull request as ready for review January 12, 2023 12:54
@github-actions
Copy link
Contributor

Test image available:

$ CRYOSTAT_IMAGE=ghcr.io/cryostatio/cryostat:pr-1290-20f9b689ff15aec9e8737d856a8fb954b2001c82 sh smoketest.sh

@ebaron
Copy link
Member

ebaron commented Jan 12, 2023

@ebaron thoughts on this approach?

Your approach to test the PR makes sense to me and I'm glad it worked! I'll give the code a closer look shortly.

ebaron
ebaron previously approved these changes Jan 12, 2023
Copy link
Member

@ebaron ebaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good and works as expected. Very nice!

@github-actions
Copy link
Contributor

Test image available:

$ CRYOSTAT_IMAGE=ghcr.io/cryostatio/cryostat:pr-1290-8a27d6679a86354844e316b372a09808c700f2ec sh smoketest.sh

@andrewazores andrewazores merged commit 79a4e23 into cryostatio:main Jan 12, 2023
@github-actions github-actions bot added the needs-triage Needs thorough attention from code reviewers label Jan 12, 2023
@andrewazores andrewazores deleted the crossns branch January 12, 2023 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request needs-documentation needs-triage Needs thorough attention from code reviewers safe-to-test
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants