diff --git a/cli/cmd/resources/odiglet.go b/cli/cmd/resources/odiglet.go index ea054386d..14c8639b8 100644 --- a/cli/cmd/resources/odiglet.go +++ b/cli/cmd/resources/odiglet.go @@ -8,6 +8,7 @@ import ( "github.com/odigos-io/odigos/cli/pkg/autodetect" cmdcontext "github.com/odigos-io/odigos/cli/pkg/cmd_context" + "github.com/odigos-io/odigos/common/consts" "github.com/odigos-io/odigos/cli/cmd/resources/odigospro" "github.com/odigos-io/odigos/cli/cmd/resources/resourcemanager" @@ -24,12 +25,16 @@ import ( ) const ( - OdigletServiceName = "odiglet" - OdigletDaemonSetName = "odiglet" - OdigletAppLabelValue = "odiglet" - OdigletContainerName = "odiglet" - OdigletImageName = "keyval/odigos-odiglet" - OdigletEnterpriseImageName = "keyval/odigos-enterprise-odiglet" + OdigletDaemonSetName = "odiglet" + OdigletAppLabelValue = OdigletDaemonSetName + OdigletServiceAccountName = OdigletDaemonSetName + OdigletRoleName = OdigletDaemonSetName + OdigletRoleBindingName = OdigletDaemonSetName + OdigletClusterRoleName = OdigletDaemonSetName + OdigletClusterRoleBindingName = OdigletDaemonSetName + OdigletContainerName = "odiglet" + OdigletImageName = "keyval/odigos-odiglet" + OdigletEnterpriseImageName = "keyval/odigos-enterprise-odiglet" ) func NewOdigletServiceAccount(ns string) *corev1.ServiceAccount { @@ -39,20 +44,21 @@ func NewOdigletServiceAccount(ns string) *corev1.ServiceAccount { APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "odiglet", + Name: OdigletServiceAccountName, Namespace: ns, }, } } -func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { - clusterrole := &rbacv1.ClusterRole{ +func NewOdigletRole(ns string) *rbacv1.Role { + return &rbacv1.Role{ TypeMeta: metav1.TypeMeta{ - Kind: "ClusterRole", + Kind: "Role", APIVersion: "rbac.authorization.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "odiglet", + Name: OdigletRoleName, + Namespace: ns, }, Rules: []rbacv1.PolicyRule{ { @@ -62,16 +68,7 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { "watch", }, APIGroups: []string{"odigos.io"}, - Resources: []string{"odigosconfigurations", "collectorsgroups", "collectorsgroups/status"}, - }, - { - Verbs: []string{ - "get", - "list", - "watch", - }, - APIGroups: []string{""}, - Resources: []string{"configmaps"}, + Resources: []string{"collectorsgroups", "collectorsgroups/status"}, }, { Verbs: []string{ @@ -79,20 +76,49 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { "list", "watch", }, - APIGroups: []string{""}, - Resources: []string{ - "pods", - }, + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + ResourceNames: []string{consts.OdigosConfigurationName}, }, + }, + } +} + +func NewOdigletRoleBinding(ns string) *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: "rbac.authorization.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: OdigletRoleBindingName, + Namespace: ns, + }, + Subjects: []rbacv1.Subject{ { - Verbs: []string{ - "get", - }, - APIGroups: []string{""}, - Resources: []string{ - "pods/status", - }, + Kind: "ServiceAccount", + Name: OdigletServiceAccountName, + Namespace: ns, }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: OdigletRoleName, + }, + } +} + +func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { + clusterrole := &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: "rbac.authorization.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: OdigletClusterRoleName, + }, + Rules: []rbacv1.PolicyRule{ { Verbs: []string{ "get", @@ -101,34 +127,7 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { }, APIGroups: []string{""}, Resources: []string{ - "nodes", - }, - }, - { - Verbs: []string{ - "get", - "list", - "watch", - }, - APIGroups: []string{"apps"}, - Resources: []string{"deployments"}, - }, - { - Verbs: []string{ - "get", - }, - APIGroups: []string{"apps"}, - Resources: []string{ - "deployments/status", - }, - }, - { - Verbs: []string{ - "get", - }, - APIGroups: []string{"apps"}, - Resources: []string{ - "deployments/finalizers", + "pods", "pods/status", }, }, { @@ -137,25 +136,9 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { "list", "watch", }, - APIGroups: []string{"apps"}, - Resources: []string{"statefulsets"}, - }, - { - Verbs: []string{ - "get", - }, - APIGroups: []string{"apps"}, - Resources: []string{ - "statefulsets/status", - }, - }, - { - Verbs: []string{ - "get", - }, - APIGroups: []string{"apps"}, + APIGroups: []string{""}, Resources: []string{ - "statefulsets/finalizers", + "nodes", }, }, { @@ -165,16 +148,7 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { "watch", }, APIGroups: []string{"apps"}, - Resources: []string{"daemonsets"}, - }, - { - Verbs: []string{ - "get", - }, - APIGroups: []string{"apps"}, - Resources: []string{ - "daemonsets/status", - }, + Resources: []string{"deployments", "daemonsets", "statefulsets"}, }, { Verbs: []string{ @@ -182,7 +156,7 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { }, APIGroups: []string{"apps"}, Resources: []string{ - "daemonsets/finalizers", + "deployments/status", "daemonsets/status", "statefulsets/status", }, }, { @@ -253,17 +227,6 @@ func NewOdigletClusterRole(psp bool) *rbacv1.ClusterRole { "instrumentationinstances/status", }, }, - { - Verbs: []string{ - "get", - "list", - "watch", - }, - APIGroups: []string{""}, - Resources: []string{ - "namespaces", - }, - }, { Verbs: []string{ "get", @@ -319,19 +282,19 @@ func NewOdigletClusterRoleBinding(ns string) *rbacv1.ClusterRoleBinding { APIVersion: "rbac.authorization.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "odiglet", + Name: OdigletClusterRoleBindingName, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", - Name: "odiglet", + Name: OdigletServiceAccountName, Namespace: ns, }, }, RoleRef: rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", - Name: "odiglet", + Name: OdigletClusterRoleName, }, } } @@ -349,7 +312,7 @@ func NewSCCRoleBinding(ns string) *rbacv1.RoleBinding { Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", - Name: "odiglet", + Name: OdigletServiceAccountName, Namespace: ns, }, { @@ -641,7 +604,7 @@ func NewOdigletDaemonSet(ns string, version string, imagePrefix string, imageNam }, }, DNSPolicy: "ClusterFirstWithHostNet", - ServiceAccountName: "odiglet", + ServiceAccountName: OdigletServiceAccountName, HostNetwork: true, HostPID: true, PriorityClassName: "system-node-critical", @@ -723,6 +686,8 @@ func (a *odigletResourceManager) InstallFromScratch(ctx context.Context) error { resources := []kube.Object{ NewOdigletServiceAccount(a.ns), + NewOdigletRole(a.ns), + NewOdigletRoleBinding(a.ns), NewOdigletClusterRole(a.config.Psp), NewOdigletClusterRoleBinding(a.ns), } diff --git a/common/config/elasticsearch.go b/common/config/elasticsearch.go index 8818bfb00..ef0c8ebc7 100644 --- a/common/config/elasticsearch.go +++ b/common/config/elasticsearch.go @@ -12,8 +12,10 @@ const ( ElasticsearchUrlKey = "ELASTICSEARCH_URL" esTracesIndexKey = "ES_TRACES_INDEX" esLogsIndexKey = "ES_LOGS_INDEX" + esBasicAuthKey = "ELASTICSEARCH_BASIC_AUTH_ENABLED" // unused in this file, currently UI only (we do not want to break existing setups by requiring this boolean) esUsername = "ELASTICSEARCH_USERNAME" esPassword = "ELASTICSEARCH_PASSWORD" + esTlsKey = "ELASTICSEARCH_TLS_ENABLED" // unused in this file, currently UI only (we do not want to break existing setups by requiring this boolean) esCaPem = "ELASTICSEARCH_CA_PEM" ) @@ -46,21 +48,20 @@ func (e *Elasticsearch) ModifyConfig(dest ExporterConfigurer, currentConfig *Con logIndexVal = "log_index" } - basicAuthUsername := dest.GetConfig()[esUsername] - caPem := dest.GetConfig()[esCaPem] - exporterConfig := GenericMap{ "endpoints": []string{parsedURL}, "traces_index": traceIndexVal, "logs_index": logIndexVal, } + caPem := dest.GetConfig()[esCaPem] if caPem != "" { exporterConfig["tls"] = GenericMap{ "ca_pem": caPem, } } + basicAuthUsername := dest.GetConfig()[esUsername] if basicAuthUsername != "" { exporterConfig["user"] = basicAuthUsername exporterConfig["password"] = fmt.Sprintf("${%s}", esPassword) diff --git a/destinations/data/elasticsearch.yaml b/destinations/data/elasticsearch.yaml index 23c49e841..089093c3d 100644 --- a/destinations/data/elasticsearch.yaml +++ b/destinations/data/elasticsearch.yaml @@ -36,6 +36,19 @@ spec: type: text placeholder: 'log_index' tooltip: 'The name of the index where logs will be stored. Defaults to log_index' + - name: ELASTICSEARCH_BASIC_AUTH_ENABLED + displayName: Enable HTTP Basic Authentication + componentType: checkbox + initialValue: false + componentProps: + required: false + customReadDataLabels: + - condition: 'true' + title: 'Basic Auth' + value: 'Enabled' + - condition: 'false' + title: 'Basic Auth' + value: 'Disabled' - name: ELASTICSEARCH_USERNAME displayName: Username componentType: input @@ -43,6 +56,8 @@ spec: type: text required: false tooltip: 'Username used for HTTP Basic Authentication' + renderCondition: ['ELASTICSEARCH_BASIC_AUTH_ENABLED', '==', 'true'] + hideFromReadData: ['ELASTICSEARCH_BASIC_AUTH_ENABLED', '==', 'false'] - name: ELASTICSEARCH_PASSWORD displayName: Password componentType: input @@ -51,6 +66,22 @@ spec: required: false tooltip: 'Password used for HTTP Basic Authentication' secret: true + renderCondition: ['ELASTICSEARCH_BASIC_AUTH_ENABLED', '==', 'true'] + hideFromReadData: ['ELASTICSEARCH_BASIC_AUTH_ENABLED', '==', 'false'] + - name: ELASTICSEARCH_TLS_ENABLED + displayName: Enable TLS + componentType: checkbox + initialValue: false + componentProps: + required: false + tooltip: 'Secure connection (Transport Layer Security)' + customReadDataLabels: + - condition: 'true' + title: 'TLS' + value: 'Encrypted' + - condition: 'false' + title: 'TLS' + value: 'Unencrypted' - name: ELASTICSEARCH_CA_PEM displayName: CA Certificate componentType: textarea @@ -58,4 +89,6 @@ spec: type: text required: false placeholder: '-----BEGIN CERTIFICATE-----' - tooltip: 'When using https, provide the CA certificate to verify the server. If empty uses system root CA' + tooltip: 'When using TLS, provide the CA certificate to verify the server. If empty uses system root CA' + renderCondition: ['ELASTICSEARCH_TLS_ENABLED', '==', 'true'] + hideFromReadData: ['true'] diff --git a/destinations/data/jaeger.yaml b/destinations/data/jaeger.yaml index 30a925a68..a39e76b4b 100644 --- a/destinations/data/jaeger.yaml +++ b/destinations/data/jaeger.yaml @@ -21,10 +21,19 @@ spec: type: text required: true - name: JAEGER_TLS_ENABLED - displayName: Enable TLS (secure connection) + displayName: Enable TLS componentType: checkbox + initialValue: false componentProps: required: false + tooltip: 'Secure connection (Transport Layer Security)' + customReadDataLabels: + - condition: 'true' + title: 'TLS' + value: 'Encrypted' + - condition: 'false' + title: 'TLS' + value: 'Unencrypted' - name: JAEGER_CA_PEM displayName: Certificate Authority componentType: textarea @@ -33,4 +42,6 @@ spec: required: false placeholder: '-----BEGIN CERTIFICATE-----' tooltip: 'When using TLS, provide the CA certificate to verify the server. If empty uses system root CA' + renderCondition: ['JAEGER_TLS_ENABLED', '==', 'true'] + hideFromReadData: ['true'] testConnectionSupported: true diff --git a/destinations/model.go b/destinations/model.go index 6d62860a0..790e5cbfe 100644 --- a/destinations/model.go +++ b/destinations/model.go @@ -28,17 +28,24 @@ type Spec struct { Supported bool `yaml:"supported"` } } - Fields []Field `yaml:"fields"` - TestConnectionSupported bool `yaml:"testConnectionSupported"` + Fields []Field `yaml:"fields"` + TestConnectionSupported bool `yaml:"testConnectionSupported"` +} + +type CustomReadDataLabel struct { + Condition string `yaml:"condition"` + Title string `yaml:"title"` + Value string `yaml:"value"` } type Field struct { - Name string `yaml:"name"` - DisplayName string `yaml:"displayName"` - VideoURL string `yaml:"videoUrl"` - ThumbnailURL string `yaml:"thumbnailUrl"` - ComponentType string `yaml:"componentType"` - ComponentProps map[string]interface{} `yaml:"componentProps"` - Secret bool `yaml:"secret"` - InitialValue string `yaml:"initialValue"` + Name string `yaml:"name"` + DisplayName string `yaml:"displayName"` + ComponentType string `yaml:"componentType"` + ComponentProps map[string]interface{} `yaml:"componentProps"` + Secret bool `yaml:"secret"` + InitialValue string `yaml:"initialValue"` + RenderCondition []string `yaml:"renderCondition"` + HideFromReadData []string `yaml:"hideFromReadData"` + CustomReadDataLabels []*CustomReadDataLabel `yaml:"customReadDataLabels"` } diff --git a/docs/backends/elasticsearch.mdx b/docs/backends/elasticsearch.mdx index 0851088a9..a02f21202 100644 --- a/docs/backends/elasticsearch.mdx +++ b/docs/backends/elasticsearch.mdx @@ -65,7 +65,9 @@ spec: ELASTICSEARCH_URL: # ES_TRACES_INDEX: # ES_LOGS_INDEX: + # ELASTICSEARCH_BASIC_AUTH_ENABLED: # ELASTICSEARCH_USERNAME: + # ELASTICSEARCH_TLS_ENABLED: # ELASTICSEARCH_CA_PEM: # Note: The commented fields above are optional. destinationName: elasticsearch diff --git a/docs/backends/jaeger.mdx b/docs/backends/jaeger.mdx index c28925087..c3ef6f6ac 100644 --- a/docs/backends/jaeger.mdx +++ b/docs/backends/jaeger.mdx @@ -43,7 +43,7 @@ metadata: spec: data: JAEGER_URL: - # JAEGER_TLS_ENABLED: + # JAEGER_TLS_ENABLED: # JAEGER_CA_PEM: # Note: The commented fields above are optional. destinationName: jaeger diff --git a/frontend/endpoints/destinations.go b/frontend/endpoints/destinations.go index 0be797fb8..5bb6d2ebf 100644 --- a/frontend/endpoints/destinations.go +++ b/frontend/endpoints/destinations.go @@ -101,14 +101,34 @@ type GetDestinationDetailsResponse struct { Fields []Field `json:"fields"` } +type CustomReadDataLabel struct { + Condition string `json:"condition"` + Title string `json:"title"` + Value string `json:"value"` +} + type Field struct { - Name string `json:"name"` - DisplayName string `json:"display_name"` - ComponentType string `json:"component_type"` - ComponentProperties map[string]interface{} `json:"component_properties"` - VideoUrl string `json:"video_url,omitempty"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - InitialValue string `json:"initial_value,omitempty"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + ComponentType string `json:"component_type"` + ComponentProperties map[string]interface{} `json:"component_properties"` + Secret bool `json:"secret,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + RenderCondition []string `json:"render_condition,omitempty"` + HideFromReadData []string `json:"hide_from_read_data,omitempty"` + CustomReadDataLabels []*CustomReadDataLabel `json:"custom_read_data_labels,omitempty"` +} + +func convertCustomReadDataLabels(labels []*destinations.CustomReadDataLabel) []*CustomReadDataLabel { + var result []*CustomReadDataLabel + for _, label := range labels { + result = append(result, &CustomReadDataLabel{ + Condition: label.Condition, + Title: label.Title, + Value: label.Value, + }) + } + return result } func GetDestinationTypeDetails(c *gin.Context) { @@ -124,13 +144,15 @@ func GetDestinationTypeDetails(c *gin.Context) { var resp GetDestinationDetailsResponse for _, field := range destTypeConfig.Spec.Fields { resp.Fields = append(resp.Fields, Field{ - Name: field.Name, - DisplayName: field.DisplayName, - ComponentType: field.ComponentType, - ComponentProperties: field.ComponentProps, - VideoUrl: field.VideoURL, - ThumbnailURL: field.ThumbnailURL, - InitialValue: field.InitialValue, + Name: field.Name, + DisplayName: field.DisplayName, + ComponentType: field.ComponentType, + ComponentProperties: field.ComponentProps, + Secret: field.Secret, + InitialValue: field.InitialValue, + RenderCondition: field.RenderCondition, + HideFromReadData: field.HideFromReadData, + CustomReadDataLabels: convertCustomReadDataLabels(field.CustomReadDataLabels), }) } diff --git a/frontend/graph/conversions.go b/frontend/graph/conversions.go index 5667f3f30..dab54dd16 100644 --- a/frontend/graph/conversions.go +++ b/frontend/graph/conversions.go @@ -4,32 +4,33 @@ import ( "time" "github.com/odigos-io/odigos/api/odigos/v1alpha1" - gqlmodel "github.com/odigos-io/odigos/frontend/graph/model" + "github.com/odigos-io/odigos/destinations" + "github.com/odigos-io/odigos/frontend/graph/model" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func k8sKindToGql(k8sResourceKind string) gqlmodel.K8sResourceKind { +func k8sKindToGql(k8sResourceKind string) model.K8sResourceKind { switch k8sResourceKind { case "Deployment": - return gqlmodel.K8sResourceKindDeployment + return model.K8sResourceKindDeployment case "StatefulSet": - return gqlmodel.K8sResourceKindStatefulSet + return model.K8sResourceKindStatefulSet case "DaemonSet": - return gqlmodel.K8sResourceKindDaemonSet + return model.K8sResourceKindDaemonSet } return "" } -func k8sConditionStatusToGql(status v1.ConditionStatus) gqlmodel.ConditionStatus { +func k8sConditionStatusToGql(status v1.ConditionStatus) model.ConditionStatus { switch status { case v1.ConditionTrue: - return gqlmodel.ConditionStatusTrue + return model.ConditionStatusTrue case v1.ConditionFalse: - return gqlmodel.ConditionStatusFalse + return model.ConditionStatusFalse case v1.ConditionUnknown: - return gqlmodel.ConditionStatusUnknown + return model.ConditionStatusUnknown } - return gqlmodel.ConditionStatusUnknown + return model.ConditionStatusUnknown } @@ -41,16 +42,16 @@ func k8sLastTransitionTimeToGql(t v1.Time) *string { return &str } -func instrumentedApplicationToActualSource(instrumentedApp v1alpha1.InstrumentedApplication) *gqlmodel.K8sActualSource { +func instrumentedApplicationToActualSource(instrumentedApp v1alpha1.InstrumentedApplication) *model.K8sActualSource { // Map the container runtime details - var containers []*gqlmodel.SourceContainerRuntimeDetails + var containers []*model.SourceContainerRuntimeDetails for _, container := range instrumentedApp.Spec.RuntimeDetails { var otherAgentName *string if container.OtherAgent != nil { otherAgentName = &container.OtherAgent.Name } - containers = append(containers, &gqlmodel.SourceContainerRuntimeDetails{ + containers = append(containers, &model.SourceContainerRuntimeDetails{ ContainerName: container.ContainerName, Language: string(container.Language), RuntimeVersion: container.RuntimeVersion, @@ -59,9 +60,9 @@ func instrumentedApplicationToActualSource(instrumentedApp v1alpha1.Instrumented } // Map the conditions of the application - var conditions []*gqlmodel.Condition + var conditions []*model.Condition for _, condition := range instrumentedApp.Status.Conditions { - conditions = append(conditions, &gqlmodel.Condition{ + conditions = append(conditions, &model.Condition{ Type: condition.Type, Status: k8sConditionStatusToGql(condition.Status), Reason: &condition.Reason, @@ -71,16 +72,28 @@ func instrumentedApplicationToActualSource(instrumentedApp v1alpha1.Instrumented } // Return the converted K8sActualSource object - return &gqlmodel.K8sActualSource{ + return &model.K8sActualSource{ Namespace: instrumentedApp.Namespace, Kind: k8sKindToGql(instrumentedApp.OwnerReferences[0].Kind), Name: instrumentedApp.OwnerReferences[0].Name, ServiceName: &instrumentedApp.Name, NumberOfInstances: nil, AutoInstrumented: instrumentedApp.Spec.Options != nil, - InstrumentedApplicationDetails: &gqlmodel.InstrumentedApplicationDetails{ + InstrumentedApplicationDetails: &model.InstrumentedApplicationDetails{ Containers: containers, Conditions: conditions, }, } } + +func convertCustomReadDataLabels(labels []*destinations.CustomReadDataLabel) []*model.CustomReadDataLabel { + var result []*model.CustomReadDataLabel + for _, label := range labels { + result = append(result, &model.CustomReadDataLabel{ + Condition: label.Condition, + Title: label.Title, + Value: label.Value, + }) + } + return result +} diff --git a/frontend/graph/generated.go b/frontend/graph/generated.go index 4c6d25079..e4a309309 100644 --- a/frontend/graph/generated.go +++ b/frontend/graph/generated.go @@ -110,6 +110,12 @@ type ComplexityRoot struct { OriginalEnv func(childComplexity int) int } + CustomReadDataLabel struct { + Condition func(childComplexity int) int + Title func(childComplexity int) int + Value func(childComplexity int) int + } + DbQueryPayloadCollection struct { DropPartialPayloads func(childComplexity int) int MaxPayloadLength func(childComplexity int) int @@ -182,13 +188,15 @@ type ComplexityRoot struct { } Field struct { - ComponentProperties func(childComplexity int) int - ComponentType func(childComplexity int) int - DisplayName func(childComplexity int) int - InitialValue func(childComplexity int) int - Name func(childComplexity int) int - ThumbnailURL func(childComplexity int) int - VideoURL func(childComplexity int) int + ComponentProperties func(childComplexity int) int + ComponentType func(childComplexity int) int + CustomReadDataLabels func(childComplexity int) int + DisplayName func(childComplexity int) int + HideFromReadData func(childComplexity int) int + InitialValue func(childComplexity int) int + Name func(childComplexity int) int + RenderCondition func(childComplexity int) int + Secret func(childComplexity int) int } GetConfigResponse struct { @@ -824,6 +832,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ContainerWorkloadManifestAnalyze.OriginalEnv(childComplexity), true + case "CustomReadDataLabel.condition": + if e.complexity.CustomReadDataLabel.Condition == nil { + break + } + + return e.complexity.CustomReadDataLabel.Condition(childComplexity), true + + case "CustomReadDataLabel.title": + if e.complexity.CustomReadDataLabel.Title == nil { + break + } + + return e.complexity.CustomReadDataLabel.Title(childComplexity), true + + case "CustomReadDataLabel.value": + if e.complexity.CustomReadDataLabel.Value == nil { + break + } + + return e.complexity.CustomReadDataLabel.Value(childComplexity), true + case "DbQueryPayloadCollection.dropPartialPayloads": if e.complexity.DbQueryPayloadCollection.DropPartialPayloads == nil { break @@ -1125,6 +1154,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Field.ComponentType(childComplexity), true + case "Field.customReadDataLabels": + if e.complexity.Field.CustomReadDataLabels == nil { + break + } + + return e.complexity.Field.CustomReadDataLabels(childComplexity), true + case "Field.displayName": if e.complexity.Field.DisplayName == nil { break @@ -1132,6 +1168,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Field.DisplayName(childComplexity), true + case "Field.hideFromReadData": + if e.complexity.Field.HideFromReadData == nil { + break + } + + return e.complexity.Field.HideFromReadData(childComplexity), true + case "Field.initialValue": if e.complexity.Field.InitialValue == nil { break @@ -1146,19 +1189,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Field.Name(childComplexity), true - case "Field.thumbnailURL": - if e.complexity.Field.ThumbnailURL == nil { + case "Field.renderCondition": + if e.complexity.Field.RenderCondition == nil { break } - return e.complexity.Field.ThumbnailURL(childComplexity), true + return e.complexity.Field.RenderCondition(childComplexity), true - case "Field.videoUrl": - if e.complexity.Field.VideoURL == nil { + case "Field.secret": + if e.complexity.Field.Secret == nil { break } - return e.complexity.Field.VideoURL(childComplexity), true + return e.complexity.Field.Secret(childComplexity), true case "GetConfigResponse.installation": if e.complexity.GetConfigResponse.Installation == nil { @@ -4950,6 +4993,138 @@ func (ec *executionContext) fieldContext_ContainerWorkloadManifestAnalyze_origin return fc, nil } +func (ec *executionContext) _CustomReadDataLabel_condition(ctx context.Context, field graphql.CollectedField, obj *model.CustomReadDataLabel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CustomReadDataLabel_condition(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Condition, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CustomReadDataLabel_condition(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CustomReadDataLabel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _CustomReadDataLabel_title(ctx context.Context, field graphql.CollectedField, obj *model.CustomReadDataLabel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CustomReadDataLabel_title(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Title, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CustomReadDataLabel_title(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CustomReadDataLabel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _CustomReadDataLabel_value(ctx context.Context, field graphql.CollectedField, obj *model.CustomReadDataLabel) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CustomReadDataLabel_value(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Value, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CustomReadDataLabel_value(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CustomReadDataLabel", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DbQueryPayloadCollection_maxPayloadLength(ctx context.Context, field graphql.CollectedField, obj *model.DbQueryPayloadCollection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DbQueryPayloadCollection_maxPayloadLength(ctx, field) if err != nil { @@ -6955,8 +7130,8 @@ func (ec *executionContext) fieldContext_Field_componentProperties(_ context.Con return fc, nil } -func (ec *executionContext) _Field_videoUrl(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Field_videoUrl(ctx, field) +func (ec *executionContext) _Field_secret(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Field_secret(ctx, field) if err != nil { return graphql.Null } @@ -6969,21 +7144,68 @@ func (ec *executionContext) _Field_videoUrl(ctx context.Context, field graphql.C }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.VideoURL, nil + return obj.Secret, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.(bool) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Field_secret(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Field_initialValue(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Field_initialValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.InitialValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Field_videoUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Field_initialValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Field", Field: field, @@ -6996,8 +7218,8 @@ func (ec *executionContext) fieldContext_Field_videoUrl(_ context.Context, field return fc, nil } -func (ec *executionContext) _Field_thumbnailURL(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Field_thumbnailURL(ctx, field) +func (ec *executionContext) _Field_renderCondition(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Field_renderCondition(ctx, field) if err != nil { return graphql.Null } @@ -7010,21 +7232,24 @@ func (ec *executionContext) _Field_thumbnailURL(ctx context.Context, field graph }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ThumbnailURL, nil + return obj.RenderCondition, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.([]string) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Field_thumbnailURL(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Field_renderCondition(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Field", Field: field, @@ -7037,8 +7262,8 @@ func (ec *executionContext) fieldContext_Field_thumbnailURL(_ context.Context, f return fc, nil } -func (ec *executionContext) _Field_initialValue(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Field_initialValue(ctx, field) +func (ec *executionContext) _Field_hideFromReadData(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Field_hideFromReadData(ctx, field) if err != nil { return graphql.Null } @@ -7051,21 +7276,24 @@ func (ec *executionContext) _Field_initialValue(ctx context.Context, field graph }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.InitialValue, nil + return obj.HideFromReadData, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.([]string) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Field_initialValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Field_hideFromReadData(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Field", Field: field, @@ -7078,6 +7306,58 @@ func (ec *executionContext) fieldContext_Field_initialValue(_ context.Context, f return fc, nil } +func (ec *executionContext) _Field_customReadDataLabels(ctx context.Context, field graphql.CollectedField, obj *model.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Field_customReadDataLabels(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CustomReadDataLabels, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.CustomReadDataLabel) + fc.Result = res + return ec.marshalNCustomReadDataLabel2ᚕᚖgithubᚗcomᚋodigosᚑioᚋodigosᚋfrontendᚋgraphᚋmodelᚐCustomReadDataLabelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Field_customReadDataLabels(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "condition": + return ec.fieldContext_CustomReadDataLabel_condition(ctx, field) + case "title": + return ec.fieldContext_CustomReadDataLabel_title(ctx, field) + case "value": + return ec.fieldContext_CustomReadDataLabel_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CustomReadDataLabel", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _GetConfigResponse_installation(ctx context.Context, field graphql.CollectedField, obj *model.GetConfigResponse) (ret graphql.Marshaler) { fc, err := ec.fieldContext_GetConfigResponse_installation(ctx, field) if err != nil { @@ -7169,12 +7449,16 @@ func (ec *executionContext) fieldContext_GetDestinationDetailsResponse_fields(_ return ec.fieldContext_Field_componentType(ctx, field) case "componentProperties": return ec.fieldContext_Field_componentProperties(ctx, field) - case "videoUrl": - return ec.fieldContext_Field_videoUrl(ctx, field) - case "thumbnailURL": - return ec.fieldContext_Field_thumbnailURL(ctx, field) + case "secret": + return ec.fieldContext_Field_secret(ctx, field) case "initialValue": return ec.fieldContext_Field_initialValue(ctx, field) + case "renderCondition": + return ec.fieldContext_Field_renderCondition(ctx, field) + case "hideFromReadData": + return ec.fieldContext_Field_hideFromReadData(ctx, field) + case "customReadDataLabels": + return ec.fieldContext_Field_customReadDataLabels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Field", field.Name) }, @@ -18643,6 +18927,55 @@ func (ec *executionContext) _ContainerWorkloadManifestAnalyze(ctx context.Contex return out } +var customReadDataLabelImplementors = []string{"CustomReadDataLabel"} + +func (ec *executionContext) _CustomReadDataLabel(ctx context.Context, sel ast.SelectionSet, obj *model.CustomReadDataLabel) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, customReadDataLabelImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CustomReadDataLabel") + case "condition": + out.Values[i] = ec._CustomReadDataLabel_condition(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "title": + out.Values[i] = ec._CustomReadDataLabel_title(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "value": + out.Values[i] = ec._CustomReadDataLabel_value(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var dbQueryPayloadCollectionImplementors = []string{"DbQueryPayloadCollection"} func (ec *executionContext) _DbQueryPayloadCollection(ctx context.Context, sel ast.SelectionSet, obj *model.DbQueryPayloadCollection) graphql.Marshaler { @@ -19254,12 +19587,31 @@ func (ec *executionContext) _Field(ctx context.Context, sel ast.SelectionSet, ob if out.Values[i] == graphql.Null { out.Invalids++ } - case "videoUrl": - out.Values[i] = ec._Field_videoUrl(ctx, field, obj) - case "thumbnailURL": - out.Values[i] = ec._Field_thumbnailURL(ctx, field, obj) + case "secret": + out.Values[i] = ec._Field_secret(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "initialValue": out.Values[i] = ec._Field_initialValue(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "renderCondition": + out.Values[i] = ec._Field_renderCondition(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "hideFromReadData": + out.Values[i] = ec._Field_hideFromReadData(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "customReadDataLabels": + out.Values[i] = ec._Field_customReadDataLabels(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -22069,6 +22421,60 @@ func (ec *executionContext) marshalNContainerWorkloadManifestAnalyze2ᚖgithub return ec._ContainerWorkloadManifestAnalyze(ctx, sel, v) } +func (ec *executionContext) marshalNCustomReadDataLabel2ᚕᚖgithubᚗcomᚋodigosᚑioᚋodigosᚋfrontendᚋgraphᚋmodelᚐCustomReadDataLabelᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.CustomReadDataLabel) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNCustomReadDataLabel2ᚖgithubᚗcomᚋodigosᚑioᚋodigosᚋfrontendᚋgraphᚋmodelᚐCustomReadDataLabel(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNCustomReadDataLabel2ᚖgithubᚗcomᚋodigosᚑioᚋodigosᚋfrontendᚋgraphᚋmodelᚐCustomReadDataLabel(ctx context.Context, sel ast.SelectionSet, v *model.CustomReadDataLabel) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._CustomReadDataLabel(ctx, sel, v) +} + func (ec *executionContext) marshalNDestination2githubᚗcomᚋodigosᚑioᚋodigosᚋfrontendᚋgraphᚋmodelᚐDestination(ctx context.Context, sel ast.SelectionSet, v model.Destination) graphql.Marshaler { return ec._Destination(ctx, sel, &v) } diff --git a/frontend/graph/model/models_gen.go b/frontend/graph/model/models_gen.go index 3112af619..8b79a6e3a 100644 --- a/frontend/graph/model/models_gen.go +++ b/frontend/graph/model/models_gen.go @@ -104,6 +104,12 @@ type ContainerWorkloadManifestAnalyze struct { OriginalEnv []*EntityProperty `json:"originalEnv"` } +type CustomReadDataLabel struct { + Condition string `json:"condition"` + Title string `json:"title"` + Value string `json:"value"` +} + type DbQueryPayloadCollection struct { MaxPayloadLength *int `json:"maxPayloadLength,omitempty"` DropPartialPayloads *bool `json:"dropPartialPayloads,omitempty"` @@ -199,13 +205,15 @@ type ExportedSignalsInput struct { } type Field struct { - Name string `json:"name"` - DisplayName string `json:"displayName"` - ComponentType string `json:"componentType"` - ComponentProperties string `json:"componentProperties"` - VideoURL *string `json:"videoUrl,omitempty"` - ThumbnailURL *string `json:"thumbnailURL,omitempty"` - InitialValue *string `json:"initialValue,omitempty"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + ComponentType string `json:"componentType"` + ComponentProperties string `json:"componentProperties"` + Secret bool `json:"secret"` + InitialValue string `json:"initialValue"` + RenderCondition []string `json:"renderCondition"` + HideFromReadData []string `json:"hideFromReadData"` + CustomReadDataLabels []*CustomReadDataLabel `json:"customReadDataLabels"` } type FieldInput struct { diff --git a/frontend/graph/schema.graphqls b/frontend/graph/schema.graphqls index 683750fe1..ba209da7e 100644 --- a/frontend/graph/schema.graphqls +++ b/frontend/graph/schema.graphqls @@ -205,11 +205,7 @@ type ComputePlatform { computePlatformType: ComputePlatformType! k8sActualNamespace(name: String!): K8sActualNamespace k8sActualNamespaces: [K8sActualNamespace]! - k8sActualSource( - name: String - namespace: String - kind: String - ): K8sActualSource + k8sActualSource(name: String, namespace: String, kind: String): K8sActualSource k8sActualSources: [K8sActualSource]! destinations: [Destination!]! actions: [IcaInstanceResponse!]! @@ -263,14 +259,22 @@ type GetConfigResponse { installation: InstallationStatus! } +type CustomReadDataLabel { + condition: String! + title: String! + value: String! +} + type Field { name: String! displayName: String! componentType: String! componentProperties: String! # Using String to store JSON data as a string - videoUrl: String - thumbnailURL: String - initialValue: String + secret: Boolean! + initialValue: String! + renderCondition: [String!]! + hideFromReadData: [String!]! + customReadDataLabels: [CustomReadDataLabel!]! } type GetDestinationDetailsResponse { @@ -574,39 +578,22 @@ type Query { potentialDestinations: [DestinationDetails!]! getOverviewMetrics: OverviewMetricsResponse! describeOdigos: OdigosAnalyze! - describeSource( - namespace: String! - kind: String! - name: String! - ): SourceAnalyze! + describeSource(namespace: String!, kind: String!, name: String!): SourceAnalyze! } type Mutation { createNewDestination(destination: DestinationInput!): Destination! persistK8sNamespace(namespace: PersistNamespaceItemInput!): Boolean! - persistK8sSources( - namespace: String! - sources: [PersistNamespaceSourceInput!]! - ): Boolean! - testConnectionForDestination( - destination: DestinationInput! - ): TestConnectionResponse! - updateK8sActualSource( - sourceId: K8sSourceId! - patchSourceRequest: PatchSourceRequestInput! - ): Boolean! + persistK8sSources(namespace: String!, sources: [PersistNamespaceSourceInput!]!): Boolean! + testConnectionForDestination(destination: DestinationInput!): TestConnectionResponse! + updateK8sActualSource(sourceId: K8sSourceId!, patchSourceRequest: PatchSourceRequestInput!): Boolean! updateDestination(id: ID!, destination: DestinationInput!): Destination! deleteDestination(id: ID!): Boolean! createAction(action: ActionInput!): Action! updateAction(id: ID!, action: ActionInput!): Action! deleteAction(id: ID!, actionType: String!): Boolean! - createInstrumentationRule( - instrumentationRule: InstrumentationRuleInput! - ): InstrumentationRule! - updateInstrumentationRule( - ruleId: ID! - instrumentationRule: InstrumentationRuleInput! - ): InstrumentationRule! + createInstrumentationRule(instrumentationRule: InstrumentationRuleInput!): InstrumentationRule! + updateInstrumentationRule(ruleId: ID!, instrumentationRule: InstrumentationRuleInput!): InstrumentationRule! deleteInstrumentationRule(ruleId: ID!): Boolean! } diff --git a/frontend/graph/schema.resolvers.go b/frontend/graph/schema.resolvers.go index e11a5125b..18674e8f1 100644 --- a/frontend/graph/schema.resolvers.go +++ b/frontend/graph/schema.resolvers.go @@ -17,8 +17,8 @@ import ( "github.com/odigos-io/odigos/frontend/kube" "github.com/odigos-io/odigos/frontend/services" actionservices "github.com/odigos-io/odigos/frontend/services/actions" - odigos_describe "github.com/odigos-io/odigos/frontend/services/describe/odigos_describe" - source_describe "github.com/odigos-io/odigos/frontend/services/describe/source_describe" + "github.com/odigos-io/odigos/frontend/services/describe/odigos_describe" + "github.com/odigos-io/odigos/frontend/services/describe/source_describe" testconnection "github.com/odigos-io/odigos/frontend/services/test_connection" "github.com/odigos-io/odigos/k8sutils/pkg/workload" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -732,15 +732,17 @@ func (r *queryResolver) DestinationTypeDetails(ctx context.Context, typeArg stri if err != nil { return nil, fmt.Errorf("error marshalling component properties: %v", err) } - resp.Fields = append(resp.Fields, &model.Field{ - Name: field.Name, - DisplayName: field.DisplayName, - ComponentType: field.ComponentType, - ComponentProperties: string(componentPropsJSON), - InitialValue: &field.InitialValue, + Name: field.Name, + DisplayName: field.DisplayName, + ComponentType: field.ComponentType, + ComponentProperties: string(componentPropsJSON), + Secret: field.Secret, + InitialValue: field.InitialValue, + RenderCondition: field.RenderCondition, + HideFromReadData: field.HideFromReadData, + CustomReadDataLabels: convertCustomReadDataLabels(field.CustomReadDataLabels), }) - } return &resp, nil diff --git a/frontend/services/describe/source_describe/source_describe.go b/frontend/services/describe/source_describe/source_describe.go index 134acfe76..b422e9836 100644 --- a/frontend/services/describe/source_describe/source_describe.go +++ b/frontend/services/describe/source_describe/source_describe.go @@ -1,4 +1,4 @@ -package describe +package source_describe import ( "context" diff --git a/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts b/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts index c21c3b28b..47929808e 100644 --- a/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts +++ b/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts @@ -1,4 +1,4 @@ -import { DISPLAY_TITLES, safeJsonParse } from '@/utils'; +import { compareCondition, DISPLAY_TITLES, safeJsonParse } from '@/utils'; import { DataCardRow, DataCardFieldTypes } from '@/reuseable-components'; import type { ActualDestination, DestinationDetailsResponse, ExportedSignals } from '@/types'; @@ -16,16 +16,41 @@ const buildCard = (destination: ActualDestination, destinationTypeDetails?: Dest { type: DataCardFieldTypes.DIVIDER, width: '100%' }, ]; - Object.entries(safeJsonParse>(fields, {})).map(([key, value]) => { - const found = destinationTypeDetails?.fields?.find((field) => field.name === key); - - const { type } = safeJsonParse(found?.componentProperties, { type: '' }); - const secret = type === 'password' ? new Array(11).fill('•').join('') : ''; - - arr.push({ - title: found?.displayName || key, - value: secret || value, - }); + const parsedFields = safeJsonParse>(fields, {}); + const sortedParsedFields = + destinationTypeDetails?.fields.map((field) => ({ key: field.name, value: parsedFields[field.name] ?? null })).filter((item) => item.value !== null) || + Object.entries(parsedFields).map(([key, value]) => ({ key, value })); + + sortedParsedFields.map(({ key, value }) => { + const { displayName, secret, componentProperties, hideFromReadData, customReadDataLabels } = destinationTypeDetails?.fields?.find((field) => field.name === key) || {}; + + const shouldHide = !!hideFromReadData?.length + ? compareCondition( + hideFromReadData, + (destinationTypeDetails?.fields || []).map((field) => ({ name: field.name, value: parsedFields[field.name] ?? null })), + ) + : false; + + if (!shouldHide) { + const { type } = safeJsonParse(componentProperties, { type: '' }); + const isSecret = (secret || type === 'password') && !!value.length ? new Array(10).fill('•').join('') : ''; + + if (!!customReadDataLabels?.length) { + customReadDataLabels.forEach(({ condition, ...custom }) => { + if (condition == value) { + arr.push({ + title: custom.title, + value: custom.value, + }); + } + }); + } else { + arr.push({ + title: displayName || key, + value: isSecret || value, + }); + } + } }); return arr; diff --git a/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx b/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx index e3c3ddff5..e99717fc1 100644 --- a/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-form-body/dynamic-fields/index.tsx @@ -1,22 +1,35 @@ import React from 'react'; -import { INPUT_TYPES } from '@/utils'; +import type { DynamicField } from '@/types'; +import { compareCondition, INPUT_TYPES } from '@/utils'; import { Dropdown, Input, TextArea, InputList, KeyValueInputsList, Checkbox } from '@/reuseable-components'; interface Props { - fields: any[]; + fields: DynamicField[]; onChange: (name: string, value: any) => void; formErrors: Record; } export const DestinationDynamicFields: React.FC = ({ fields, onChange, formErrors }) => { - return fields?.map((field: any) => { - const { componentType, ...rest } = field; + return fields?.map((field) => { + const { componentType, renderCondition, ...rest } = field; + + const shouldRender = compareCondition(renderCondition, fields); + if (!shouldRender) return null; switch (componentType) { case INPUT_TYPES.INPUT: return onChange(field.name, e.target.value)} errorMessage={formErrors[field.name]} />; case INPUT_TYPES.DROPDOWN: - return onChange(field.name, option.value)} errorMessage={formErrors[field.name]} />; + return ( + onChange(field.name, option.value)} + errorMessage={formErrors[field.name]} + /> + ); case INPUT_TYPES.MULTI_INPUT: return onChange(field.name, JSON.stringify(value))} errorMessage={formErrors[field.name]} />; case INPUT_TYPES.KEY_VALUE_PAIR: diff --git a/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-simple/sources-list/index.tsx b/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-simple/sources-list/index.tsx index c86524e71..8bc147a45 100644 --- a/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-simple/sources-list/index.tsx +++ b/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-simple/sources-list/index.tsx @@ -13,7 +13,6 @@ const SourcesListWrapper = styled.div<{ $isModal: Props['isModal'] }>` align-items: center; flex-direction: column; gap: 12px; - max-height: ${({ $isModal }) => ($isModal ? 'calc(100vh - 548px)' : 'calc(100vh - 360px)')}; height: fit-content; padding-bottom: ${({ $isModal }) => ($isModal ? '48px' : '0')}; overflow-y: scroll; diff --git a/frontend/webapp/graphql/queries/destination.ts b/frontend/webapp/graphql/queries/destination.ts index 1bc8a05d9..952aa70dc 100644 --- a/frontend/webapp/graphql/queries/destination.ts +++ b/frontend/webapp/graphql/queries/destination.ts @@ -35,7 +35,15 @@ export const GET_DESTINATION_TYPE_DETAILS = gql` displayName componentType componentProperties + secret initialValue + renderCondition + hideFromReadData + customReadDataLabels { + condition + title + value + } } } } diff --git a/frontend/webapp/hooks/destinations/index.ts b/frontend/webapp/hooks/destinations/index.ts index 3b987c456..02a4ed46f 100644 --- a/frontend/webapp/hooks/destinations/index.ts +++ b/frontend/webapp/hooks/destinations/index.ts @@ -1,5 +1,4 @@ export * from './useTestConnection'; -export * from './useConnectDestinationForm'; export * from './usePotentialDestinations'; export * from './useDestinationCRUD'; export * from './useDestinationFormData'; diff --git a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts b/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts deleted file mode 100644 index 4dd8827b3..000000000 --- a/frontend/webapp/hooks/destinations/useConnectDestinationForm.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { safeJsonParse, INPUT_TYPES } from '@/utils'; -import { DestinationDetailsField, DynamicField } from '@/types'; - -export function useConnectDestinationForm() { - function buildFormDynamicFields(fields: DestinationDetailsField[]): DynamicField[] { - return fields - .map((field) => { - const { componentType, displayName, initialValue, componentProperties, ...restOfField } = field; - - let componentPropertiesJson; - let initialValuesJson; - - switch (componentType) { - case INPUT_TYPES.INPUT: - case INPUT_TYPES.TEXTAREA: - case INPUT_TYPES.CHECKBOX: - case INPUT_TYPES.KEY_VALUE_PAIR: - componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); - - return { - componentType, - title: displayName, - value: initialValue, - ...restOfField, - ...componentPropertiesJson, - }; - - case INPUT_TYPES.MULTI_INPUT: - componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); - initialValuesJson = safeJsonParse(initialValue, []); - - return { - componentType, - title: displayName, - value: initialValuesJson, - initialValues: initialValuesJson, - ...restOfField, - ...componentPropertiesJson, - }; - - case INPUT_TYPES.DROPDOWN: - componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); - - const options = Array.isArray(componentPropertiesJson.values) - ? componentPropertiesJson.values.map((value) => ({ - id: value, - value, - })) - : Object.entries(componentPropertiesJson.values).map(([key, value]) => ({ - id: key, - value, - })); - - return { - componentType, - title: displayName, - value: initialValue, - placeholder: componentPropertiesJson.placeholder || 'Select an option', - options, - onSelect: () => {}, - ...restOfField, - ...componentPropertiesJson, - }; - - default: - return undefined; - } - }) - .filter((field): field is DynamicField => field !== undefined); - } - - return { buildFormDynamicFields }; -} diff --git a/frontend/webapp/hooks/destinations/useDestinationFormData.ts b/frontend/webapp/hooks/destinations/useDestinationFormData.ts index eb50c9ac2..8fbb73bb9 100644 --- a/frontend/webapp/hooks/destinations/useDestinationFormData.ts +++ b/frontend/webapp/hooks/destinations/useDestinationFormData.ts @@ -1,9 +1,9 @@ import { useState, useEffect } from 'react'; +import { useGenericForm } from '@/hooks'; import { useQuery } from '@apollo/client'; import { GET_DESTINATION_TYPE_DETAILS } from '@/graphql'; -import { DrawerItem, useNotificationStore } from '@/store'; -import { ACTION, FORM_ALERTS, safeJsonParse } from '@/utils'; -import { useConnectDestinationForm, useGenericForm } from '@/hooks'; +import { type DrawerItem, useNotificationStore } from '@/store'; +import { ACTION, FORM_ALERTS, INPUT_TYPES, safeJsonParse } from '@/utils'; import { type DynamicField, type DestinationDetailsResponse, @@ -13,6 +13,7 @@ import { type SupportedDestinationSignals, OVERVIEW_ENTITY_TYPES, NOTIFICATION_TYPE, + type DestinationDetailsField, } from '@/types'; const INITIAL: DestinationInput = { @@ -26,13 +27,74 @@ const INITIAL: DestinationInput = { fields: [], }; +const buildFormDynamicFields = (fields: DestinationDetailsField[]): DynamicField[] => { + return fields + .map((field) => { + const { name, componentType, componentProperties, displayName, initialValue, renderCondition } = field; + + switch (componentType) { + case INPUT_TYPES.MULTI_INPUT: { + const componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); + const initialValuesJson = safeJsonParse(initialValue, []); + + return { + name, + componentType, + title: displayName, + value: initialValuesJson, + initialValues: initialValuesJson, + renderCondition, + ...componentPropertiesJson, + }; + } + + case INPUT_TYPES.DROPDOWN: { + const componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); + const options = Array.isArray(componentPropertiesJson.values) + ? componentPropertiesJson.values.map((value) => ({ + id: value, + value, + })) + : Object.entries(componentPropertiesJson.values).map(([key, value]) => ({ + id: key, + value, + })); + + return { + name, + componentType, + title: displayName, + value: initialValue, + placeholder: componentPropertiesJson.placeholder || 'Select an option', + options, + renderCondition, + ...componentPropertiesJson, + }; + } + + default: { + const componentPropertiesJson = safeJsonParse<{ [key: string]: string }>(componentProperties, {}); + + return { + name, + componentType, + title: displayName, + value: initialValue, + renderCondition, + ...componentPropertiesJson, + }; + } + } + }) + .filter((field): field is DynamicField => field !== undefined); +}; + export function useDestinationFormData(params?: { destinationType?: string; supportedSignals?: SupportedDestinationSignals; preLoadedFields?: string | DestinationTypeItem['fields'] }) { const { destinationType, supportedSignals, preLoadedFields } = params || {}; const { addNotification } = useNotificationStore(); const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL); - const { buildFormDynamicFields } = useConnectDestinationForm(); const [dynamicFields, setDynamicFields] = useState([]); const t = destinationType || formData.type; diff --git a/frontend/webapp/reuseable-components/checkbox/index.tsx b/frontend/webapp/reuseable-components/checkbox/index.tsx index 05552a840..294ad9f6d 100644 --- a/frontend/webapp/reuseable-components/checkbox/index.tsx +++ b/frontend/webapp/reuseable-components/checkbox/index.tsx @@ -4,6 +4,8 @@ import theme from '@/styles/theme'; import { CheckIcon } from '@/assets'; import { Tooltip } from '../tooltip'; import styled from 'styled-components'; +import { FlexColumn } from '@/styles'; +import { FieldError } from '../field-error'; interface CheckboxProps { title?: string; @@ -13,6 +15,7 @@ interface CheckboxProps { onChange?: (value: boolean) => void; disabled?: boolean; style?: React.CSSProperties; + errorMessage?: string; } const Container = styled.div<{ $disabled?: CheckboxProps['disabled'] }>` @@ -36,7 +39,7 @@ const CheckboxWrapper = styled.div<{ $isChecked: boolean; $disabled?: CheckboxPr transition: border 0.3s, background-color 0.3s; `; -export const Checkbox: React.FC = ({ title, titleColor, tooltip, value = false, onChange, disabled, style }) => { +export const Checkbox: React.FC = ({ title, titleColor, tooltip, value = false, onChange, disabled, style, errorMessage }) => { const [isChecked, setIsChecked] = useState(value); useEffect(() => setIsChecked(value), [value]); @@ -50,18 +53,22 @@ export const Checkbox: React.FC = ({ title, titleColor, tooltip, }; return ( - - - {isChecked && } - + + + + {isChecked && } + - {title && ( - - - {title} - - - )} - + {title && ( + + + {title} + + + )} + + + {!!errorMessage && {errorMessage}} + ); }; diff --git a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx index 972d6cb03..dd00b77f2 100644 --- a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx +++ b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx @@ -1,8 +1,9 @@ -import React, { useId } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { NOTIFICATION_TYPE } from '@/types'; import { ActiveStatus, Code, DataTab, Divider, InstrumentStatus, MonitorsIcons, NotificationNote, Text, Tooltip } from '@/reuseable-components'; import { capitalizeFirstLetter, getProgrammingLanguageIcon, parseJsonStringToPrettyString, safeJsonParse, WORKLOAD_PROGRAMMING_LANGUAGES } from '@/utils'; +import theme from '@/styles/theme'; export enum DataCardFieldTypes { DIVIDER = 'divider', @@ -47,18 +48,14 @@ const ItemTitle = styled(Text)` export const DataCardFields: React.FC = ({ data }) => { return ( - {data.map(({ type, title, tooltip, value, width = 'unset' }) => { - const id = useId(); - - return ( - - - {!!title && {title}} - - {renderValue(type, value)} - - ); - })} + {data.map(({ type, title, tooltip, value, width = 'unset' }) => ( + + + {!!title && {title}} + + {renderValue(type, value)} + + ))} ); }; @@ -76,7 +73,7 @@ const renderValue = (type: DataCardRow['type'], value: DataCardRow['value']) => return ; case DataCardFieldTypes.MONITORS: - return ; + return ; case DataCardFieldTypes.ACTIVE_STATUS: return ; diff --git a/frontend/webapp/reuseable-components/dropdown/index.tsx b/frontend/webapp/reuseable-components/dropdown/index.tsx index 08007b487..6547229c7 100644 --- a/frontend/webapp/reuseable-components/dropdown/index.tsx +++ b/frontend/webapp/reuseable-components/dropdown/index.tsx @@ -13,7 +13,7 @@ interface DropdownProps { options: DropdownOption[]; value: DropdownOption | DropdownOption[] | undefined; onSelect: (option: DropdownOption) => void; - onDeselect: (option: DropdownOption) => void; + onDeselect?: (option: DropdownOption) => void; isMulti?: boolean; required?: boolean; showSearch?: boolean; diff --git a/frontend/webapp/types/destinations.ts b/frontend/webapp/types/destinations.ts index 08e90393d..ea8d84895 100644 --- a/frontend/webapp/types/destinations.ts +++ b/frontend/webapp/types/destinations.ts @@ -1,4 +1,4 @@ -import type { Condition, ExportedSignals } from './common'; +import type { Condition, DropdownOption, ExportedSignals } from './common'; interface ObservabilitySignalSupport { supported: boolean; @@ -37,21 +37,28 @@ export interface DestinationDetailsField { displayName: string; componentType: string; componentProperties: string; - videoUrl: string | null; - thumbnailURL: string | null; + secret: boolean; initialValue: string; - __typename: string; + renderCondition: string[]; + hideFromReadData: string[]; + customReadDataLabels: { + condition: string; + title: string; + value: string; + }[]; } -export type DynamicField = { +export interface DynamicField { + componentType: string; name: string; - componentType: 'input' | 'dropdown' | 'multi_input' | 'textarea'; - type: string; title: string; - value?: any; + value: any; + type?: string; + placeholder?: string; required?: boolean; - [key: string]: any; -}; + options?: DropdownOption[]; + renderCondition: string[]; +} export interface DestinationDetailsResponse { destinationTypeDetails: { diff --git a/frontend/webapp/utils/functions/resolvers/compare-condition/index.ts b/frontend/webapp/utils/functions/resolvers/compare-condition/index.ts new file mode 100644 index 000000000..fff624f65 --- /dev/null +++ b/frontend/webapp/utils/functions/resolvers/compare-condition/index.ts @@ -0,0 +1,32 @@ +export const compareCondition = (renderCondition: string[], fields: { name: string; value: any }[]) => { + if (!renderCondition || !renderCondition.length) return true; + if (renderCondition.length === 1) return renderCondition[0] == 'true'; + + const [key, cond, val] = renderCondition; + const field = fields.find((field) => field.name === key); + + if (!field) { + console.warn(`Field with name ${key} not found, condition will be skipped`); + return true; + } + + switch (cond) { + case '===': + case '==': + return field.value === val; + case '!==': + case '!=': + return field.value !== val; + case '>': + return field.value > val; + case '<': + return field.value < val; + case '>=': + return field.value >= val; + case '<=': + return field.value <= val; + default: + console.warn(`Invalid condition ${cond}, condition will be skipped`); + return true; + } +}; diff --git a/frontend/webapp/utils/functions/resolvers/index.ts b/frontend/webapp/utils/functions/resolvers/index.ts index 5115af1ea..6c3ae5d3c 100644 --- a/frontend/webapp/utils/functions/resolvers/index.ts +++ b/frontend/webapp/utils/functions/resolvers/index.ts @@ -1 +1,2 @@ +export * from './compare-condition'; export * from './get-value-for-range'; diff --git a/helm/odigos/templates/odiglet/clusterrole.yaml b/helm/odigos/templates/odiglet/clusterrole.yaml index 9cbb2d109..c632517d9 100644 --- a/helm/odigos/templates/odiglet/clusterrole.yaml +++ b/helm/odigos/templates/odiglet/clusterrole.yaml @@ -6,11 +6,8 @@ rules: - apiGroups: - "" resources: - - configmaps - - namespaces - - nodes - pods - - services + - pods/status verbs: - get - list @@ -18,14 +15,16 @@ rules: - apiGroups: - "" resources: - - pods/status + - nodes verbs: - get + - list + - watch - apiGroups: - apps resources: - - daemonsets - deployments + - daemonsets - statefulsets verbs: - get @@ -34,37 +33,11 @@ rules: - apiGroups: - apps resources: - - daemonsets/finalizers - - deployments/finalizers - - statefulsets/finalizers - verbs: - - update - - apiGroups: - - apps - resources: - - daemonsets/status - deployments/status + - daemonsets/status - statefulsets/status verbs: - get - - apiGroups: - - odigos.io - resources: - - collectorsgroups - - collectorsgroups/status - verbs: - - get - - list - - watch - - apiGroups: - - odigos.io - resources: - - instrumentationconfigs/status - verbs: - - get - - list - - watch - - patch - apiGroups: - apps resources: @@ -74,36 +47,38 @@ rules: - apiGroups: - odigos.io resources: - - instrumentationconfigs + - instrumentedapplications verbs: + - create - get - list - - watch - patch - update + - watch - apiGroups: - odigos.io resources: - - instrumentedapplications + - instrumentedapplications/status verbs: - - create - get - - list - patch - update - - watch - apiGroups: - odigos.io resources: - - odigosconfigurations + - instrumentationinstances verbs: + - create - get - list + - patch + - update - watch + - delete - apiGroups: - odigos.io resources: - - instrumentedapplications/status + - instrumentationinstances/status verbs: - get - patch @@ -111,30 +86,29 @@ rules: - apiGroups: - odigos.io resources: - - instrumentationinstances + - instrumentationconfigs verbs: - - create - get - list + - watch - patch - update - - watch - - delete - apiGroups: - odigos.io resources: - - instrumentationinstances/status + - instrumentationconfigs/status verbs: - get + - list + - watch - patch - - update {{ if .Values.psp.enabled }} - apiGroups: - - policy + - policy resourceNames: - - privileged + - privileged resources: - - podsecuritypolicies + - podsecuritypolicies verbs: - - use + - use {{ end }} diff --git a/helm/odigos/templates/odiglet/role.yaml b/helm/odigos/templates/odiglet/role.yaml new file mode 100644 index 000000000..358a0aa47 --- /dev/null +++ b/helm/odigos/templates/odiglet/role.yaml @@ -0,0 +1,25 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: odiglet + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - odigos.io + resources: + - collectorsgroups + - collectorsgroups/status + verbs: + - get + - list + - watch + - apiGroups: + - "" + resourceNames: + - odigos-config + resources: + - configmaps + verbs: + - get + - list + - watch diff --git a/helm/odigos/templates/odiglet/rolebinding.yaml b/helm/odigos/templates/odiglet/rolebinding.yaml index 908f085c4..b18bd7168 100644 --- a/helm/odigos/templates/odiglet/rolebinding.yaml +++ b/helm/odigos/templates/odiglet/rolebinding.yaml @@ -16,3 +16,17 @@ subjects: name: odigos-data-collection namespace: {{ .Release.Namespace }} {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: odiglet + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: odiglet +subjects: + - kind: ServiceAccount + name: odiglet + namespace: {{ .Release.Namespace }} diff --git a/instrumentor/go.mod b/instrumentor/go.mod index 36c993199..0622e2607 100644 --- a/instrumentor/go.mod +++ b/instrumentor/go.mod @@ -12,7 +12,7 @@ require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.36.1 github.com/stretchr/testify v1.10.0 - go.opentelemetry.io/otel v1.29.0 + go.opentelemetry.io/otel v1.33.0 k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.0 k8s.io/client-go v0.32.0 @@ -32,9 +32,10 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect diff --git a/instrumentor/go.sum b/instrumentor/go.sum index 4a41a86c4..78eb5c07f 100644 --- a/instrumentor/go.sum +++ b/instrumentor/go.sum @@ -139,8 +139,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 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= @@ -159,14 +159,16 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/odiglet/go.mod b/odiglet/go.mod index 2ec6dec65..d8920ba15 100644 --- a/odiglet/go.mod +++ b/odiglet/go.mod @@ -22,7 +22,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.10.0 - google.golang.org/grpc v1.69.0 + google.golang.org/grpc v1.69.2 k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.0 k8s.io/client-go v0.32.0 diff --git a/odiglet/go.sum b/odiglet/go.sum index a9770fe39..2467a2f29 100644 --- a/odiglet/go.sum +++ b/odiglet/go.sum @@ -536,8 +536,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI= -google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/odiglet/pkg/env/current.go b/odiglet/pkg/env/current.go index 06a819bb4..8f8dc2be2 100644 --- a/odiglet/pkg/env/current.go +++ b/odiglet/pkg/env/current.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "runtime" + + "github.com/odigos-io/odigos/common/consts" ) const ( @@ -12,8 +14,9 @@ const ( ) type Environment struct { - NodeName string - NodeIP string + NodeName string + NodeIP string + Namespace string } var Current Environment @@ -29,9 +32,15 @@ func Load() error { return fmt.Errorf("env var %s is not set", NodeIPEnvVar) } + ns, ok := os.LookupEnv(consts.CurrentNamespaceEnvVar) + if !ok { + return fmt.Errorf("env var %s is not set", consts.CurrentNamespaceEnvVar) + } + Current = Environment{ - NodeName: nn, - NodeIP: ni, + NodeName: nn, + NodeIP: ni, + Namespace: ns, } return nil } diff --git a/odiglet/pkg/kube/manager.go b/odiglet/pkg/kube/manager.go index 3b1b911a9..22a9e146a 100644 --- a/odiglet/pkg/kube/manager.go +++ b/odiglet/pkg/kube/manager.go @@ -3,7 +3,6 @@ package kube import ( "github.com/odigos-io/odigos/common/consts" "github.com/odigos-io/odigos/instrumentation" - "k8s.io/apimachinery/pkg/labels" "github.com/odigos-io/odigos/odiglet/pkg/ebpf" "github.com/odigos-io/odigos/odiglet/pkg/env" @@ -38,6 +37,13 @@ func init() { func CreateManager() (ctrl.Manager, error) { log.Logger.V(0).Info("Starting reconcileres for runtime details") ctrl.SetLogger(log.Logger) + + odigosNs := env.Current.Namespace + nsSelector := client.InNamespace(odigosNs).AsSelector() + nameSelector := fields.OneTermEqualSelector("metadata.name", consts.OdigosConfigurationName) + odigosConfigSelector := fields.AndSelectors(nsSelector, nameSelector) + currentNodeSelector := fields.OneTermEqualSelector("spec.nodeName", env.Current.NodeName) + return manager.New(config.GetConfigOrDie(), manager.Options{ Scheme: scheme, Cache: cache.Options{ @@ -45,12 +51,14 @@ func CreateManager() (ctrl.Manager, error) { // running `kubectl get .... --show-managed-fields` will show the managed fields. DefaultTransform: cache.TransformStripManagedFields(), ByObject: map[client.Object]cache.ByObject{ + &corev1.ConfigMap{}: { + Field: odigosConfigSelector, + }, &corev1.Pod{}: { - // only watch and list pods in the current node - Field: fields.OneTermEqualSelector("spec.nodeName", env.Current.NodeName), + Field: currentNodeSelector, }, - &corev1.Namespace{}: { - Label: labels.Set{consts.OdigosInstrumentationLabel: consts.InstrumentationEnabled}.AsSelector(), + &odigosv1.CollectorsGroup{}: { // Used by OpAMP server to figure out which signals are collected + Field: nsSelector, }, }, },