diff --git a/cli/cmd/resources/instrumentor.go b/cli/cmd/resources/instrumentor.go index 29e3fe223..358a18c6a 100644 --- a/cli/cmd/resources/instrumentor.go +++ b/cli/cmd/resources/instrumentor.go @@ -2,13 +2,18 @@ package resources import ( "context" + "fmt" "github.com/odigos-io/odigos/cli/cmd/resources/resourcemanager" "github.com/odigos-io/odigos/cli/pkg/containers" + "github.com/odigos-io/odigos/cli/pkg/crypto" "github.com/odigos-io/odigos/cli/pkg/kube" "github.com/odigos-io/odigos/common" "sigs.k8s.io/controller-runtime/pkg/client" + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -18,10 +23,12 @@ import ( ) const ( - InstrumentorServiceName = "instrumentor" - InstrumentorDeploymentName = "odigos-instrumentor" - InstrumentorAppLabelValue = "odigos-instrumentor" - InstrumentorContainerName = "manager" + InstrumentorServiceName = "instrumentor" + InstrumentorDeploymentName = "odigos-instrumentor" + InstrumentorAppLabelValue = "odigos-instrumentor" + InstrumentorContainerName = "manager" + InstrumentorWebhookSecretName = "instrumentor-webhook-cert" + InstrumentorWebhookVolumeName = "webhook-cert" ) func NewInstrumentorServiceAccount(ns string) *corev1.ServiceAccount { @@ -219,6 +226,193 @@ func NewInstrumentorClusterRoleBinding(ns string) *rbacv1.ClusterRoleBinding { } } +func isCertManagerInstalled(ctx context.Context, c *kube.Client) bool { + // Check if CRD is installed + _, err := c.ApiExtensions.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, "issuers.cert-manager.io", metav1.GetOptions{}) + if err != nil { + return false + } + + return true +} + +func NewInstrumentorIssuer(ns string) *certv1.Issuer { + return &certv1.Issuer{ + TypeMeta: metav1.TypeMeta{ + Kind: "Issuer", + APIVersion: "cert-manager.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "selfsigned-issuer", + Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/name": "issuer", + "app.kubernetes.io/instance": "selfsigned-issuer", + "app.kubernetes.io/component": "certificate", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + }, + Spec: certv1.IssuerSpec{ + IssuerConfig: certv1.IssuerConfig{ + SelfSigned: &certv1.SelfSignedIssuer{}, + }, + }, + } +} + +func NewInstrumentorCertificate(ns string) *certv1.Certificate { + return &certv1.Certificate{ + TypeMeta: metav1.TypeMeta{ + Kind: "Certificate", + APIVersion: "cert-manager.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "serving-cert", + Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/name": "instrumentor-cert", + "app.kubernetes.io/instance": "instrumentor-cert", + "app.kubernetes.io/component": "certificate", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + }, + Spec: certv1.CertificateSpec{ + DNSNames: []string{ + fmt.Sprintf("odigos-instrumentor.%s.svc", ns), + fmt.Sprintf("odigos-instrumentor.%s.svc.cluster.local", ns), + }, + IssuerRef: cmmeta.ObjectReference{ + Kind: "Issuer", + Name: "selfsigned-issuer", + }, + SecretName: InstrumentorWebhookSecretName, + }, + } +} + +func NewInstrumentorService(ns string) *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "odigos-instrumentor", + Namespace: ns, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "webhook-server", + Port: 9443, + TargetPort: intstr.FromInt(9443), + }, + }, + Selector: map[string]string{ + "app.kubernetes.io/name": InstrumentorAppLabelValue, + }, + }, + } +} + +func NewMutatingWebhookConfiguration(ns string, caBundle []byte) *admissionregistrationv1.MutatingWebhookConfiguration { + webhook := &admissionregistrationv1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "MutatingWebhookConfiguration", + APIVersion: "admissionregistration.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "mutating-webhook-configuration", + Labels: map[string]string{ + "app.kubernetes.io/name": "pod-mutating-webhook", + "app.kubernetes.io/instance": "mutating-webhook-configuration", + "app.kubernetes.io/component": "webhook", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + { + Name: "pod-mutating-webhook.odigos.io", + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Name: "odigos-instrumentor", + Namespace: ns, + Path: ptrString("/mutate--v1-pod"), + Port: intPtr(9443), + }, + }, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.Create, + admissionregistrationv1.Update, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + Scope: ptrGeneric(admissionregistrationv1.NamespacedScope), + }, + }, + }, + FailurePolicy: ptrGeneric(admissionregistrationv1.Ignore), + ReinvocationPolicy: ptrGeneric(admissionregistrationv1.IfNeededReinvocationPolicy), + SideEffects: ptrGeneric(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: intPtr(10), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "odigos.io/inject-instrumentation": "true", + }, + }, + AdmissionReviewVersions: []string{ + "v1", + }, + }, + }, + } + + if caBundle == nil { + webhook.Annotations = map[string]string{ + "cert-manager.io/inject-ca-from": fmt.Sprintf("%s/serving-cert", ns), + } + } else { + webhook.Webhooks[0].ClientConfig.CABundle = caBundle + } + + return webhook +} + +func NewInstrumentorTLSSecret(ns string, cert *crypto.Certificate) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: InstrumentorWebhookSecretName, + Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/name": "instrumentor-cert", + "app.kubernetes.io/instance": "instrumentor-cert", + "app.kubernetes.io/component": "certificate", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + Annotations: map[string]string{ + "helm.sh/hook": "pre-install,pre-upgrade", + "helm.sh/hook-delete-policy": "before-hook-creation", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(cert.Cert), + "tls.key": []byte(cert.Key), + }, + } +} + func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, imagePrefix string, imageName string) *appsv1.Deployment { args := []string{ "--health-probe-bind-address=:8081", @@ -230,7 +424,7 @@ func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, args = append(args, "--telemetry-disabled") } - return &appsv1.Deployment{ + dep := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", @@ -290,6 +484,20 @@ func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, }, }, }, + Ports: []corev1.ContainerPort{ + { + Name: "webhook-server", + ContainerPort: 9443, + Protocol: corev1.ProtocolTCP, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: InstrumentorWebhookVolumeName, + ReadOnly: true, + MountPath: "/tmp/k8s-webhook-server/serving-certs", + }, + }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ "cpu": resource.MustParse("500m"), @@ -324,12 +532,25 @@ func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, SecurityContext: &corev1.PodSecurityContext{ RunAsNonRoot: ptrbool(true), }, + Volumes: []corev1.Volume{ + { + Name: InstrumentorWebhookVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: InstrumentorWebhookSecretName, + DefaultMode: ptrint32(420), + }, + }, + }, + }, }, }, Strategy: appsv1.DeploymentStrategy{}, MinReadySeconds: 0, }, } + + return dep } func ptrint32(i int32) *int32 { @@ -363,12 +584,43 @@ func NewInstrumentorResourceManager(client *kube.Client, ns string, config *comm func (a *instrumentorResourceManager) Name() string { return "Instrumentor" } func (a *instrumentorResourceManager) InstallFromScratch(ctx context.Context) error { + certManagerInstalled := isCertManagerInstalled(ctx, a.client) resources := []client.Object{ NewInstrumentorServiceAccount(a.ns), NewInstrumentorRoleBinding(a.ns), NewInstrumentorClusterRole(), NewInstrumentorClusterRoleBinding(a.ns), NewInstrumentorDeployment(a.ns, a.odigosVersion, a.config.TelemetryEnabled, a.config.ImagePrefix, a.config.InstrumentorImage), + NewInstrumentorService(a.ns), + } + + if certManagerInstalled { + resources = append([]client.Object{NewInstrumentorIssuer(a.ns), + NewInstrumentorCertificate(a.ns), + NewMutatingWebhookConfiguration(a.ns, nil), + }, + resources...) + } else { + ca, err := crypto.GenCA("odigos-instrumentor", 365) + if err != nil { + return fmt.Errorf("failed to generate CA: %w", err) + } + + altNames := []string{ + fmt.Sprintf("odigos-instrumentor.%s.svc", a.ns), + fmt.Sprintf("odigos-instrumentor.%s.svc.cluster.local", a.ns), + } + + cert, err := crypto.GenerateSignedCertificate("serving-cert", nil, altNames, 365, ca) + if err != nil { + return fmt.Errorf("failed to generate signed certificate: %w", err) + } + + resources = append([]client.Object{NewInstrumentorTLSSecret(a.ns, &cert), + NewMutatingWebhookConfiguration(a.ns, []byte(cert.Cert)), + }, + resources...) } + return a.client.ApplyResources(ctx, a.config.ConfigVersion, resources) } diff --git a/cli/cmd/resources/owntelemetry.go b/cli/cmd/resources/owntelemetry.go index 370b9ea96..934bf50d6 100644 --- a/cli/cmd/resources/owntelemetry.go +++ b/cli/cmd/resources/owntelemetry.go @@ -250,6 +250,14 @@ func int64Ptr(n int64) *int64 { return &n } +func ptrString(s string) *string { + return &s +} + +func ptrGeneric[T any](v T) *T { + return &v +} + type ownTelemetryResourceManager struct { client *kube.Client ns string diff --git a/cli/go.mod b/cli/go.mod index 66b1a7691..dab0338ce 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -3,6 +3,7 @@ module github.com/odigos-io/odigos/cli go 1.22.0 require ( + github.com/cert-manager/cert-manager v1.15.3 github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.7.0 github.com/odigos-io/odigos/api v0.0.0 @@ -22,31 +23,31 @@ require ( github.com/fatih/color v1.16.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/goccy/go-yaml v1.11.3 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/spdystream v0.4.0 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + sigs.k8s.io/gateway-api v1.1.0 // indirect ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/go-logr/logr v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -63,12 +64,12 @@ require ( golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/cli/go.sum b/cli/go.sum index 24b9458c0..c67c4944a 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,13 +1,14 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cert-manager/cert-manager v1.15.3 h1:/u9T0griwd5MegPfWbB7v0KcVcT9OJrEvPNhc9tl7xQ= +github.com/cert-manager/cert-manager v1.15.3/go.mod h1:stBge/DTvrhfQMB/93+Y62s+gQgZBsfL1o0C/4AL/mI= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -18,13 +19,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= @@ -51,12 +51,12 @@ github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2 github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -65,11 +65,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= @@ -109,12 +106,7 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= @@ -136,8 +128,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -164,8 +156,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -188,10 +180,8 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= @@ -204,12 +194,14 @@ k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGcXawNMouPECM1+F9BVxEaM= +k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= +sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/cli/pkg/crypto/crypto.go b/cli/pkg/crypto/crypto.go new file mode 100644 index 000000000..a60322bf9 --- /dev/null +++ b/cli/pkg/crypto/crypto.go @@ -0,0 +1,326 @@ +package crypto + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net" + "strings" + "time" +) + +// Source: https://github.com/Masterminds/sprig/blob/master/crypto.go +type Certificate struct { + Cert string + Key string +} + +func GenCA( + cn string, + daysValid int, +) (Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return Certificate{}, fmt.Errorf("error generating rsa key: %s", err) + } + + return generateCertificateAuthorityWithKeyInternal(cn, daysValid, priv) +} + +func GenerateSignedCertificate( + cn string, + ips []interface{}, + alternateDNS []string, + daysValid int, + ca Certificate, +) (Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return Certificate{}, fmt.Errorf("error generating rsa key: %s", err) + } + return generateSignedCertificateWithKeyInternal(cn, ips, alternateDNS, daysValid, ca, priv) +} + +func generateSignedCertificateWithKeyInternal( + cn string, + ips []interface{}, + alternateDNS []string, + daysValid int, + ca Certificate, + priv crypto.PrivateKey, +) (Certificate, error) { + cert := Certificate{} + + decodedSignerCert, _ := pem.Decode([]byte(ca.Cert)) + if decodedSignerCert == nil { + return cert, errors.New("unable to decode Certificate") + } + signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes) + if err != nil { + return cert, fmt.Errorf( + "error parsing Certificate: decodedSignerCert.Bytes: %s", + err, + ) + } + signerKey, err := parsePrivateKeyPEM(ca.Key) + if err != nil { + return cert, fmt.Errorf( + "error parsing private key: %s", + err, + ) + } + + template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid) + if err != nil { + return cert, err + } + + cert.Cert, cert.Key, err = getCertAndKey( + template, + priv, + signerCert, + signerKey, + ) + + return cert, err +} + +func parsePrivateKeyPEM(pemBlock string) (crypto.PrivateKey, error) { + block, _ := pem.Decode([]byte(pemBlock)) + if block == nil { + return nil, errors.New("no PEM data in input") + } + + if block.Type == "PRIVATE KEY" { + priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("decoding PEM as PKCS#8: %s", err) + } + return priv, nil + } else if !strings.HasSuffix(block.Type, " PRIVATE KEY") { + return nil, fmt.Errorf("no private key data in PEM block of type %s", block.Type) + } + + switch block.Type[:len(block.Type)-12] { // strip " PRIVATE KEY" + case "RSA": + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parsing RSA private key from PEM: %s", err) + } + return priv, nil + case "EC": + priv, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parsing EC private key from PEM: %s", err) + } + return priv, nil + case "DSA": + var k DSAKeyFormat + _, err := asn1.Unmarshal(block.Bytes, &k) + if err != nil { + return nil, fmt.Errorf("parsing DSA private key from PEM: %s", err) + } + priv := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, Q: k.Q, G: k.G, + }, + Y: k.Y, + }, + X: k.X, + } + return priv, nil + default: + return nil, fmt.Errorf("invalid private key type %s", block.Type) + } +} + +func generateCertificateAuthorityWithKeyInternal( + cn string, + daysValid int, + priv crypto.PrivateKey, +) (Certificate, error) { + ca := Certificate{} + + template, err := getBaseCertTemplate(cn, nil, nil, daysValid) + if err != nil { + return ca, err + } + // Override KeyUsage and IsCA + template.KeyUsage = x509.KeyUsageKeyEncipherment | + x509.KeyUsageDigitalSignature | + x509.KeyUsageCertSign + template.IsCA = true + + ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv) + + return ca, err +} + +func getCertAndKey( + template *x509.Certificate, + signeeKey crypto.PrivateKey, + parent *x509.Certificate, + signingKey crypto.PrivateKey, +) (string, string, error) { + signeePubKey, err := getPublicKey(signeeKey) + if err != nil { + return "", "", fmt.Errorf("error retrieving public key from signee key: %s", err) + } + derBytes, err := x509.CreateCertificate( + rand.Reader, + template, + parent, + signeePubKey, + signingKey, + ) + if err != nil { + return "", "", fmt.Errorf("error creating Certificate: %s", err) + } + + certBuffer := bytes.Buffer{} + if err := pem.Encode( + &certBuffer, + &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}, + ); err != nil { + return "", "", fmt.Errorf("error pem-encoding Certificate: %s", err) + } + + keyBuffer := bytes.Buffer{} + if err := pem.Encode( + &keyBuffer, + pemBlockForKey(signeeKey), + ); err != nil { + return "", "", fmt.Errorf("error pem-encoding key: %s", err) + } + + return certBuffer.String(), keyBuffer.String(), nil +} + +func getBaseCertTemplate( + cn string, + ips []interface{}, + dnsNames []string, + daysValid int, +) (*x509.Certificate, error) { + ipAddresses, err := getNetIPs(ips) + if err != nil { + return nil, err + } + + serialNumberUpperBound := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberUpperBound) + if err != nil { + return nil, err + } + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: cn, + }, + IPAddresses: ipAddresses, + DNSNames: dnsNames, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + BasicConstraintsValid: true, + }, nil +} + +func getNetIPs(ips []interface{}) ([]net.IP, error) { + if ips == nil { + return []net.IP{}, nil + } + var ipStr string + var ok bool + var netIP net.IP + netIPs := make([]net.IP, len(ips)) + for i, ip := range ips { + ipStr, ok = ip.(string) + if !ok { + return nil, fmt.Errorf("error parsing ip: %v is not a string", ip) + } + netIP = net.ParseIP(ipStr) + if netIP == nil { + return nil, fmt.Errorf("error parsing ip: %s", ipStr) + } + netIPs[i] = netIP + } + return netIPs, nil +} + +func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) { + if alternateDNS == nil { + return []string{}, nil + } + var dnsStr string + var ok bool + alternateDNSStrs := make([]string, len(alternateDNS)) + for i, dns := range alternateDNS { + dnsStr, ok = dns.(string) + if !ok { + return nil, fmt.Errorf( + "error processing alternate dns name: %v is not a string", + dns, + ) + } + alternateDNSStrs[i] = dnsStr + } + return alternateDNSStrs, nil +} + +func getPublicKey(priv crypto.PrivateKey) (crypto.PublicKey, error) { + switch k := priv.(type) { + case interface{ Public() crypto.PublicKey }: + return k.Public(), nil + case *dsa.PrivateKey: + return &k.PublicKey, nil + default: + return nil, fmt.Errorf("unable to get public key for type %T", priv) + } +} + +// DSAKeyFormat stores the format for DSA keys. +// Used by pemBlockForKey +type DSAKeyFormat struct { + Version int + P, Q, G, Y, X *big.Int +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *dsa.PrivateKey: + val := DSAKeyFormat{ + P: k.P, Q: k.Q, G: k.G, + Y: k.Y, X: k.X, + } + bytes, _ := asn1.Marshal(val) + return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes} + case *ecdsa.PrivateKey: + b, _ := x509.MarshalECPrivateKey(k) + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + // attempt PKCS#8 format for all other keys + b, err := x509.MarshalPKCS8PrivateKey(k) + if err != nil { + return nil + } + return &pem.Block{Type: "PRIVATE KEY", Bytes: b} + } +} diff --git a/docs/cli/odigos_diagnose.mdx b/docs/cli/odigos_diagnose.mdx new file mode 100644 index 000000000..5691d612e --- /dev/null +++ b/docs/cli/odigos_diagnose.mdx @@ -0,0 +1,30 @@ +--- +title: "odigos diagnose" +sidebarTitle: "odigos diagnose" +--- + +Retrieves Logs of all Odigos Components in the odigos-system namespace and CRDs of Actions, Instrumentation resources and more.
+The results will be saved in a compressed file for further troubleshooting. + +## Synopsis + +This command can be used for troubleshooting and observing Odigos state. +The file will be saved in this format: odigos_debug_ddmmyyyyhhmmss.tar.gz + + +```bash +odigos diagnose +``` + +## Output + +``` +odigos_debug_25092024102322.tar.gz +``` + + +## SEE ALSO + +* [odigos](/cli/odigos) - odigos CLI +* [odigos describe](/cli/odigos_describe) - Show details of a specific odigos entity + diff --git a/docs/mint.json b/docs/mint.json index 8f62c20c3..e76afb30d 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -80,6 +80,7 @@ "cli/odigos", "cli/odigos_cloud", "cli/odigos_describe", + "cli/odigos_diagnose", "cli/odigos_install", "cli/odigos_profile", "cli/odigos_ui", diff --git a/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx b/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx index 75f5cdf26..e26065162 100644 --- a/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx +++ b/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { SourcesOptionMenuWrapper } from './sources.option.menu.styled'; import { FilterSourcesOptions } from './filter.sources.options'; import { ActionSourcesOptions } from './action.sources.options'; +import { KeyvalButton, KeyvalText } from '@/design.system'; +import theme from '@/styles/palette'; export function SourcesOptionMenu({ setCurrentItem, @@ -12,6 +14,7 @@ export function SourcesOptionMenu({ selectedApplications, currentNamespace, onFutureApplyChange, + toggleFastSourcesSelection, }: any) { return ( @@ -28,6 +31,11 @@ export function SourcesOptionMenu({ selectedApplications={selectedApplications} onFutureApplyChange={onFutureApplyChange} /> + + + Fast Sources Selection + + ); } diff --git a/frontend/webapp/containers/setup/sources/fast-sources-selection.tsx b/frontend/webapp/containers/setup/sources/fast-sources-selection.tsx new file mode 100644 index 000000000..9f4edd3ac --- /dev/null +++ b/frontend/webapp/containers/setup/sources/fast-sources-selection.tsx @@ -0,0 +1,216 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { KeyvalButton, KeyvalLoader, KeyvalText } from '@/design.system'; +import { OVERVIEW, QUERIES, ROUTES } from '@/utils'; +import { getApplication, getNamespaces } from '@/services'; +import { + LoaderWrapper, + SectionContainerWrapper, +} from './sources.section.styled'; +import { NamespaceAccordion } from './namespace-accordion'; +import styled from 'styled-components'; +import theme from '@/styles/palette'; +import { useSources } from '@/hooks'; +import { useRouter } from 'next/navigation'; +import { setSources } from '@/store'; +import { useDispatch } from 'react-redux'; + +const TitleWrapper = styled.div` + margin-bottom: 24px; +`; + +const ButtonWrapper = styled.div` + position: absolute; + bottom: 24px; + width: 90%; +`; + +interface AccordionItem { + name: string; + kind: string; + instances: number; + app_instrumentation_labeled: boolean; + ns_instrumentation_labeled: boolean; + instrumentation_effective: boolean; + selected: boolean; +} + +interface AccordionData { + title: string; + items: AccordionItem[]; +} + +export function FastSourcesSelection({ sectionData, setSectionData }) { + const [accordionData, setAccordionData] = useState([]); + const router = useRouter(); + const dispatch = useDispatch(); + const { isLoading, data: namespaces } = useQuery( + [QUERIES.API_NAMESPACES], + getNamespaces + ); + + const { upsertSources } = useSources(); + + useEffect(() => { + if (namespaces) { + const accordionData = namespaces.namespaces.map((item, index) => ({ + title: item.name, + items: + sectionData?.[item.name]?.objects.map((app) => ({ + ...app, + selected: app.selected, + })) || [], + })); + setAccordionData(accordionData); + } + }, [namespaces]); + + const handleSetCurrentNamespace = async (selectedNs) => { + const currentNsApps = await getApplication(selectedNs.title); + + const updatedSectionData = { + ...sectionData, + [selectedNs.title]: { + selected_all: sectionData[selectedNs.title]?.selected_all || false, + future_selected: + sectionData[selectedNs.title]?.future_selected || false, + objects: currentNsApps.applications.map((app) => ({ + ...app, + selected: sectionData[selectedNs.title]?.objects?.find( + (a) => a.name === app.name + )?.selected, + })), + }, + }; + + const accordionData = namespaces.namespaces.map((item) => ({ + title: item.name, + items: updatedSectionData[item.name]?.objects + .map((app) => ({ + ...app, + selected: app.selected, + })) + .filter((app) => !app.instrumentation_effective), + })); + + // Update both sectionData and accordionData + setSectionData(updatedSectionData); + setAccordionData(accordionData); + }; + + const onSelectItemChange = (item: AccordionItem, ns: string) => { + const updatedAccordionData = accordionData.map((a_data: AccordionData) => { + if (a_data.title === ns) { + return { + ...a_data, + items: a_data.items.map((i: AccordionItem) => { + if (i.name === item.name) { + return { ...i, selected: !i.selected }; + } + return i; + }), + }; + } + return a_data; + }); + + const updatedSectionData = { + ...sectionData, + [ns]: { + ...sectionData[ns], + selected_all: accordionData[ns] + ?.find((a) => a.title === ns) + .items.every((i) => i.selected), + future_selected: accordionData[ns] + ?.find((a) => a.title === ns) + .items.some((i) => !i.selected), + objects: sectionData[ns].objects.map((a) => { + if (a.name === item.name) { + return { ...a, selected: !a.selected }; + } + return a; + }), + }, + }; + + setSectionData(updatedSectionData); + + // Update the accordion data state with the modified data + setAccordionData(updatedAccordionData); + }; + + const onSelectAllChange = (ns, value) => { + const updatedAccordionData = accordionData.map((a_data: AccordionData) => { + if (a_data.title === ns) { + return { + ...a_data, + items: a_data.items.map((i: AccordionItem) => { + return { ...i, selected: value }; + }), + }; + } + return a_data; + }); + + const updatedSectionData = { + ...sectionData, + [ns]: { + ...sectionData[ns], + objects: sectionData[ns]?.objects?.map((a) => { + return { ...a, selected: value }; + }), + }, + }; + + // Update the accordion data state with the modified data + setSectionData(updatedSectionData); + setAccordionData(updatedAccordionData); + }; + + function onConnectClick() { + const isSetup = window.location.pathname.includes('choose-sources'); + + if (isSetup) { + dispatch(setSources(sectionData)); + router.push(ROUTES.CHOOSE_DESTINATION); + return; + } + + upsertSources({ + sectionData, + onSuccess: () => router.push(`${ROUTES.SOURCES}?poll=true`), + onError: null, + }); + } + + if (isLoading) { + return ( + + + + ); + } + const isSetup = window.location.pathname.includes('choose-sources'); + return ( + + + Fast Sources Selection + +
+ handleSetCurrentNamespace(data)} + onSelectAllChange={onSelectAllChange} + /> +
+ + + + {isSetup ? 'Next' : OVERVIEW.CONNECT} + + + +
+ ); +} diff --git a/frontend/webapp/containers/setup/sources/namespace-accordion.tsx b/frontend/webapp/containers/setup/sources/namespace-accordion.tsx new file mode 100644 index 000000000..989e0e30e --- /dev/null +++ b/frontend/webapp/containers/setup/sources/namespace-accordion.tsx @@ -0,0 +1,125 @@ +import { KeyvalCheckbox, KeyvalText } from '@/design.system'; +import React, { useEffect, useState } from 'react'; +import { WhiteArrowIcon } from '@keyval-dev/design-system'; +import styled from 'styled-components'; +interface AccordionItemData { + title: string; + items: any[]; +} + +interface AccordionProps { + data: AccordionItemData[]; + setCurrentNamespace: (data: AccordionItemData) => void; + onSelectItem: (item: any, ns: string) => void; + onSelectAllChange: (ns, value) => void; +} + +const ArrowIconWrapper = styled.div<{ expanded: boolean }>` + margin-left: 24px; + transform: rotate(${({ expanded }) => (expanded ? '90deg' : '-90deg')}); +`; + +export function NamespaceAccordion({ + data, + setCurrentNamespace, + onSelectItem, + onSelectAllChange, +}: AccordionProps) { + return ( +
+ {data.map((itemData, index) => ( + + ))} +
+ ); +} + +interface AccordionItemProps { + data: any; + setCurrentNamespace: (data: AccordionItemData) => void; + onSelectItem: (item: any, ns: string) => void; + onSelectAllChange: (ns, value) => void; +} + +function AccordionItem({ + data, + setCurrentNamespace, + onSelectItem, + onSelectAllChange, +}: AccordionItemProps) { + const [isAllSelected, setIsAllSelected] = useState(false); + const [expanded, setExpanded] = useState(false); + + useEffect(() => { + const selectedItems = data.items?.filter((item) => item.selected); + setIsAllSelected( + selectedItems?.length === data?.items?.length && selectedItems?.length > 0 + ); + }, [data]); + + const handleSelectAllChange = (ns, value) => { + onSelectAllChange(ns, value); + setIsAllSelected(value); + }; + + const handleItemChange = (item) => { + onSelectItem(item, data.title); + }; + + const handleExpand = () => { + setCurrentNamespace(data); + setExpanded(!expanded); + }; + + return ( +
+
+ handleSelectAllChange(data.title, !isAllSelected)} + label={''} + /> +
+ + {data.title} + + + + +
+
+ {expanded && ( +
+ {data.items?.map((item, index) => ( +
+ handleItemChange(item)} + label={`${item.name} / ${item.kind.toLowerCase()}`} + /> +
+ ))} +
+ )} +
+ ); +} diff --git a/frontend/webapp/containers/setup/sources/sources.section.tsx b/frontend/webapp/containers/setup/sources/sources.section.tsx index 525bb821a..4df611d0f 100644 --- a/frontend/webapp/containers/setup/sources/sources.section.tsx +++ b/frontend/webapp/containers/setup/sources/sources.section.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; -import { KeyvalLoader } from '@/design.system'; +import { Drawer, KeyvalLoader } from '@/design.system'; import { NOTIFICATION, QUERIES } from '@/utils'; import { getApplication, getNamespaces } from '@/services'; import { SourcesList, SourcesOptionMenu } from '@/components/setup'; @@ -14,12 +14,14 @@ import { NamespaceConfiguration, SelectedSourcesConfiguration, } from '@/types'; +import { FastSourcesSelection } from './fast-sources-selection'; const DEFAULT = 'default'; export function SourcesSection({ sectionData, setSectionData }) { const [currentNamespace, setCurrentNamespace] = useState(); const [searchFilter, setSearchFilter] = useState(''); + const [isDrawerOpen, setDrawerOpen] = useState(false); const { isLoading, data, isError, error } = useQuery( [QUERIES.API_NAMESPACES], @@ -65,6 +67,8 @@ export function SourcesSection({ sectionData, setSectionData }) { ); }, [searchFilter, currentNamespace, sectionData]); + const toggleDrawer = () => setDrawerOpen(!isDrawerOpen); + async function onNameSpaceChange() { if (!currentNamespace || sectionData[currentNamespace?.name]) return; const namespace = await getApplication(currentNamespace?.name); @@ -171,6 +175,16 @@ export function SourcesSection({ sectionData, setSectionData }) { return ( + {isDrawerOpen && ( + + + + )} void; + position?: 'left' | 'right'; // Optional prop to specify the drawer opening side + width?: string; // Optional width control, defaults to 300px + children: React.ReactNode; +} + +// Styled-component for overlay +const Overlay = styled.div<{ isOpen: boolean }>` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; + transition: opacity 0.3s ease; + visibility: ${({ isOpen }) => (isOpen ? 'visible' : 'hidden')}; + z-index: 999; +`; + +// Styled-component for drawer container +const DrawerContainer = styled.div<{ + isOpen: boolean; + position: 'left' | 'right'; + width: string; +}>` + position: fixed; + top: 0; + bottom: 0; + ${({ position, width }) => position}: 0; + width: ${({ width }) => width}; + background-color: ${({ theme }) => theme.colors.light_dark}; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + transform: translateX( + ${({ isOpen, position }) => + isOpen ? '0' : position === 'left' ? '-100%' : '100%'} + ); + transition: transform 0.3s ease; + z-index: 1000; + overflow-y: auto; +`; + +export const Drawer: React.FC = ({ + isOpen, + onClose, + position = 'right', + width = '300px', + children, +}) => { + // Handle closing the drawer when escape key is pressed + useEffect(() => { + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isOpen) { + onClose(); + } + }; + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('keydown', handleEscape); + }; + }, [isOpen, onClose]); + + return ( + <> + + + {children} + + + ); +}; diff --git a/frontend/webapp/design.system/index.tsx b/frontend/webapp/design.system/index.tsx index d11dd61c2..2a95ab080 100644 --- a/frontend/webapp/design.system/index.tsx +++ b/frontend/webapp/design.system/index.tsx @@ -34,3 +34,4 @@ export { OdigosTable as Table } from './table'; export { YMLEditor } from './yml.editor'; export { CodeBlock } from './code-block'; export { Conditions } from './condition.alert'; +export { Drawer } from './drawer'; diff --git a/helm/odigos/templates/_helpers.tpl b/helm/odigos/templates/_helpers.tpl new file mode 100644 index 000000000..f2a0003b9 --- /dev/null +++ b/helm/odigos/templates/_helpers.tpl @@ -0,0 +1,13 @@ +{{- define "utils.certManagerApiVersion" -}} +{{- if .Capabilities.APIVersions.Has "cert-manager.io/v1" -}} +cert-manager.io/v1 +{{- else if .Capabilities.APIVersions.Has "cert-manager.io/v1beta1" -}} +cert-manager.io/v1beta1 +{{- else if .Capabilities.APIVersions.Has "cert-manager.io/v1alpha2" -}} +cert-manager.io/v1alpha2 +{{- else if .Capabilities.APIVersions.Has "certmanager.k8s.io/v1alpha1" -}} +certmanager.k8s.io/v1alpha1 +{{- else -}} +{{- print "" -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/helm/odigos/templates/instrumentor/certificates.yaml b/helm/odigos/templates/instrumentor/certificates.yaml new file mode 100644 index 000000000..ab55f6c51 --- /dev/null +++ b/helm/odigos/templates/instrumentor/certificates.yaml @@ -0,0 +1,36 @@ +{{- $certManagerApiVersion := include "utils.certManagerApiVersion" . -}} +{{- if $certManagerApiVersion }} +apiVersion: {{ $certManagerApiVersion }} +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: issuer + app.kubernetes.io/instance: selfsigned-issuer + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos +spec: + selfSigned: {} +--- +apiVersion: {{ $certManagerApiVersion }} +kind: Certificate +metadata: + name: serving-cert + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: instrumentor-cert + app.kubernetes.io/instance: instrumentor-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos +spec: + dnsNames: + - "odigos-instrumentor.{{ .Release.Namespace }}.svc" + - "odigos-instrumentor.{{ .Release.Namespace }}.svc.cluster.local" + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: instrumentor-webhook-cert +{{- end }} \ No newline at end of file diff --git a/helm/odigos/templates/instrumentor/deployment.yaml b/helm/odigos/templates/instrumentor/deployment.yaml index a5a3e58ac..6093737de 100644 --- a/helm/odigos/templates/instrumentor/deployment.yaml +++ b/helm/odigos/templates/instrumentor/deployment.yaml @@ -29,6 +29,10 @@ spec: {{- else }} image: "{{ .Values.instrumentor.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" {{- end }} + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP env: - name: OTEL_SERVICE_NAME value: instrumentor @@ -36,6 +40,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: webhook-cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true envFrom: - configMapRef: name: odigos-own-telemetry-otel-config @@ -59,6 +67,11 @@ spec: securityContext: runAsNonRoot: true serviceAccountName: odigos-instrumentor + volumes: + - name: webhook-cert + secret: + secretName: instrumentor-webhook-cert + defaultMode: 420 terminationGracePeriodSeconds: 10 {{- if .Values.imagePullSecrets }} imagePullSecrets: diff --git a/helm/odigos/templates/instrumentor/service.yaml b/helm/odigos/templates/instrumentor/service.yaml new file mode 100644 index 000000000..7d1857813 --- /dev/null +++ b/helm/odigos/templates/instrumentor/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: odigos-instrumentor + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: webhook-server + port: 9443 + targetPort: 9443 + selector: + app.kubernetes.io/name: odigos-instrumentor \ No newline at end of file diff --git a/helm/odigos/templates/instrumentor/webhook.yaml b/helm/odigos/templates/instrumentor/webhook.yaml new file mode 100644 index 000000000..41ed17f26 --- /dev/null +++ b/helm/odigos/templates/instrumentor/webhook.yaml @@ -0,0 +1,66 @@ +{{- $certManagerApiVersion := include "utils.certManagerApiVersion" . -}} +{{- $altNames := list (printf "odigos-instrumentor.%s.svc" .Release.Namespace) (printf "odigos-instrumentor.%s.svc.cluster.local" .Release.Namespace) -}} +{{- $ca := genCA "serving-cert" 365 -}} +{{- $cert := genSignedCert "serving-cert" nil $altNames 365 $ca -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration + labels: + app.kubernetes.io/name: pod-mutating-webhook + app.kubernetes.io/instance: mutating-webhook-configuration + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos +{{- if $certManagerApiVersion }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/serving-cert +{{- end }} +webhooks: + - name: pod-mutating-webhook.odigos.io + clientConfig: +{{- if not $certManagerApiVersion }} + caBundle: {{ $ca.Cert | b64enc }} +{{- end }} + service: + name: odigos-instrumentor + namespace: {{ .Release.Namespace }} + path: /mutate--v1-pod + port: 9443 + rules: + - operations: + - CREATE + - UPDATE + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + scope: Namespaced + failurePolicy: Ignore + reinvocationPolicy: IfNeeded + sideEffects: None + objectSelector: + matchLabels: + odigos.io/inject-instrumentation: "true" + timeoutSeconds: 10 + admissionReviewVersions: ["v1"] +--- +{{- if not $certManagerApiVersion }} +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: instrumentor-webhook-cert + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: instrumentor-cert + app.kubernetes.io/instance: instrumentor-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos + annotations: + "helm.sh/hook": "pre-install,pre-upgrade" + "helm.sh/hook-delete-policy": "before-hook-creation" +data: + tls.crt: {{ $cert.Cert | b64enc }} + tls.key: {{ $cert.Key | b64enc }} +{{- end }} \ No newline at end of file diff --git a/instrumentor/PROJECT b/instrumentor/PROJECT index 83a4eb6c1..46f3dd370 100644 --- a/instrumentor/PROJECT +++ b/instrumentor/PROJECT @@ -44,4 +44,11 @@ resources: kind: DaemonSet path: k8s.io/api/apps/v1 version: v1 +- domain: odigos.io + kind: Pod + path: k8s.io/api/core/v1 + version: v1 + webhooks: + defaulting: true + webhookVersion: v1 version: "3" diff --git a/instrumentor/controllers/instrumentationdevice/manager.go b/instrumentor/controllers/instrumentationdevice/manager.go index 8fb5d8b68..8b37b0015 100644 --- a/instrumentor/controllers/instrumentationdevice/manager.go +++ b/instrumentor/controllers/instrumentationdevice/manager.go @@ -165,5 +165,14 @@ func SetupWithManager(mgr ctrl.Manager) error { return err } + err = builder. + WebhookManagedBy(mgr). + For(&corev1.Pod{}). + WithDefaulter(&PodsWebhook{}). + Complete() + if err != nil { + return err + } + return nil } diff --git a/instrumentor/controllers/instrumentationdevice/pods_webhook.go b/instrumentor/controllers/instrumentationdevice/pods_webhook.go new file mode 100644 index 000000000..45fb0594c --- /dev/null +++ b/instrumentor/controllers/instrumentationdevice/pods_webhook.go @@ -0,0 +1,34 @@ +package instrumentationdevice + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/webhook" + + corev1 "k8s.io/api/core/v1" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "k8s.io/apimachinery/pkg/runtime" +) + +type PodsWebhook struct{} + +var _ webhook.CustomDefaulter = &PodsWebhook{} + +func (p *PodsWebhook) Default(ctx context.Context, obj runtime.Object) error { + // TODO(edenfed): add object selector to mutatingwebhookconfiguration + log := logf.FromContext(ctx) + pod, ok := obj.(*corev1.Pod) + if !ok { + return fmt.Errorf("expected a Pod but got a %T", obj) + } + + if pod.Annotations == nil { + pod.Annotations = map[string]string{} + } + + //pod.Annotations["odigos.io/instrumented-webhook"] = "true" + log.V(0).Info("Defaulted Pod", "name", pod.Name) + return nil +} diff --git a/instrumentor/controllers/instrumentationdevice/suite_test.go b/instrumentor/controllers/instrumentationdevice/suite_test.go index 4b7681edc..c8caedc57 100644 --- a/instrumentor/controllers/instrumentationdevice/suite_test.go +++ b/instrumentor/controllers/instrumentationdevice/suite_test.go @@ -21,6 +21,8 @@ import ( "path/filepath" "testing" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "github.com/odigos-io/odigos/instrumentor/internal/testutil" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -93,8 +95,14 @@ var _ = BeforeSuite(func() { odigosConfiguration := testutil.NewMockOdigosConfig() Expect(k8sClient.Create(testCtx, odigosConfiguration)).Should(Succeed()) + webhookInstallOptions := &testEnv.WebhookInstallOptions k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme.Scheme, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), }) Expect(err).ToNot(HaveOccurred()) diff --git a/odiglet/Dockerfile b/odiglet/Dockerfile index 8d59a507e..3c50c1509 100644 --- a/odiglet/Dockerfile +++ b/odiglet/Dockerfile @@ -61,7 +61,7 @@ RUN yarn --cwd ./odigos/agents/nodejs-native-community --production --frozen-loc FROM --platform=$BUILDPLATFORM busybox:1.36.1 AS dotnet-builder WORKDIR /dotnet-instrumentation -ARG DOTNET_OTEL_VERSION=v1.7.0 +ARG DOTNET_OTEL_VERSION=v1.8.0 ARG TARGETARCH RUN if [ "$TARGETARCH" = "arm64" ]; then \ echo "arm64" > /tmp/arch_suffix; \ @@ -93,7 +93,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ WORKDIR /instrumentations # Java -ARG JAVA_OTEL_VERSION=v2.7.0 +ARG JAVA_OTEL_VERSION=v2.8.0 ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$JAVA_OTEL_VERSION/opentelemetry-javaagent.jar /instrumentations/java/javaagent.jar RUN chmod 644 /instrumentations/java/javaagent.jar diff --git a/tests/common/traceql_runner.sh b/tests/common/traceql_runner.sh index b36bd8e3c..a77d02f6d 100755 --- a/tests/common/traceql_runner.sh +++ b/tests/common/traceql_runner.sh @@ -42,7 +42,7 @@ function process_yaml_file() { one_hour=3600 start_epoch=$(($current_epoch - one_hour)) end_epoch=$(($current_epoch + one_hour)) - response=$(kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/api/search\?end=$end_epoch\&start=$start_epoch\&q=$encoded_query) + response=$(kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/api/search\?end=$end_epoch\&start=$start_epoch\&q=$encoded_query\&limit=50) num_of_traces=$(echo $response | jq '.traces | length') # if num_of_traces not equal to expected_count if [ "$num_of_traces" -ne "$expected_count" ]; then diff --git a/tests/e2e/cli-upgrade/chainsaw-test.yaml b/tests/e2e/cli-upgrade/chainsaw-test.yaml index 9a07dbf3f..481afb7ee 100644 --- a/tests/e2e/cli-upgrade/chainsaw-test.yaml +++ b/tests/e2e/cli-upgrade/chainsaw-test.yaml @@ -16,7 +16,10 @@ spec: else helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 fi - assert: file: assert-tempo-running.yaml diff --git a/tests/e2e/fe-synthetic/chainsaw-test.yaml b/tests/e2e/fe-synthetic/chainsaw-test.yaml index 8c8df6d1f..db9280410 100644 --- a/tests/e2e/fe-synthetic/chainsaw-test.yaml +++ b/tests/e2e/fe-synthetic/chainsaw-test.yaml @@ -13,7 +13,10 @@ spec: content: | helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml diff --git a/tests/e2e/helm-chart/chainsaw-test.yaml b/tests/e2e/helm-chart/chainsaw-test.yaml index 81790c4a9..637bedb81 100644 --- a/tests/e2e/helm-chart/chainsaw-test.yaml +++ b/tests/e2e/helm-chart/chainsaw-test.yaml @@ -13,7 +13,10 @@ spec: content: | helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml - name: Wait for destination to be ready diff --git a/tests/e2e/multi-apps/chainsaw-test.yaml b/tests/e2e/multi-apps/chainsaw-test.yaml index 4b04f38eb..8dfeb4d47 100644 --- a/tests/e2e/multi-apps/chainsaw-test.yaml +++ b/tests/e2e/multi-apps/chainsaw-test.yaml @@ -13,7 +13,10 @@ spec: content: | helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml - name: Wait for destination to be ready diff --git a/tests/e2e/workload-lifecycle/01-assert-apps-installed.yaml b/tests/e2e/workload-lifecycle/01-assert-apps-installed.yaml index 8270f4f81..c5c739814 100644 --- a/tests/e2e/workload-lifecycle/01-assert-apps-installed.yaml +++ b/tests/e2e/workload-lifecycle/01-assert-apps-installed.yaml @@ -110,3 +110,88 @@ status: started: true phase: Running --- + +apiVersion: v1 +kind: Pod +metadata: + labels: + app: java-supported-version + namespace: default +status: + containerStatuses: + - name: java-supported-version + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: java-azul + namespace: default +status: + containerStatuses: + - name: java-azul + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: java-supported-docker-env + namespace: default +status: + containerStatuses: + - name: java-supported-docker-env + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: java-supported-manifest-env + namespace: default +status: + containerStatuses: + - name: java-supported-manifest-env + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: java-latest-version + namespace: default +status: + containerStatuses: + - name: java-latest-version + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: java-old-version + namespace: default +status: + containerStatuses: + - name: java-old-version + ready: true + restartCount: 0 + started: true + phase: Running +--- \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/01-assert-instrumented.yaml b/tests/e2e/workload-lifecycle/01-assert-instrumented.yaml index e4010e4d4..3c5c69e53 100644 --- a/tests/e2e/workload-lifecycle/01-assert-instrumented.yaml +++ b/tests/e2e/workload-lifecycle/01-assert-instrumented.yaml @@ -195,3 +195,82 @@ status: healthy: true # no need to verify all properties, as this is not the intent of this test --- + +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + namespace: default + name: deployment-java-supported-version +status: + conditions: + - message: "Instrumentation device applied successfully" + observedGeneration: 1 + reason: InstrumentationDeviceApplied + status: "True" + type: AppliedInstrumentationDevice +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + namespace: default + name: deployment-java-azul +status: + conditions: + - message: "Instrumentation device applied successfully" + observedGeneration: 1 + reason: InstrumentationDeviceApplied + status: "True" + type: AppliedInstrumentationDevice +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + namespace: default + name: deployment-java-supported-docker-env +status: + conditions: + - message: "Instrumentation device applied successfully" + observedGeneration: 1 + reason: InstrumentationDeviceApplied + status: "True" + type: AppliedInstrumentationDevice +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + namespace: default + name: deployment-java-supported-manifest-env +status: + conditions: + - message: "Instrumentation device applied successfully" + observedGeneration: 1 + reason: InstrumentationDeviceApplied + status: "True" + type: AppliedInstrumentationDevice +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + namespace: default + name: deployment-java-latest-version +status: + conditions: + - message: "Instrumentation device applied successfully" + observedGeneration: 1 + reason: InstrumentationDeviceApplied + status: "True" + type: AppliedInstrumentationDevice +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + namespace: default + name: deployment-java-old-version +status: + conditions: + - message: "Instrumentation device applied successfully" + observedGeneration: 1 + reason: InstrumentationDeviceApplied + status: "True" + type: AppliedInstrumentationDevice +--- \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/01-assert-runtime-detected.yaml b/tests/e2e/workload-lifecycle/01-assert-runtime-detected.yaml index 5f09c8e78..42a62b05f 100644 --- a/tests/e2e/workload-lifecycle/01-assert-runtime-detected.yaml +++ b/tests/e2e/workload-lifecycle/01-assert-runtime-detected.yaml @@ -142,3 +142,110 @@ spec: language: javascript runtimeVersion: 20.17.0 --- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-java-supported-version + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: java-supported-version +spec: + runtimeDetails: + - containerName: java-supported-version + language: java + runtimeVersion: 17.0.12+7 +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-java-azul + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: java-azul +spec: + runtimeDetails: + - containerName: java-azul + language: java +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-java-supported-docker-env + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: java-supported-docker-env +spec: + runtimeDetails: + - containerName: java-supported-docker-env + language: java + envVars: + - name: JAVA_OPTS + value: "-Dthis.does.not.exist=true" + runtimeVersion: 17.0.12+7 + +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-java-supported-manifest-env + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: java-supported-manifest-env +spec: + runtimeDetails: + - containerName: java-supported-manifest-env + language: java + envVars: + - name: JAVA_OPTS + value: "-Dnot.work=true" + runtimeVersion: 17.0.12+7 + +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-java-latest-version + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: java-latest-version +spec: + runtimeDetails: + - containerName: java-latest-version + language: java +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-java-old-version + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: java-old-version +spec: + runtimeDetails: + - containerName: java-old-version + language: java +--- \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/01-assert-workloads.yaml b/tests/e2e/workload-lifecycle/01-assert-workloads.yaml index 5a2150b01..da7d3a206 100644 --- a/tests/e2e/workload-lifecycle/01-assert-workloads.yaml +++ b/tests/e2e/workload-lifecycle/01-assert-workloads.yaml @@ -230,3 +230,202 @@ status: replicas: 1 updatedReplicas: 1 --- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "2" + generation: 2 + labels: + app: java-supported-version + name: java-supported-version + namespace: default +spec: + selector: + matchLabels: + app: java-supported-version + template: + spec: + containers: + - image: java-supported-version:v0.0.1 + name: java-supported-version + ports: + - containerPort: 3000 + protocol: TCP + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "2" + generation: 2 + labels: + app: java-azul + name: java-azul + namespace: default +spec: + selector: + matchLabels: + app: java-azul + template: + spec: + containers: + - image: java-azul:v0.0.1 + name: java-azul + ports: + - containerPort: 3000 + protocol: TCP + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "2" + generation: 2 + labels: + app: java-supported-docker-env + name: java-supported-docker-env + namespace: default +spec: + selector: + matchLabels: + app: java-supported-docker-env + template: + spec: + containers: + - image: java-supported-docker-env:v0.0.1 + name: java-supported-docker-env + ports: + - containerPort: 3000 + protocol: TCP + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" + env: + - name: JAVA_OPTS + value: "-Dthis.does.not.exist=true -javaagent:/var/odigos/java/javaagent.jar" + +status: + availableReplicas: 1 + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "2" + generation: 2 + labels: + app: java-supported-manifest-env + name: java-supported-manifest-env + namespace: default +spec: + selector: + matchLabels: + app: java-supported-manifest-env + template: + spec: + containers: + - image: java-supported-manifest-env:v0.0.1 + name: java-supported-manifest-env + ports: + - containerPort: 3000 + protocol: TCP + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" + env: + - name: JAVA_OPTS + value: "-Dnot.work=true -javaagent:/var/odigos/java/javaagent.jar" + +status: + availableReplicas: 1 + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "2" + generation: 2 + labels: + app: java-latest-version + name: java-latest-version + namespace: default +spec: + selector: + matchLabels: + app: java-latest-version + template: + spec: + containers: + - image: java-latest-version:v0.0.1 + name: java-latest-version + ports: + - containerPort: 3000 + protocol: TCP + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "2" + generation: 2 + labels: + app: java-old-version + name: java-old-version + namespace: default +spec: + selector: + matchLabels: + app: java-old-version + template: + spec: + containers: + - image: java-old-version:v0.0.1 + name: java-old-version + ports: + - containerPort: 3000 + protocol: TCP + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/01-generate-traffic.yaml b/tests/e2e/workload-lifecycle/01-generate-traffic.yaml index 7f021f667..63c5ab05f 100644 --- a/tests/e2e/workload-lifecycle/01-generate-traffic.yaml +++ b/tests/e2e/workload-lifecycle/01-generate-traffic.yaml @@ -31,3 +31,9 @@ spec: curl -s --fail http://nodejs-manifest-env:3000 curl -s --fail http://cpp-http-server:3000 curl -s --fail http://language-change:3000 + curl -s --fail http://java-supported-version:3000 + curl -s --fail http://java-supported-docker-env:3000 + curl -s --fail http://java-azul:3000 + curl -s --fail http://java-supported-manifest-env:3000 + curl -s --fail http://java-latest-version:3000 + curl -s --fail http://java-old-version:3000 diff --git a/tests/e2e/workload-lifecycle/01-install-test-apps.yaml b/tests/e2e/workload-lifecycle/01-install-test-apps.yaml index 9d985beca..1257a17b4 100644 --- a/tests/e2e/workload-lifecycle/01-install-test-apps.yaml +++ b/tests/e2e/workload-lifecycle/01-install-test-apps.yaml @@ -281,3 +281,244 @@ spec: - protocol: TCP port: 3000 --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-supported-version + namespace: default + labels: + app: java-supported-version +spec: + selector: + matchLabels: + app: java-supported-version + template: + metadata: + labels: + app: java-supported-version + spec: + containers: + - name: java-supported-version + image: java-supported-version:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- +kind: Service +apiVersion: v1 +metadata: + name: java-supported-version + namespace: default +spec: + selector: + app: java-supported-version + ports: + - protocol: TCP + port: 3000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-azul + namespace: default + labels: + app: java-azul +spec: + selector: + matchLabels: + app: java-azul + template: + metadata: + labels: + app: java-azul + spec: + containers: + - name: java-azul + image: java-azul:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- +kind: Service +apiVersion: v1 +metadata: + name: java-azul + namespace: default +spec: + selector: + app: java-azul + ports: + - protocol: TCP + port: 3000 +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-supported-docker-env + namespace: default + labels: + app: java-supported-docker-env +spec: + selector: + matchLabels: + app: java-supported-docker-env + template: + metadata: + labels: + app: java-supported-docker-env + spec: + containers: + - name: java-supported-docker-env + image: java-supported-docker-env:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 + + +--- +kind: Service +apiVersion: v1 +metadata: + name: java-supported-docker-env + namespace: default +spec: + selector: + app: java-supported-docker-env + ports: + - protocol: TCP + port: 3000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-supported-manifest-env + namespace: default + labels: + app: java-supported-manifest-env +spec: + selector: + matchLabels: + app: java-supported-manifest-env + template: + metadata: + labels: + app: java-supported-manifest-env + spec: + containers: + - name: java-supported-manifest-env + image: java-supported-manifest-env:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + env: + - name: JAVA_OPTS + value: "-Dnot.work=true" + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 + +--- +kind: Service +apiVersion: v1 +metadata: + name: java-supported-manifest-env + namespace: default +spec: + selector: + app: java-supported-manifest-env + ports: + - protocol: TCP + port: 3000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-latest-version + namespace: default + labels: + app: java-latest-version +spec: + selector: + matchLabels: + app: java-latest-version + template: + metadata: + labels: + app: java-latest-version + spec: + containers: + - name: java-latest-version + image: java-latest-version:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- +kind: Service +apiVersion: v1 +metadata: + name: java-latest-version + namespace: default +spec: + selector: + app: java-latest-version + ports: + - protocol: TCP + port: 3000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-old-version + namespace: default + labels: + app: java-old-version +spec: + selector: + matchLabels: + app: java-old-version + template: + metadata: + labels: + app: java-old-version + spec: + containers: + - name: java-old-version + image: java-old-version:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- +kind: Service +apiVersion: v1 +metadata: + name: java-old-version + namespace: default +spec: + selector: + app: java-old-version + ports: + - protocol: TCP + port: 3000 +--- \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/01-wait-for-trace.yaml b/tests/e2e/workload-lifecycle/01-wait-for-trace.yaml index 03f73a6ec..19a88c112 100644 --- a/tests/e2e/workload-lifecycle/01-wait-for-trace.yaml +++ b/tests/e2e/workload-lifecycle/01-wait-for-trace.yaml @@ -6,6 +6,12 @@ query: | { resource.service.name = "nodejs-latest-version" } || { resource.service.name = "nodejs-dockerfile-env" } || { resource.service.name = "nodejs-manifest-env" } || - { resource.service.name = "language-change" } + { resource.service.name = "language-change" } || + { resource.service.name = "java-supported-version" } || + { resource.service.name = "java-latest-version" } || + { resource.service.name = "java-old-version" } || + { resource.service.name = "java-supported-docker-env" } || + { resource.service.name = "java-supported-manifest-env" } || + { resource.service.name = "java-azul" } expected: - count: 5 + count: 11 diff --git a/tests/e2e/workload-lifecycle/02-assert-workload-update.yaml b/tests/e2e/workload-lifecycle/02-assert-workload-update.yaml index 367e7263a..98294993e 100644 --- a/tests/e2e/workload-lifecycle/02-assert-workload-update.yaml +++ b/tests/e2e/workload-lifecycle/02-assert-workload-update.yaml @@ -212,3 +212,167 @@ status: readyReplicas: 1 replicas: 1 updatedReplicas: 1 + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generation: 4 # on step 2, the manifest was updated (1->2) + labels: + app: java-supported-version + name: java-supported-version + namespace: default +spec: + selector: + matchLabels: + app: java-supported-version + template: + spec: + containers: + - image: java-supported-version:v0.0.1 + name: java-supported-version + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 4 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generation: 4 # on step 2, the manifest was updated (1->2) + labels: + app: java-azul + name: java-azul + namespace: default +spec: + selector: + matchLabels: + app: java-azul + template: + spec: + containers: + - image: java-azul:v0.0.1 + name: java-azul + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 4 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generation: 4 # on step 2, the manifest was updated (1->2) + labels: + app: java-supported-docker-env + name: java-supported-docker-env + namespace: default +spec: + selector: + matchLabels: + app: java-supported-docker-env + template: + spec: + containers: + - image: java-supported-docker-env:v0.0.1 + name: java-supported-docker-env + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 4 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generation: 4 # on step 2, the manifest was updated (1->2) + labels: + app: java-supported-manifest-env + name: java-supported-manifest-env + namespace: default +spec: + selector: + matchLabels: + app: java-supported-manifest-env + template: + spec: + containers: + - image: java-supported-manifest-env:v0.0.1 + name: java-supported-manifest-env + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 4 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generation: 4 # on step 2, the manifest was updated (1->2) + labels: + app: java-latest-version + name: java-latest-version + namespace: default +spec: + selector: + matchLabels: + app: java-latest-version + template: + spec: + containers: + - image: java-latest-version:v0.0.1 + name: java-latest-version + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 4 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generation: 4 # on step 2, the manifest was updated (1->2) + labels: + app: java-old-version + name: java-old-version + namespace: default +spec: + selector: + matchLabels: + app: java-old-version + template: + spec: + containers: + - image: java-old-version:v0.0.1 + name: java-old-version + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" +status: + availableReplicas: 1 + observedGeneration: 4 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/02-update-workload-manifests.yaml b/tests/e2e/workload-lifecycle/02-update-workload-manifests.yaml index 961a712c6..b34785929 100644 --- a/tests/e2e/workload-lifecycle/02-update-workload-manifests.yaml +++ b/tests/e2e/workload-lifecycle/02-update-workload-manifests.yaml @@ -201,3 +201,179 @@ spec: ports: - containerPort: 3000 --- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: java-supported-version + namespace: default + labels: + app: java-supported-version +spec: + selector: + matchLabels: + app: java-supported-version + template: + metadata: + labels: + app: java-supported-version + annotations: + odigos-test-step: "2" + spec: + containers: + - name: java-supported-version + image: java-supported-version:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: java-azul + namespace: default + labels: + app: java-azul +spec: + selector: + matchLabels: + app: java-azul + template: + metadata: + labels: + app: java-azul + annotations: + odigos-test-step: "2" + spec: + containers: + - name: java-azul + image: java-azul:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: java-supported-docker-env + namespace: default + labels: + app: java-supported-docker-env +spec: + selector: + matchLabels: + app: java-supported-docker-env + template: + metadata: + labels: + app: java-supported-docker-env + annotations: + odigos-test-step: "2" + spec: + containers: + - name: java-supported-docker-env + image: java-supported-docker-env:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: java-supported-manifest-env + namespace: default + labels: + app: java-supported-manifest-env +spec: + selector: + matchLabels: + app: java-supported-manifest-env + template: + metadata: + labels: + app: java-supported-manifest-env + annotations: + odigos-test-step: "2" + spec: + containers: + - name: java-supported-manifest-env + image: java-supported-manifest-env:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: java-latest-version + namespace: default + labels: + app: java-latest-version +spec: + selector: + matchLabels: + app: java-latest-version + template: + metadata: + labels: + app: java-latest-version + annotations: + odigos-test-step: "2" + spec: + containers: + - name: java-latest-version + image: java-latest-version:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 +--- + +kind: Deployment +apiVersion: apps/v1 +metadata: + name: java-old-version + namespace: default + labels: + app: java-old-version +spec: + selector: + matchLabels: + app: java-old-version + template: + metadata: + labels: + app: java-old-version + annotations: + odigos-test-step: "2" + spec: + containers: + - name: java-old-version + image: java-old-version:v0.0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 20 \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml b/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml index 5642ea109..a984e8109 100644 --- a/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml +++ b/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml @@ -5,6 +5,13 @@ query: | { resource.service.name = "nodejs-minimum-version" } || { resource.service.name = "nodejs-latest-version" } || { resource.service.name = "nodejs-dockerfile-env" } || - { resource.service.name = "nodejs-manifest-env" } + { resource.service.name = "nodejs-manifest-env" } || + { resource.service.name = "language-change" } || + { resource.service.name = "java-supported-version" } || + { resource.service.name = "java-latest-version" } || + { resource.service.name = "java-old-version" } || + { resource.service.name = "java-supported-docker-env" } || + { resource.service.name = "java-supported-manifest-env" } || + { resource.service.name = "java-azul" } expected: - count: 8 # 4 before +4 new ones + count: 21 # 11 before +10 new ones diff --git a/tests/e2e/workload-lifecycle/README.md b/tests/e2e/workload-lifecycle/README.md index a0d6a344a..e51cd0123 100644 --- a/tests/e2e/workload-lifecycle/README.md +++ b/tests/e2e/workload-lifecycle/README.md @@ -42,6 +42,43 @@ This e2e test verify various scenarios related to the lifecycle of workloads in - Application uses NODE_OPTIONS environment variable in the k8s deployment manifest to set one `--require` flag and another `--max-old-space-size` flag. - This workload verifies that after instrumentation is applied, those 2 options still works as expected. +## Java Workloads + +### java-supported-version + +- Java version 17 (minimum support version is 8) +- NODE_VERSION environment variable set in the image - detected by runtime inspection +- Should not be instrumented + +### java-latest-version + +- Java latest version (minimum support version is 8) +- This workload verifies that we support the latest version of Java + +### java-old-version + +- Java version 11 (minimum support version is 8) +- Instrumentation device should be added, agent should load and report traces correctly. +- This workload verifies that we support the old versions. + +### java-azul + +- Java version 17 (minimum support version is 8) +- This workload checks a special JRE named Azul, which specialize at low-latency and high-throughput applications. + +### java-supported-docker-env + +- Java version 17 (minimum support version is 8) +- Application uses JAVA_OPTS environment variable in the dockerfile. +- This workload verifies that after instrumentation is applied, those 2 options still works as expected. + +### java-supported-manifest-env + +- Java version 17 (minimum support version is 8) +- Application uses JAVA_OPTS environment variable in the k8s deployment manifest. +- This workload verifies that after instrumentation is applied, those 2 options still works as expected. + + ## CPP Workloads - Workload with a language odigos does not support. diff --git a/tests/e2e/workload-lifecycle/chainsaw-test.yaml b/tests/e2e/workload-lifecycle/chainsaw-test.yaml index 28de24290..e2f229572 100644 --- a/tests/e2e/workload-lifecycle/chainsaw-test.yaml +++ b/tests/e2e/workload-lifecycle/chainsaw-test.yaml @@ -9,7 +9,7 @@ spec: - name: Build and Load Test App Images try: - script: - timeout: 100s + timeout: 200s content: | docker build -t nodejs-unsupported-version:v0.0.1 -f services/nodejs-http-server/unsupported-version.Dockerfile services/nodejs-http-server kind load docker-image nodejs-unsupported-version:v0.0.1 @@ -25,15 +25,32 @@ spec: kind load docker-image nodejs-manifest-env:v0.0.1 docker build -t cpp-http-server:v0.0.1 -f services/cpp-http-server/Dockerfile services/cpp-http-server kind load docker-image cpp-http-server:v0.0.1 + docker build -t java-supported-version:v0.0.1 -f services/java-http-server/java-supported-version.Dockerfile services/java-http-server + kind load docker-image java-supported-version:v0.0.1 + docker build -t java-azul:v0.0.1 -f services/java-http-server/java-azul.Dockerfile services/java-http-server + kind load docker-image java-azul:v0.0.1 + docker build -t java-supported-docker-env:v0.0.1 -f services/java-http-server/java-supported-docker-env.Dockerfile services/java-http-server + kind load docker-image java-supported-docker-env:v0.0.1 + docker build -t java-supported-manifest-env:v0.0.1 -f services/java-http-server/java-supported-manifest-env.Dockerfile services/java-http-server + kind load docker-image java-supported-manifest-env:v0.0.1 + docker build -t java-latest-version:v0.0.1 -f services/java-http-server/java-latest-version.Dockerfile services/java-http-server + kind load docker-image java-latest-version:v0.0.1 + docker build -t java-old-version:v0.0.1 -f services/java-http-server/java-old-version.Dockerfile services/java-http-server + kind load docker-image java-old-version:v0.0.1 - name: Prepare destination try: - script: timeout: 60s content: | - helm repo add grafana https://grafana.github.io/helm-charts - helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm repo add grafana https://grafana.github.io/helm-charts + helm repo update + helm install e2e-tests grafana/tempo \ + -n traces --create-namespace \ + --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml - name: Wait for destination to be ready @@ -60,16 +77,24 @@ spec: file: 01-instrument-ns.yaml - assert: file: 01-assert-runtime-detected.yaml + - name: '01 Add Destination' try: - apply: file: 01-add-destination.yaml - - assert: - file: 01-assert-pipeline.yaml + - name: Assert Pipeline + try: + - assert: + file: 01-assert-pipeline.yaml + - name: Assert instrumented + try: - assert: file: 01-assert-instrumented.yaml + - name: Assert workload + try: - assert: file: 01-assert-workloads.yaml + - name: '01 - Generate Traffic' # at this point, we know the expected services are instrumented because we asserted the instrumentation instance # send traffic to all services to verify they are working as expected and verify traces for instrumented ones @@ -78,7 +103,21 @@ spec: timeout: 60s content: | set -e - + + NAMESPACE="default" + DEPLOYMENTS=$(kubectl get deployments -n $NAMESPACE -o jsonpath='{.items[*].metadata.name}') + + + for DEPLOYMENT in $DEPLOYMENTS; do + echo "Waiting for deployment $DEPLOYMENT to finish rollout..." + kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE + if [ $? -ne 0 ]; then + echo "Deployment $DEPLOYMENT failed to finish rollout." + exit 1 + fi + done + + # Apply the job kubectl apply -f 01-generate-traffic.yaml @@ -88,12 +127,15 @@ spec: # Delete the job kubectl delete -f 01-generate-traffic.yaml + + - name: '01 - Wait for Traces' try: - script: timeout: 60s content: | + sleep 20 while true; do ../../common/traceql_runner.sh 01-wait-for-trace.yaml if [ $? -eq 0 ]; then @@ -124,16 +166,33 @@ spec: timeout: 60s content: | set -e + + NAMESPACE="default" + DEPLOYMENTS=$(kubectl get deployments -n $NAMESPACE -o jsonpath='{.items[*].metadata.name}') + + + for DEPLOYMENT in $DEPLOYMENTS; do + echo "Waiting for deployment $DEPLOYMENT to finish rollout..." + kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE + if [ $? -ne 0 ]; then + echo "Deployment $DEPLOYMENT failed to finish rollout." + exit 1 + fi + done + + kubectl apply -f 01-generate-traffic.yaml job_name=$(kubectl get -f 01-generate-traffic.yaml -o=jsonpath='{.metadata.name}') kubectl wait --for=condition=complete job/$job_name kubectl delete -f 01-generate-traffic.yaml - + - name: '02 - Wait for Traces' try: - script: timeout: 60s content: | + sleep 20 + while true; do ../../common/traceql_runner.sh 02-wait-for-trace.yaml if [ $? -eq 0 ]; then @@ -143,54 +202,3 @@ spec: ../../common/flush_traces.sh fi done - - - # - name: Verify Trace - Context Propagation - # try: - # - script: - # content: | - # ../../common/traceql_runner.sh tracesql/context-propagation.yaml - # catch: - # - podLogs: - # name: odiglet - # namespace: odigos-system - # - name: Verify Trace - Resource Attributes - # try: - # - script: - # content: | - # ../../common/traceql_runner.sh tracesql/resource-attributes.yaml - # catch: - # - podLogs: - # name: odiglet - # namespace: odigos-system - # - name: Verify Trace - Span Attributes - # try: - # - script: - # timeout: 60s - # content: | - # ../../common/traceql_runner.sh tracesql/span-attributes.yaml - # catch: - # - podLogs: - # name: odiglet - # namespace: odigos-system - - # - name: Start Odigos UI in background - # try: - # - script: - # timeout: 60s - # content: | - # ../../common/ui-tests/start_odigos_ui.sh - - # - name: Run Cypress tests should pass - # try: - # - script: - # timeout: 60s - # content: | - # ../../common/ui-tests/run_cypress_tests.sh - - # - name: Stop Odigos UI - # try: - # - script: - # timeout: 60s - # content: | - # ../../common/ui-tests/stop_ui_and_clean.sh diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/java-azul.Dockerfile b/tests/e2e/workload-lifecycle/services/java-http-server/java-azul.Dockerfile new file mode 100644 index 000000000..b8fbaa4fd --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/java-azul.Dockerfile @@ -0,0 +1,9 @@ +FROM maven:3.8.5-openjdk-17 AS build +COPY src /home/app/src +COPY pom.xml /home/app +RUN mvn -f /home/app/pom.xml clean package + +FROM azul/zulu-openjdk-alpine:17-jre +COPY --from=build /home/app/target/*.jar /app/java-azul.jar +USER 15000 +CMD ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseZGC", "-jar", "/app/java-azul.jar"] diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/java-latest-version.Dockerfile b/tests/e2e/workload-lifecycle/services/java-http-server/java-latest-version.Dockerfile new file mode 100644 index 000000000..bfd753e5a --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/java-latest-version.Dockerfile @@ -0,0 +1,9 @@ +FROM maven:latest AS build +COPY src /home/app/src +COPY pom.xml /home/app +RUN mvn -f /home/app/pom.xml clean package + +FROM eclipse-temurin:latest +COPY --from=build /home/app/target/*.jar /app/java-latest-version.jar +USER 15000 +CMD ["java","-jar", "/app/java-latest-version.jar"] \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/java-old-version.Dockerfile b/tests/e2e/workload-lifecycle/services/java-http-server/java-old-version.Dockerfile new file mode 100644 index 000000000..e390ebf8b --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/java-old-version.Dockerfile @@ -0,0 +1,9 @@ +FROM maven:3.8.5-openjdk-11 AS build +COPY src /home/app/src +COPY pom11.xml /home/app +RUN mvn -f /home/app/pom11.xml clean package + +FROM eclipse-temurin:11-jre-jammy +COPY --from=build /home/app/target/*.jar /app/java-old-version.jar +USER 15000 +CMD ["java","-jar", "/app/java-old-version.jar"] \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-docker-env.Dockerfile b/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-docker-env.Dockerfile new file mode 100644 index 000000000..2853ca826 --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-docker-env.Dockerfile @@ -0,0 +1,10 @@ +FROM maven:3.8.5-openjdk-17 AS build +COPY src /home/app/src +COPY pom.xml /home/app +RUN mvn -f /home/app/pom.xml clean package + +FROM eclipse-temurin:17-jre-jammy +ENV JAVA_OPTS="-Dthis.does.not.exist=true" +COPY --from=build /home/app/target/*.jar /app/java-supported-docker-env.jar +USER 15000 +CMD ["sh", "-c", "java $JAVA_OPTS -jar /app/java-supported-docker-env.jar"] diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-manifest-env.Dockerfile b/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-manifest-env.Dockerfile new file mode 100644 index 000000000..16857797e --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-manifest-env.Dockerfile @@ -0,0 +1,9 @@ +FROM maven:3.8.5-openjdk-17 AS build +COPY src /home/app/src +COPY pom.xml /home/app +RUN mvn -f /home/app/pom.xml clean package + +FROM eclipse-temurin:17-jre-jammy +COPY --from=build /home/app/target/*.jar /app/java-supported-manifest-env.jar +USER 15000 +CMD ["java","-jar", "/app/java-supported-manifest-env.jar"] \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-version.Dockerfile b/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-version.Dockerfile new file mode 100644 index 000000000..6acc08f29 --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/java-supported-version.Dockerfile @@ -0,0 +1,9 @@ +FROM maven:3.8.5-openjdk-17 AS build +COPY src /home/app/src +COPY pom.xml /home/app +RUN mvn -f /home/app/pom.xml clean package + +FROM eclipse-temurin:17-jre-jammy +COPY --from=build /home/app/target/*.jar /app/java-supported-version.jar +USER 15000 +CMD ["java","-jar", "/app/java-supported-version.jar"] \ No newline at end of file diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/mvnw b/tests/e2e/workload-lifecycle/services/java-http-server/mvnw new file mode 100755 index 000000000..66df28542 --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# https://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/mvnw.cmd b/tests/e2e/workload-lifecycle/services/java-http-server/mvnw.cmd new file mode 100644 index 000000000..95ba6f54a --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/pom.xml b/tests/e2e/workload-lifecycle/services/java-http-server/pom.xml new file mode 100644 index 000000000..e28e27a12 --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.2 + + + dev.keyval.kvshop + frontend + 0.0.1-SNAPSHOT + frontend + Demo project for Spring Boot + + 8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/pom11.xml b/tests/e2e/workload-lifecycle/services/java-http-server/pom11.xml new file mode 100644 index 000000000..9d7e993f5 --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/pom11.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.12 + + + dev.keyval.kvshop + frontend + 0.0.1-SNAPSHOT + frontend + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + + + diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/src/main/java/dev/keyval/kvshop/frontend/FrontendApplication.java b/tests/e2e/workload-lifecycle/services/java-http-server/src/main/java/dev/keyval/kvshop/frontend/FrontendApplication.java new file mode 100644 index 000000000..c8a065e8a --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/src/main/java/dev/keyval/kvshop/frontend/FrontendApplication.java @@ -0,0 +1,13 @@ +package dev.keyval.kvshop.frontend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class FrontendApplication { + + public static void main(String[] args) { + SpringApplication.run(FrontendApplication.class, args); + } + +} diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/src/main/java/dev/keyval/kvshop/frontend/HelloController.java b/tests/e2e/workload-lifecycle/services/java-http-server/src/main/java/dev/keyval/kvshop/frontend/HelloController.java new file mode 100644 index 000000000..224665ed1 --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/src/main/java/dev/keyval/kvshop/frontend/HelloController.java @@ -0,0 +1,22 @@ +package dev.keyval.kvshop.frontend; + +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.beans.factory.annotation.Autowired; + +@RestController +public class HelloController { + + @Autowired + public HelloController() { + // Constructor logic can be added here if necessary + } + + @CrossOrigin(origins = "*") + @GetMapping("/") + public String printHello() { + System.out.println("Received request for Hello"); + return "Hello"; + } +} diff --git a/tests/e2e/workload-lifecycle/services/java-http-server/src/main/resources/application.properties b/tests/e2e/workload-lifecycle/services/java-http-server/src/main/resources/application.properties new file mode 100644 index 000000000..b0f26698c --- /dev/null +++ b/tests/e2e/workload-lifecycle/services/java-http-server/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=3000