Compare commits

..

33 Commits

Author SHA1 Message Date
nikhilmantri0902
e6900dabc8 chore: warnings added passing from queryResponse warning to host lists response struct 2026-04-09 00:09:38 +05:30
nikhilmantri0902
c1ba389b63 chore: add type for response and files rearrange 2026-04-08 23:35:53 +05:30
nikhilmantri0902
3a1f40234f Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-08 23:03:50 +05:30
Piyush Singariya
8bfadbc197 fix: has value fixes (#10864)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
2026-04-08 13:22:54 +00:00
Nikhil Mantri
2e4891fa63 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-08 16:07:57 +05:30
Nikhil Mantri
04ebc0bec7 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-08 11:08:10 +05:30
nikhilmantri0902
6fa815c294 chore: modified getMetadata query 2026-04-07 18:55:57 +05:30
nikhilmantri0902
63ec518efb chore: added hostName logic 2026-04-07 17:36:15 +05:30
nikhilmantri0902
c4ca20dd90 chore: return errors from getMetadata and lint fix 2026-04-07 17:01:13 +05:30
nikhilmantri0902
e56cc4222b chore: return errors from getMetadata and lint fix 2026-04-07 16:57:35 +05:30
nikhilmantri0902
07d2944d7c chore: yarn generate api 2026-04-07 16:44:06 +05:30
nikhilmantri0902
dea01ae36a chore: hostStatusNone added for clarity that this field can be left empty as well in payload 2026-04-07 16:32:25 +05:30
nikhilmantri0902
62ea5b54e2 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-07 14:09:48 +05:30
nikhilmantri0902
61baa1be7a chore: code improvements 2026-04-07 13:49:00 +05:30
nikhilmantri0902
2e049556e4 chore: unified composite key function 2026-04-07 11:15:03 +05:30
nikhilmantri0902
7458fb4855 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-06 17:18:01 +05:30
nikhilmantri0902
5f55f3938b chore: added temporalities of metrics 2026-04-06 17:17:15 +05:30
nikhilmantri0902
861c682ea5 chore: nil pointer dereference fix in req.Filter 2026-04-04 20:52:08 +05:30
nikhilmantri0902
19aada656c chore: updated spec 2026-04-04 16:44:15 +05:30
nikhilmantri0902
37fb0e9254 Merge branch 'infraM/base_dependencies' into infraM/v2_hosts_list_api 2026-04-03 17:49:00 +05:30
nikhilmantri0902
aecfa1a174 chore: added validation on order by 2026-04-02 20:13:30 +05:30
nikhilmantri0902
b869d23d94 chore: moved funcs 2026-04-02 20:02:22 +05:30
nikhilmantri0902
6ee3d44f76 chore: removed isSendingK8sAgentsMetricsCode 2026-04-02 19:58:30 +05:30
nikhilmantri0902
462e554107 chore: yarn generate api 2026-04-02 14:49:15 +05:30
nikhilmantri0902
66afa73e6f chore: return status as a string 2026-04-02 14:39:02 +05:30
nikhilmantri0902
54c604bcf4 chore: added some unit tests 2026-04-02 14:20:27 +05:30
nikhilmantri0902
c1be02ba54 chore: added validate function 2026-04-02 14:14:34 +05:30
nikhilmantri0902
d3c7ba8f45 chore: disk usage 2026-04-02 14:01:18 +05:30
nikhilmantri0902
039c4a0496 fix: bug fix 2026-04-02 11:32:49 +05:30
nikhilmantri0902
51a94b6bbc chore: added logic for hosts v3 api 2026-04-02 02:52:28 +05:30
nikhilmantri0902
bbfbb94f52 chore: merged main 2026-04-01 00:45:40 +05:30
nikhilmantri0902
d1eb9ef16f chore: endpoint detail update 2026-03-31 16:16:31 +05:30
nikhilmantri0902
3db00f8bc3 chore: baseline setup 2026-03-31 15:27:18 +05:30
49 changed files with 2862 additions and 1663 deletions

View File

@@ -403,65 +403,27 @@ components:
required:
- regions
type: object
CloudintegrationtypesAWSCloudWatchLogsSubscription:
CloudintegrationtypesAWSCollectionStrategy:
properties:
filterPattern:
type: string
logGroupNamePrefix:
type: string
required:
- logGroupNamePrefix
- filterPattern
type: object
CloudintegrationtypesAWSCloudWatchMetricStreamFilter:
properties:
metricNames:
items:
type: string
type: array
namespace:
type: string
required:
- namespace
aws_logs:
$ref: '#/components/schemas/CloudintegrationtypesAWSLogsStrategy'
aws_metrics:
$ref: '#/components/schemas/CloudintegrationtypesAWSMetricsStrategy'
s3_buckets:
additionalProperties:
items:
type: string
type: array
type: object
type: object
CloudintegrationtypesAWSConnectionArtifact:
properties:
connectionUrl:
connectionURL:
type: string
required:
- connectionUrl
- connectionURL
type: object
CloudintegrationtypesAWSIntegrationConfig:
properties:
enabledRegions:
items:
type: string
type: array
telemetryCollectionStrategy:
$ref: '#/components/schemas/CloudintegrationtypesAWSTelemetryCollectionStrategy'
required:
- enabledRegions
- telemetryCollectionStrategy
type: object
CloudintegrationtypesAWSLogsCollectionStrategy:
properties:
subscriptions:
items:
$ref: '#/components/schemas/CloudintegrationtypesAWSCloudWatchLogsSubscription'
type: array
required:
- subscriptions
type: object
CloudintegrationtypesAWSMetricsCollectionStrategy:
properties:
streamFilters:
items:
$ref: '#/components/schemas/CloudintegrationtypesAWSCloudWatchMetricStreamFilter'
type: array
required:
- streamFilters
type: object
CloudintegrationtypesAWSPostableAccountConfig:
CloudintegrationtypesAWSConnectionArtifactRequest:
properties:
deploymentRegion:
type: string
@@ -473,6 +435,46 @@ components:
- deploymentRegion
- regions
type: object
CloudintegrationtypesAWSIntegrationConfig:
properties:
enabledRegions:
items:
type: string
type: array
telemetry:
$ref: '#/components/schemas/CloudintegrationtypesAWSCollectionStrategy'
required:
- enabledRegions
- telemetry
type: object
CloudintegrationtypesAWSLogsStrategy:
properties:
cloudwatch_logs_subscriptions:
items:
properties:
filter_pattern:
type: string
log_group_name_prefix:
type: string
type: object
nullable: true
type: array
type: object
CloudintegrationtypesAWSMetricsStrategy:
properties:
cloudwatch_metric_stream_filters:
items:
properties:
MetricNames:
items:
type: string
type: array
Namespace:
type: string
type: object
nullable: true
type: array
type: object
CloudintegrationtypesAWSServiceConfig:
properties:
logs:
@@ -484,7 +486,7 @@ components:
properties:
enabled:
type: boolean
s3Buckets:
s3_buckets:
additionalProperties:
items:
type: string
@@ -496,19 +498,6 @@ components:
enabled:
type: boolean
type: object
CloudintegrationtypesAWSTelemetryCollectionStrategy:
properties:
logs:
$ref: '#/components/schemas/CloudintegrationtypesAWSLogsCollectionStrategy'
metrics:
$ref: '#/components/schemas/CloudintegrationtypesAWSMetricsCollectionStrategy'
s3Buckets:
additionalProperties:
items:
type: string
type: array
type: object
type: object
CloudintegrationtypesAccount:
properties:
agentReport:
@@ -572,26 +561,6 @@ components:
nullable: true
type: array
type: object
CloudintegrationtypesCloudIntegrationService:
nullable: true
properties:
cloudIntegrationId:
type: string
config:
$ref: '#/components/schemas/CloudintegrationtypesServiceConfig'
createdAt:
format: date-time
type: string
id:
type: string
type:
$ref: '#/components/schemas/CloudintegrationtypesServiceID'
updatedAt:
format: date-time
type: string
required:
- id
type: object
CloudintegrationtypesCollectedLogAttribute:
properties:
name:
@@ -612,6 +581,13 @@ components:
unit:
type: string
type: object
CloudintegrationtypesCollectionStrategy:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSCollectionStrategy'
required:
- aws
type: object
CloudintegrationtypesConnectionArtifact:
properties:
aws:
@@ -619,21 +595,12 @@ components:
required:
- aws
type: object
CloudintegrationtypesCredentials:
CloudintegrationtypesConnectionArtifactRequest:
properties:
ingestionKey:
type: string
ingestionUrl:
type: string
sigNozApiKey:
type: string
sigNozApiUrl:
type: string
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSConnectionArtifactRequest'
required:
- sigNozApiUrl
- sigNozApiKey
- ingestionUrl
- ingestionKey
- aws
type: object
CloudintegrationtypesDashboard:
properties:
@@ -659,7 +626,7 @@ components:
nullable: true
type: array
type: object
CloudintegrationtypesGettableAccountWithConnectionArtifact:
CloudintegrationtypesGettableAccountWithArtifact:
properties:
connectionArtifact:
$ref: '#/components/schemas/CloudintegrationtypesConnectionArtifact'
@@ -678,7 +645,7 @@ components:
required:
- accounts
type: object
CloudintegrationtypesGettableAgentCheckIn:
CloudintegrationtypesGettableAgentCheckInResponse:
properties:
account_id:
type: string
@@ -727,72 +694,12 @@ components:
type: string
type: array
telemetry:
$ref: '#/components/schemas/CloudintegrationtypesOldAWSCollectionStrategy'
$ref: '#/components/schemas/CloudintegrationtypesAWSCollectionStrategy'
required:
- enabled_regions
- telemetry
type: object
CloudintegrationtypesOldAWSCollectionStrategy:
properties:
aws_logs:
$ref: '#/components/schemas/CloudintegrationtypesOldAWSLogsStrategy'
aws_metrics:
$ref: '#/components/schemas/CloudintegrationtypesOldAWSMetricsStrategy'
provider:
type: string
s3_buckets:
additionalProperties:
items:
type: string
type: array
type: object
type: object
CloudintegrationtypesOldAWSLogsStrategy:
properties:
cloudwatch_logs_subscriptions:
items:
properties:
filter_pattern:
type: string
log_group_name_prefix:
type: string
type: object
nullable: true
type: array
type: object
CloudintegrationtypesOldAWSMetricsStrategy:
properties:
cloudwatch_metric_stream_filters:
items:
properties:
MetricNames:
items:
type: string
type: array
Namespace:
type: string
type: object
nullable: true
type: array
type: object
CloudintegrationtypesPostableAccount:
properties:
config:
$ref: '#/components/schemas/CloudintegrationtypesPostableAccountConfig'
credentials:
$ref: '#/components/schemas/CloudintegrationtypesCredentials'
required:
- config
- credentials
type: object
CloudintegrationtypesPostableAccountConfig:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSPostableAccountConfig'
required:
- aws
type: object
CloudintegrationtypesPostableAgentCheckIn:
CloudintegrationtypesPostableAgentCheckInRequest:
properties:
account_id:
type: string
@@ -820,8 +727,6 @@ components:
properties:
assets:
$ref: '#/components/schemas/CloudintegrationtypesAssets'
cloudIntegrationService:
$ref: '#/components/schemas/CloudintegrationtypesCloudIntegrationService'
dataCollected:
$ref: '#/components/schemas/CloudintegrationtypesDataCollected'
icon:
@@ -830,10 +735,12 @@ components:
type: string
overview:
type: string
supportedSignals:
serviceConfig:
$ref: '#/components/schemas/CloudintegrationtypesServiceConfig'
supported_signals:
$ref: '#/components/schemas/CloudintegrationtypesSupportedSignals'
telemetryCollectionStrategy:
$ref: '#/components/schemas/CloudintegrationtypesTelemetryCollectionStrategy'
$ref: '#/components/schemas/CloudintegrationtypesCollectionStrategy'
title:
type: string
required:
@@ -842,10 +749,9 @@ components:
- icon
- overview
- assets
- supportedSignals
- supported_signals
- dataCollected
- telemetryCollectionStrategy
- cloudIntegrationService
type: object
CloudintegrationtypesServiceConfig:
properties:
@@ -854,22 +760,6 @@ components:
required:
- aws
type: object
CloudintegrationtypesServiceID:
enum:
- alb
- api-gateway
- dynamodb
- ec2
- ecs
- eks
- elasticache
- lambda
- msk
- rds
- s3sync
- sns
- sqs
type: string
CloudintegrationtypesServiceMetadata:
properties:
enabled:
@@ -893,13 +783,6 @@ components:
metrics:
type: boolean
type: object
CloudintegrationtypesTelemetryCollectionStrategy:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSTelemetryCollectionStrategy'
required:
- aws
type: object
CloudintegrationtypesUpdatableAccount:
properties:
config:
@@ -1215,6 +1098,85 @@ components:
enabled:
type: boolean
type: object
InframonitoringtypesHostRecord:
properties:
cpu:
format: double
type: number
diskUsage:
format: double
type: number
hostName:
type: string
load15:
format: double
type: number
memory:
format: double
type: number
meta:
additionalProperties: {}
nullable: true
type: object
status:
$ref: '#/components/schemas/InframonitoringtypesHostStatus'
wait:
format: double
type: number
type: object
InframonitoringtypesHostStatus:
enum:
- active
- inactive
- ""
type: string
InframonitoringtypesHostsListRequest:
properties:
end:
format: int64
type: integer
filter:
$ref: '#/components/schemas/Querybuildertypesv5Filter'
filterByStatus:
$ref: '#/components/schemas/InframonitoringtypesHostStatus'
groupBy:
items:
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
nullable: true
type: array
limit:
type: integer
offset:
type: integer
orderBy:
$ref: '#/components/schemas/Querybuildertypesv5OrderBy'
start:
format: int64
type: integer
type: object
InframonitoringtypesHostsListResponse:
properties:
endTimeBeforeRetention:
type: boolean
records:
items:
$ref: '#/components/schemas/InframonitoringtypesHostRecord'
nullable: true
type: array
sentAnyMetricsData:
type: boolean
total:
type: integer
type:
$ref: '#/components/schemas/InframonitoringtypesResponseType'
warning:
$ref: '#/components/schemas/Querybuildertypesv5QueryWarnData'
type: object
InframonitoringtypesResponseType:
enum:
- list
- grouped_list
type: string
MetricsexplorertypesInspectMetricsRequest:
properties:
end:
@@ -3198,7 +3160,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/CloudintegrationtypesPostableAgentCheckIn'
$ref: '#/components/schemas/CloudintegrationtypesPostableAgentCheckInRequest'
responses:
"200":
content:
@@ -3206,7 +3168,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/CloudintegrationtypesGettableAgentCheckIn'
$ref: '#/components/schemas/CloudintegrationtypesGettableAgentCheckInResponse'
status:
type: string
required:
@@ -3307,7 +3269,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/CloudintegrationtypesPostableAccount'
$ref: '#/components/schemas/CloudintegrationtypesConnectionArtifactRequest'
responses:
"200":
content:
@@ -3315,7 +3277,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/CloudintegrationtypesGettableAccountWithConnectionArtifact'
$ref: '#/components/schemas/CloudintegrationtypesGettableAccountWithArtifact'
status:
type: string
required:
@@ -3511,61 +3473,6 @@ paths:
summary: Update account
tags:
- cloudintegration
/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}/services/{service_id}:
put:
deprecated: false
description: This endpoint updates a service for the specified cloud provider
operationId: UpdateService
parameters:
- in: path
name: cloud_provider
required: true
schema:
type: string
- in: path
name: id
required: true
schema:
type: string
- in: path
name: service_id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CloudintegrationtypesUpdatableService'
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update service
tags:
- cloudintegration
/api/v1/cloud_integrations/{cloud_provider}/accounts/check_in:
post:
deprecated: false
@@ -3581,7 +3488,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/CloudintegrationtypesPostableAgentCheckIn'
$ref: '#/components/schemas/CloudintegrationtypesPostableAgentCheckInRequest'
responses:
"200":
content:
@@ -3589,7 +3496,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/CloudintegrationtypesGettableAgentCheckIn'
$ref: '#/components/schemas/CloudintegrationtypesGettableAgentCheckInResponse'
status:
type: string
required:
@@ -3623,59 +3530,6 @@ paths:
summary: Agent check-in
tags:
- cloudintegration
/api/v1/cloud_integrations/{cloud_provider}/credentials:
get:
deprecated: false
description: This endpoint retrieves the connection credentials required for
integration
operationId: GetConnectionCredentials
parameters:
- in: path
name: cloud_provider
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/CloudintegrationtypesCredentials'
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Get connection credentials
tags:
- cloudintegration
/api/v1/cloud_integrations/{cloud_provider}/services:
get:
deprecated: false
@@ -3786,6 +3640,55 @@ paths:
summary: Get service
tags:
- cloudintegration
put:
deprecated: false
description: This endpoint updates a service for the specified cloud provider
operationId: UpdateService
parameters:
- in: path
name: cloud_provider
required: true
schema:
type: string
- in: path
name: service_id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CloudintegrationtypesUpdatableService'
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update service
tags:
- cloudintegration
/api/v1/complete/google:
get:
deprecated: false
@@ -7356,6 +7259,64 @@ paths:
summary: Health check
tags:
- health
/api/v2/infra-monitoring/hosts/list:
post:
deprecated: false
description: This endpoint returns a list of hosts along with other information
for each of them
operationId: HostsList
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InframonitoringtypesHostsListRequest'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/InframonitoringtypesHostsListResponse'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List Hosts for Infra Monitoring
tags:
- infra-monitoring
/api/v2/livez:
get:
deprecated: false

View File

@@ -24,8 +24,8 @@ import type {
AgentCheckInDeprecated200,
AgentCheckInDeprecatedPathParameters,
AgentCheckInPathParameters,
CloudintegrationtypesPostableAccountDTO,
CloudintegrationtypesPostableAgentCheckInDTO,
CloudintegrationtypesConnectionArtifactRequestDTO,
CloudintegrationtypesPostableAgentCheckInRequestDTO,
CloudintegrationtypesUpdatableAccountDTO,
CloudintegrationtypesUpdatableServiceDTO,
CreateAccount200,
@@ -33,8 +33,6 @@ import type {
DisconnectAccountPathParameters,
GetAccount200,
GetAccountPathParameters,
GetConnectionCredentials200,
GetConnectionCredentialsPathParameters,
GetService200,
GetServicePathParameters,
ListAccounts200,
@@ -53,14 +51,14 @@ import type {
*/
export const agentCheckInDeprecated = (
{ cloudProvider }: AgentCheckInDeprecatedPathParameters,
cloudintegrationtypesPostableAgentCheckInDTO: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>,
cloudintegrationtypesPostableAgentCheckInRequestDTO: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<AgentCheckInDeprecated200>({
url: `/api/v1/cloud-integrations/${cloudProvider}/agent-check-in`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: cloudintegrationtypesPostableAgentCheckInDTO,
data: cloudintegrationtypesPostableAgentCheckInRequestDTO,
signal,
});
};
@@ -74,7 +72,7 @@ export const getAgentCheckInDeprecatedMutationOptions = <
TError,
{
pathParams: AgentCheckInDeprecatedPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
>;
@@ -83,7 +81,7 @@ export const getAgentCheckInDeprecatedMutationOptions = <
TError,
{
pathParams: AgentCheckInDeprecatedPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
> => {
@@ -100,7 +98,7 @@ export const getAgentCheckInDeprecatedMutationOptions = <
Awaited<ReturnType<typeof agentCheckInDeprecated>>,
{
pathParams: AgentCheckInDeprecatedPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -114,7 +112,7 @@ export const getAgentCheckInDeprecatedMutationOptions = <
export type AgentCheckInDeprecatedMutationResult = NonNullable<
Awaited<ReturnType<typeof agentCheckInDeprecated>>
>;
export type AgentCheckInDeprecatedMutationBody = BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
export type AgentCheckInDeprecatedMutationBody = BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
export type AgentCheckInDeprecatedMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -130,7 +128,7 @@ export const useAgentCheckInDeprecated = <
TError,
{
pathParams: AgentCheckInDeprecatedPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
>;
@@ -139,7 +137,7 @@ export const useAgentCheckInDeprecated = <
TError,
{
pathParams: AgentCheckInDeprecatedPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
> => {
@@ -257,14 +255,14 @@ export const invalidateListAccounts = async (
*/
export const createAccount = (
{ cloudProvider }: CreateAccountPathParameters,
cloudintegrationtypesPostableAccountDTO: BodyType<CloudintegrationtypesPostableAccountDTO>,
cloudintegrationtypesConnectionArtifactRequestDTO: BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateAccount200>({
url: `/api/v1/cloud_integrations/${cloudProvider}/accounts`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: cloudintegrationtypesPostableAccountDTO,
data: cloudintegrationtypesConnectionArtifactRequestDTO,
signal,
});
};
@@ -278,7 +276,7 @@ export const getCreateAccountMutationOptions = <
TError,
{
pathParams: CreateAccountPathParameters;
data: BodyType<CloudintegrationtypesPostableAccountDTO>;
data: BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>;
},
TContext
>;
@@ -287,7 +285,7 @@ export const getCreateAccountMutationOptions = <
TError,
{
pathParams: CreateAccountPathParameters;
data: BodyType<CloudintegrationtypesPostableAccountDTO>;
data: BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>;
},
TContext
> => {
@@ -304,7 +302,7 @@ export const getCreateAccountMutationOptions = <
Awaited<ReturnType<typeof createAccount>>,
{
pathParams: CreateAccountPathParameters;
data: BodyType<CloudintegrationtypesPostableAccountDTO>;
data: BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -318,7 +316,7 @@ export const getCreateAccountMutationOptions = <
export type CreateAccountMutationResult = NonNullable<
Awaited<ReturnType<typeof createAccount>>
>;
export type CreateAccountMutationBody = BodyType<CloudintegrationtypesPostableAccountDTO>;
export type CreateAccountMutationBody = BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>;
export type CreateAccountMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -333,7 +331,7 @@ export const useCreateAccount = <
TError,
{
pathParams: CreateAccountPathParameters;
data: BodyType<CloudintegrationtypesPostableAccountDTO>;
data: BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>;
},
TContext
>;
@@ -342,7 +340,7 @@ export const useCreateAccount = <
TError,
{
pathParams: CreateAccountPathParameters;
data: BodyType<CloudintegrationtypesPostableAccountDTO>;
data: BodyType<CloudintegrationtypesConnectionArtifactRequestDTO>;
},
TContext
> => {
@@ -630,117 +628,20 @@ export const useUpdateAccount = <
return useMutation(mutationOptions);
};
/**
* This endpoint updates a service for the specified cloud provider
* @summary Update service
*/
export const updateService = (
{ cloudProvider, id, serviceId }: UpdateServicePathParameters,
cloudintegrationtypesUpdatableServiceDTO: BodyType<CloudintegrationtypesUpdatableServiceDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/cloud_integrations/${cloudProvider}/accounts/${id}/services/${serviceId}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: cloudintegrationtypesUpdatableServiceDTO,
});
};
export const getUpdateServiceMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
> => {
const mutationKey = ['updateService'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateService>>,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateService(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateServiceMutationResult = NonNullable<
Awaited<ReturnType<typeof updateService>>
>;
export type UpdateServiceMutationBody = BodyType<CloudintegrationtypesUpdatableServiceDTO>;
export type UpdateServiceMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update service
*/
export const useUpdateService = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
> => {
const mutationOptions = getUpdateServiceMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint is called by the deployed agent to check in
* @summary Agent check-in
*/
export const agentCheckIn = (
{ cloudProvider }: AgentCheckInPathParameters,
cloudintegrationtypesPostableAgentCheckInDTO: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>,
cloudintegrationtypesPostableAgentCheckInRequestDTO: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<AgentCheckIn200>({
url: `/api/v1/cloud_integrations/${cloudProvider}/accounts/check_in`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: cloudintegrationtypesPostableAgentCheckInDTO,
data: cloudintegrationtypesPostableAgentCheckInRequestDTO,
signal,
});
};
@@ -754,7 +655,7 @@ export const getAgentCheckInMutationOptions = <
TError,
{
pathParams: AgentCheckInPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
>;
@@ -763,7 +664,7 @@ export const getAgentCheckInMutationOptions = <
TError,
{
pathParams: AgentCheckInPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
> => {
@@ -780,7 +681,7 @@ export const getAgentCheckInMutationOptions = <
Awaited<ReturnType<typeof agentCheckIn>>,
{
pathParams: AgentCheckInPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -794,7 +695,7 @@ export const getAgentCheckInMutationOptions = <
export type AgentCheckInMutationResult = NonNullable<
Awaited<ReturnType<typeof agentCheckIn>>
>;
export type AgentCheckInMutationBody = BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
export type AgentCheckInMutationBody = BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
export type AgentCheckInMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -809,7 +710,7 @@ export const useAgentCheckIn = <
TError,
{
pathParams: AgentCheckInPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
>;
@@ -818,7 +719,7 @@ export const useAgentCheckIn = <
TError,
{
pathParams: AgentCheckInPathParameters;
data: BodyType<CloudintegrationtypesPostableAgentCheckInDTO>;
data: BodyType<CloudintegrationtypesPostableAgentCheckInRequestDTO>;
},
TContext
> => {
@@ -826,114 +727,6 @@ export const useAgentCheckIn = <
return useMutation(mutationOptions);
};
/**
* This endpoint retrieves the connection credentials required for integration
* @summary Get connection credentials
*/
export const getConnectionCredentials = (
{ cloudProvider }: GetConnectionCredentialsPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetConnectionCredentials200>({
url: `/api/v1/cloud_integrations/${cloudProvider}/credentials`,
method: 'GET',
signal,
});
};
export const getGetConnectionCredentialsQueryKey = ({
cloudProvider,
}: GetConnectionCredentialsPathParameters) => {
return [`/api/v1/cloud_integrations/${cloudProvider}/credentials`] as const;
};
export const getGetConnectionCredentialsQueryOptions = <
TData = Awaited<ReturnType<typeof getConnectionCredentials>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ cloudProvider }: GetConnectionCredentialsPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getConnectionCredentials>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ??
getGetConnectionCredentialsQueryKey({ cloudProvider });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getConnectionCredentials>>
> = ({ signal }) => getConnectionCredentials({ cloudProvider }, signal);
return {
queryKey,
queryFn,
enabled: !!cloudProvider,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getConnectionCredentials>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetConnectionCredentialsQueryResult = NonNullable<
Awaited<ReturnType<typeof getConnectionCredentials>>
>;
export type GetConnectionCredentialsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get connection credentials
*/
export function useGetConnectionCredentials<
TData = Awaited<ReturnType<typeof getConnectionCredentials>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ cloudProvider }: GetConnectionCredentialsPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getConnectionCredentials>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetConnectionCredentialsQueryOptions(
{ cloudProvider },
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get connection credentials
*/
export const invalidateGetConnectionCredentials = async (
queryClient: QueryClient,
{ cloudProvider }: GetConnectionCredentialsPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetConnectionCredentialsQueryKey({ cloudProvider }) },
options,
);
return queryClient;
};
/**
* This endpoint lists the services metadata for the specified cloud provider
* @summary List services metadata
@@ -1148,3 +941,101 @@ export const invalidateGetService = async (
return queryClient;
};
/**
* This endpoint updates a service for the specified cloud provider
* @summary Update service
*/
export const updateService = (
{ cloudProvider, serviceId }: UpdateServicePathParameters,
cloudintegrationtypesUpdatableServiceDTO: BodyType<CloudintegrationtypesUpdatableServiceDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: cloudintegrationtypesUpdatableServiceDTO,
});
};
export const getUpdateServiceMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
> => {
const mutationKey = ['updateService'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateService>>,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateService(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateServiceMutationResult = NonNullable<
Awaited<ReturnType<typeof updateService>>
>;
export type UpdateServiceMutationBody = BodyType<CloudintegrationtypesUpdatableServiceDTO>;
export type UpdateServiceMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update service
*/
export const useUpdateService = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateService>>,
TError,
{
pathParams: UpdateServicePathParameters;
data: BodyType<CloudintegrationtypesUpdatableServiceDTO>;
},
TContext
> => {
const mutationOptions = getUpdateServiceMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -0,0 +1,104 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
MutationFunction,
UseMutationOptions,
UseMutationResult,
} from 'react-query';
import { useMutation } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
HostsList200,
InframonitoringtypesHostsListRequestDTO,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
/**
* This endpoint returns a list of hosts along with other information for each of them
* @summary List Hosts for Infra Monitoring
*/
export const hostsList = (
inframonitoringtypesHostsListRequestDTO: BodyType<InframonitoringtypesHostsListRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<HostsList200>({
url: `/api/v2/infra-monitoring/hosts/list`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: inframonitoringtypesHostsListRequestDTO,
signal,
});
};
export const getHostsListMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof hostsList>>,
TError,
{ data: BodyType<InframonitoringtypesHostsListRequestDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof hostsList>>,
TError,
{ data: BodyType<InframonitoringtypesHostsListRequestDTO> },
TContext
> => {
const mutationKey = ['hostsList'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof hostsList>>,
{ data: BodyType<InframonitoringtypesHostsListRequestDTO> }
> = (props) => {
const { data } = props ?? {};
return hostsList(data);
};
return { mutationFn, ...mutationOptions };
};
export type HostsListMutationResult = NonNullable<
Awaited<ReturnType<typeof hostsList>>
>;
export type HostsListMutationBody = BodyType<InframonitoringtypesHostsListRequestDTO>;
export type HostsListMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List Hosts for Infra Monitoring
*/
export const useHostsList = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof hostsList>>,
TError,
{ data: BodyType<InframonitoringtypesHostsListRequestDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof hostsList>>,
TError,
{ data: BodyType<InframonitoringtypesHostsListRequestDTO> },
TContext
> => {
const mutationOptions = getHostsListMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -512,58 +512,27 @@ export interface CloudintegrationtypesAWSAccountConfigDTO {
regions: string[];
}
export interface CloudintegrationtypesAWSCloudWatchLogsSubscriptionDTO {
/**
* @type string
*/
filterPattern: string;
/**
* @type string
*/
logGroupNamePrefix: string;
}
export type CloudintegrationtypesAWSCollectionStrategyDTOS3Buckets = {
[key: string]: string[];
};
export interface CloudintegrationtypesAWSCloudWatchMetricStreamFilterDTO {
export interface CloudintegrationtypesAWSCollectionStrategyDTO {
aws_logs?: CloudintegrationtypesAWSLogsStrategyDTO;
aws_metrics?: CloudintegrationtypesAWSMetricsStrategyDTO;
/**
* @type array
* @type object
*/
metricNames?: string[];
/**
* @type string
*/
namespace: string;
s3_buckets?: CloudintegrationtypesAWSCollectionStrategyDTOS3Buckets;
}
export interface CloudintegrationtypesAWSConnectionArtifactDTO {
/**
* @type string
*/
connectionUrl: string;
connectionURL: string;
}
export interface CloudintegrationtypesAWSIntegrationConfigDTO {
/**
* @type array
*/
enabledRegions: string[];
telemetryCollectionStrategy: CloudintegrationtypesAWSTelemetryCollectionStrategyDTO;
}
export interface CloudintegrationtypesAWSLogsCollectionStrategyDTO {
/**
* @type array
*/
subscriptions: CloudintegrationtypesAWSCloudWatchLogsSubscriptionDTO[];
}
export interface CloudintegrationtypesAWSMetricsCollectionStrategyDTO {
/**
* @type array
*/
streamFilters: CloudintegrationtypesAWSCloudWatchMetricStreamFilterDTO[];
}
export interface CloudintegrationtypesAWSPostableAccountConfigDTO {
export interface CloudintegrationtypesAWSConnectionArtifactRequestDTO {
/**
* @type string
*/
@@ -574,6 +543,56 @@ export interface CloudintegrationtypesAWSPostableAccountConfigDTO {
regions: string[];
}
export interface CloudintegrationtypesAWSIntegrationConfigDTO {
/**
* @type array
*/
enabledRegions: string[];
telemetry: CloudintegrationtypesAWSCollectionStrategyDTO;
}
export type CloudintegrationtypesAWSLogsStrategyDTOCloudwatchLogsSubscriptionsItem = {
/**
* @type string
*/
filter_pattern?: string;
/**
* @type string
*/
log_group_name_prefix?: string;
};
export interface CloudintegrationtypesAWSLogsStrategyDTO {
/**
* @type array
* @nullable true
*/
cloudwatch_logs_subscriptions?:
| CloudintegrationtypesAWSLogsStrategyDTOCloudwatchLogsSubscriptionsItem[]
| null;
}
export type CloudintegrationtypesAWSMetricsStrategyDTOCloudwatchMetricStreamFiltersItem = {
/**
* @type array
*/
MetricNames?: string[];
/**
* @type string
*/
Namespace?: string;
};
export interface CloudintegrationtypesAWSMetricsStrategyDTO {
/**
* @type array
* @nullable true
*/
cloudwatch_metric_stream_filters?:
| CloudintegrationtypesAWSMetricsStrategyDTOCloudwatchMetricStreamFiltersItem[]
| null;
}
export interface CloudintegrationtypesAWSServiceConfigDTO {
logs?: CloudintegrationtypesAWSServiceLogsConfigDTO;
metrics?: CloudintegrationtypesAWSServiceMetricsConfigDTO;
@@ -591,7 +610,7 @@ export interface CloudintegrationtypesAWSServiceLogsConfigDTO {
/**
* @type object
*/
s3Buckets?: CloudintegrationtypesAWSServiceLogsConfigDTOS3Buckets;
s3_buckets?: CloudintegrationtypesAWSServiceLogsConfigDTOS3Buckets;
}
export interface CloudintegrationtypesAWSServiceMetricsConfigDTO {
@@ -601,19 +620,6 @@ export interface CloudintegrationtypesAWSServiceMetricsConfigDTO {
enabled?: boolean;
}
export type CloudintegrationtypesAWSTelemetryCollectionStrategyDTOS3Buckets = {
[key: string]: string[];
};
export interface CloudintegrationtypesAWSTelemetryCollectionStrategyDTO {
logs?: CloudintegrationtypesAWSLogsCollectionStrategyDTO;
metrics?: CloudintegrationtypesAWSMetricsCollectionStrategyDTO;
/**
* @type object
*/
s3Buckets?: CloudintegrationtypesAWSTelemetryCollectionStrategyDTOS3Buckets;
}
export interface CloudintegrationtypesAccountDTO {
agentReport: CloudintegrationtypesAgentReportDTO;
config: CloudintegrationtypesAccountConfigDTO;
@@ -687,32 +693,6 @@ export interface CloudintegrationtypesAssetsDTO {
dashboards?: CloudintegrationtypesDashboardDTO[] | null;
}
/**
* @nullable
*/
export type CloudintegrationtypesCloudIntegrationServiceDTO = {
/**
* @type string
*/
cloudIntegrationId?: string;
config?: CloudintegrationtypesServiceConfigDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
id: string;
type?: CloudintegrationtypesServiceIDDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
} | null;
export interface CloudintegrationtypesCollectedLogAttributeDTO {
/**
* @type string
@@ -747,27 +727,16 @@ export interface CloudintegrationtypesCollectedMetricDTO {
unit?: string;
}
export interface CloudintegrationtypesCollectionStrategyDTO {
aws: CloudintegrationtypesAWSCollectionStrategyDTO;
}
export interface CloudintegrationtypesConnectionArtifactDTO {
aws: CloudintegrationtypesAWSConnectionArtifactDTO;
}
export interface CloudintegrationtypesCredentialsDTO {
/**
* @type string
*/
ingestionKey: string;
/**
* @type string
*/
ingestionUrl: string;
/**
* @type string
*/
sigNozApiKey: string;
/**
* @type string
*/
sigNozApiUrl: string;
export interface CloudintegrationtypesConnectionArtifactRequestDTO {
aws: CloudintegrationtypesAWSConnectionArtifactRequestDTO;
}
export interface CloudintegrationtypesDashboardDTO {
@@ -799,7 +768,7 @@ export interface CloudintegrationtypesDataCollectedDTO {
metrics?: CloudintegrationtypesCollectedMetricDTO[] | null;
}
export interface CloudintegrationtypesGettableAccountWithConnectionArtifactDTO {
export interface CloudintegrationtypesGettableAccountWithArtifactDTO {
connectionArtifact: CloudintegrationtypesConnectionArtifactDTO;
/**
* @type string
@@ -814,7 +783,7 @@ export interface CloudintegrationtypesGettableAccountsDTO {
accounts: CloudintegrationtypesAccountDTO[];
}
export interface CloudintegrationtypesGettableAgentCheckInDTO {
export interface CloudintegrationtypesGettableAgentCheckInResponseDTO {
/**
* @type string
*/
@@ -862,85 +831,17 @@ export type CloudintegrationtypesIntegrationConfigDTO = {
* @type array
*/
enabled_regions: string[];
telemetry: CloudintegrationtypesOldAWSCollectionStrategyDTO;
telemetry: CloudintegrationtypesAWSCollectionStrategyDTO;
} | null;
export type CloudintegrationtypesOldAWSCollectionStrategyDTOS3Buckets = {
[key: string]: string[];
};
export interface CloudintegrationtypesOldAWSCollectionStrategyDTO {
aws_logs?: CloudintegrationtypesOldAWSLogsStrategyDTO;
aws_metrics?: CloudintegrationtypesOldAWSMetricsStrategyDTO;
/**
* @type string
*/
provider?: string;
/**
* @type object
*/
s3_buckets?: CloudintegrationtypesOldAWSCollectionStrategyDTOS3Buckets;
}
export type CloudintegrationtypesOldAWSLogsStrategyDTOCloudwatchLogsSubscriptionsItem = {
/**
* @type string
*/
filter_pattern?: string;
/**
* @type string
*/
log_group_name_prefix?: string;
};
export interface CloudintegrationtypesOldAWSLogsStrategyDTO {
/**
* @type array
* @nullable true
*/
cloudwatch_logs_subscriptions?:
| CloudintegrationtypesOldAWSLogsStrategyDTOCloudwatchLogsSubscriptionsItem[]
| null;
}
export type CloudintegrationtypesOldAWSMetricsStrategyDTOCloudwatchMetricStreamFiltersItem = {
/**
* @type array
*/
MetricNames?: string[];
/**
* @type string
*/
Namespace?: string;
};
export interface CloudintegrationtypesOldAWSMetricsStrategyDTO {
/**
* @type array
* @nullable true
*/
cloudwatch_metric_stream_filters?:
| CloudintegrationtypesOldAWSMetricsStrategyDTOCloudwatchMetricStreamFiltersItem[]
| null;
}
export interface CloudintegrationtypesPostableAccountDTO {
config: CloudintegrationtypesPostableAccountConfigDTO;
credentials: CloudintegrationtypesCredentialsDTO;
}
export interface CloudintegrationtypesPostableAccountConfigDTO {
aws: CloudintegrationtypesAWSPostableAccountConfigDTO;
}
/**
* @nullable
*/
export type CloudintegrationtypesPostableAgentCheckInDTOData = {
export type CloudintegrationtypesPostableAgentCheckInRequestDTOData = {
[key: string]: unknown;
} | null;
export interface CloudintegrationtypesPostableAgentCheckInDTO {
export interface CloudintegrationtypesPostableAgentCheckInRequestDTO {
/**
* @type string
*/
@@ -957,7 +858,7 @@ export interface CloudintegrationtypesPostableAgentCheckInDTO {
* @type object
* @nullable true
*/
data: CloudintegrationtypesPostableAgentCheckInDTOData;
data: CloudintegrationtypesPostableAgentCheckInRequestDTOData;
/**
* @type string
*/
@@ -970,7 +871,6 @@ export interface CloudintegrationtypesProviderIntegrationConfigDTO {
export interface CloudintegrationtypesServiceDTO {
assets: CloudintegrationtypesAssetsDTO;
cloudIntegrationService: CloudintegrationtypesCloudIntegrationServiceDTO;
dataCollected: CloudintegrationtypesDataCollectedDTO;
/**
* @type string
@@ -984,8 +884,9 @@ export interface CloudintegrationtypesServiceDTO {
* @type string
*/
overview: string;
supportedSignals: CloudintegrationtypesSupportedSignalsDTO;
telemetryCollectionStrategy: CloudintegrationtypesTelemetryCollectionStrategyDTO;
serviceConfig?: CloudintegrationtypesServiceConfigDTO;
supported_signals: CloudintegrationtypesSupportedSignalsDTO;
telemetryCollectionStrategy: CloudintegrationtypesCollectionStrategyDTO;
/**
* @type string
*/
@@ -996,21 +897,6 @@ export interface CloudintegrationtypesServiceConfigDTO {
aws: CloudintegrationtypesAWSServiceConfigDTO;
}
export enum CloudintegrationtypesServiceIDDTO {
alb = 'alb',
'api-gateway' = 'api-gateway',
dynamodb = 'dynamodb',
ec2 = 'ec2',
ecs = 'ecs',
eks = 'eks',
elasticache = 'elasticache',
lambda = 'lambda',
msk = 'msk',
rds = 'rds',
s3sync = 's3sync',
sns = 'sns',
sqs = 'sqs',
}
export interface CloudintegrationtypesServiceMetadataDTO {
/**
* @type boolean
@@ -1041,10 +927,6 @@ export interface CloudintegrationtypesSupportedSignalsDTO {
metrics?: boolean;
}
export interface CloudintegrationtypesTelemetryCollectionStrategyDTO {
aws: CloudintegrationtypesAWSTelemetryCollectionStrategyDTO;
}
export interface CloudintegrationtypesUpdatableAccountDTO {
config: CloudintegrationtypesAccountConfigDTO;
}
@@ -1448,6 +1330,111 @@ export interface GlobaltypesTokenizerConfigDTO {
enabled?: boolean;
}
/**
* @nullable
*/
export type InframonitoringtypesHostRecordDTOMeta = {
[key: string]: unknown;
} | null;
export interface InframonitoringtypesHostRecordDTO {
/**
* @type number
* @format double
*/
cpu?: number;
/**
* @type number
* @format double
*/
diskUsage?: number;
/**
* @type string
*/
hostName?: string;
/**
* @type number
* @format double
*/
load15?: number;
/**
* @type number
* @format double
*/
memory?: number;
/**
* @type object
* @nullable true
*/
meta?: InframonitoringtypesHostRecordDTOMeta;
status?: InframonitoringtypesHostStatusDTO;
/**
* @type number
* @format double
*/
wait?: number;
}
export enum InframonitoringtypesHostStatusDTO {
active = 'active',
inactive = 'inactive',
'' = '',
}
export interface InframonitoringtypesHostsListRequestDTO {
/**
* @type integer
* @format int64
*/
end?: number;
filter?: Querybuildertypesv5FilterDTO;
filterByStatus?: InframonitoringtypesHostStatusDTO;
/**
* @type array
* @nullable true
*/
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
/**
* @type integer
*/
limit?: number;
/**
* @type integer
*/
offset?: number;
orderBy?: Querybuildertypesv5OrderByDTO;
/**
* @type integer
* @format int64
*/
start?: number;
}
export interface InframonitoringtypesHostsListResponseDTO {
/**
* @type boolean
*/
endTimeBeforeRetention?: boolean;
/**
* @type array
* @nullable true
*/
records?: InframonitoringtypesHostRecordDTO[] | null;
/**
* @type boolean
*/
sentAnyMetricsData?: boolean;
/**
* @type integer
*/
total?: number;
type?: InframonitoringtypesResponseTypeDTO;
warning?: Querybuildertypesv5QueryWarnDataDTO;
}
export enum InframonitoringtypesResponseTypeDTO {
list = 'list',
grouped_list = 'grouped_list',
}
export interface MetricsexplorertypesInspectMetricsRequestDTO {
/**
* @type integer
@@ -3568,7 +3555,7 @@ export type AgentCheckInDeprecatedPathParameters = {
cloudProvider: string;
};
export type AgentCheckInDeprecated200 = {
data: CloudintegrationtypesGettableAgentCheckInDTO;
data: CloudintegrationtypesGettableAgentCheckInResponseDTO;
/**
* @type string
*/
@@ -3590,7 +3577,7 @@ export type CreateAccountPathParameters = {
cloudProvider: string;
};
export type CreateAccount200 = {
data: CloudintegrationtypesGettableAccountWithConnectionArtifactDTO;
data: CloudintegrationtypesGettableAccountWithArtifactDTO;
/**
* @type string
*/
@@ -3617,27 +3604,11 @@ export type UpdateAccountPathParameters = {
cloudProvider: string;
id: string;
};
export type UpdateServicePathParameters = {
cloudProvider: string;
id: string;
serviceId: string;
};
export type AgentCheckInPathParameters = {
cloudProvider: string;
};
export type AgentCheckIn200 = {
data: CloudintegrationtypesGettableAgentCheckInDTO;
/**
* @type string
*/
status: string;
};
export type GetConnectionCredentialsPathParameters = {
cloudProvider: string;
};
export type GetConnectionCredentials200 = {
data: CloudintegrationtypesCredentialsDTO;
data: CloudintegrationtypesGettableAgentCheckInResponseDTO;
/**
* @type string
*/
@@ -3667,6 +3638,10 @@ export type GetService200 = {
status: string;
};
export type UpdateServicePathParameters = {
cloudProvider: string;
serviceId: string;
};
export type CreateSessionByGoogleCallback303 = {
data: AuthtypesGettableTokenDTO;
/**
@@ -4322,6 +4297,14 @@ export type Healthz503 = {
status: string;
};
export type HostsList200 = {
data: InframonitoringtypesHostsListResponseDTO;
/**
* @type string
*/
status: string;
};
export type Livez200 = {
data: FactoryResponseDTO;
/**

View File

@@ -10,26 +10,6 @@ import (
)
func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/credentials", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.GetConnectionCredentials),
handler.OpenAPIDef{
ID: "GetConnectionCredentials",
Tags: []string{"cloudintegration"},
Summary: "Get connection credentials",
Description: "This endpoint retrieves the connection credentials required for integration",
Request: nil,
RequestContentType: "application/json",
Response: new(citypes.Credentials),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.CreateAccount),
handler.OpenAPIDef{
@@ -37,9 +17,9 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Tags: []string{"cloudintegration"},
Summary: "Create account",
Description: "This endpoint creates a new cloud integration account for the specified cloud provider",
Request: new(citypes.PostableAccount),
Request: new(citypes.PostableConnectionArtifact),
RequestContentType: "application/json",
Response: new(citypes.GettableAccountWithConnectionArtifact),
Response: new(citypes.GettableAccountWithArtifact),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
@@ -79,7 +59,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Description: "This endpoint gets an account for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.Account),
Response: new(citypes.GettableAccount),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
@@ -159,7 +139,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Description: "This endpoint gets a service for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.Service),
Response: new(citypes.GettableService),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
@@ -170,7 +150,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}/services/{service_id}", handler.New(
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/services/{service_id}", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.UpdateService),
handler.OpenAPIDef{
ID: "UpdateService",
@@ -199,9 +179,9 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Tags: []string{"cloudintegration"},
Summary: "Agent check-in",
Description: "[Deprecated] This endpoint is called by the deployed agent to check in",
Request: new(citypes.PostableAgentCheckIn),
Request: new(citypes.PostableAgentCheckInRequest),
RequestContentType: "application/json",
Response: new(citypes.GettableAgentCheckIn),
Response: new(citypes.GettableAgentCheckInResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
@@ -219,9 +199,9 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Tags: []string{"cloudintegration"},
Summary: "Agent check-in",
Description: "This endpoint is called by the deployed agent to check in",
Request: new(citypes.PostableAgentCheckIn),
Request: new(citypes.PostableAgentCheckInRequest),
RequestContentType: "application/json",
Response: new(citypes.GettableAgentCheckIn),
Response: new(citypes.GettableAgentCheckInResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},

View File

@@ -0,0 +1,33 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/gorilla/mux"
)
func (provider *provider) addInfraMonitoringRoutes(router *mux.Router) error {
if err := router.Handle("/api/v2/infra-monitoring/hosts/list", handler.New(
provider.authZ.ViewAccess(provider.infraMonitoringHandler.HostsList),
handler.OpenAPIDef{
ID: "HostsList",
Tags: []string{"infra-monitoring"},
Summary: "List Hosts for Infra Monitoring",
Description: "This endpoint returns a list of hosts along with other information for each of them",
Request: new(inframonitoringtypes.HostsListRequest),
RequestContentType: "application/json",
Response: new(inframonitoringtypes.HostsListResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/modules/promote"
@@ -47,6 +48,7 @@ type provider struct {
dashboardModule dashboard.Module
dashboardHandler dashboard.Handler
metricsExplorerHandler metricsexplorer.Handler
infraMonitoringHandler inframonitoring.Handler
gatewayHandler gateway.Handler
fieldsHandler fields.Handler
authzHandler authz.Handler
@@ -73,6 +75,7 @@ func NewFactory(
dashboardModule dashboard.Module,
dashboardHandler dashboard.Handler,
metricsExplorerHandler metricsexplorer.Handler,
infraMonitoringHandler inframonitoring.Handler,
gatewayHandler gateway.Handler,
fieldsHandler fields.Handler,
authzHandler authz.Handler,
@@ -102,6 +105,7 @@ func NewFactory(
dashboardModule,
dashboardHandler,
metricsExplorerHandler,
infraMonitoringHandler,
gatewayHandler,
fieldsHandler,
authzHandler,
@@ -133,6 +137,7 @@ func newProvider(
dashboardModule dashboard.Module,
dashboardHandler dashboard.Handler,
metricsExplorerHandler metricsexplorer.Handler,
infraMonitoringHandler inframonitoring.Handler,
gatewayHandler gateway.Handler,
fieldsHandler fields.Handler,
authzHandler authz.Handler,
@@ -162,6 +167,7 @@ func newProvider(
dashboardModule: dashboardModule,
dashboardHandler: dashboardHandler,
metricsExplorerHandler: metricsExplorerHandler,
infraMonitoringHandler: infraMonitoringHandler,
gatewayHandler: gatewayHandler,
fieldsHandler: fieldsHandler,
authzHandler: authzHandler,
@@ -228,6 +234,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addInfraMonitoringRoutes(router); err != nil {
return err
}
if err := provider.addGatewayRoutes(router); err != nil {
return err
}

View File

@@ -10,42 +10,37 @@ import (
)
type Module interface {
GetConnectionCredentials(ctx context.Context, orgID valuer.UUID, provider citypes.CloudProviderType) (*citypes.Credentials, error)
CreateAccount(ctx context.Context, account *citypes.Account) error
// GetAccount returns cloud integration account
GetAccount(ctx context.Context, orgID, accountID valuer.UUID, provider citypes.CloudProviderType) (*citypes.Account, error)
GetAccount(ctx context.Context, orgID, accountID valuer.UUID) (*citypes.Account, error)
// ListAccounts lists accounts where agent is connected
ListAccounts(ctx context.Context, orgID valuer.UUID, provider citypes.CloudProviderType) ([]*citypes.Account, error)
ListAccounts(ctx context.Context, orgID valuer.UUID) ([]*citypes.Account, error)
// UpdateAccount updates the cloud integration account for a specific organization.
UpdateAccount(ctx context.Context, account *citypes.Account) error
// DisconnectAccount soft deletes/removes a cloud integration account.
DisconnectAccount(ctx context.Context, orgID, accountID valuer.UUID, provider citypes.CloudProviderType) error
DisconnectAccount(ctx context.Context, orgID, accountID valuer.UUID) error
// GetConnectionArtifact returns cloud provider specific connection information,
// client side handles how this information is shown
GetConnectionArtifact(ctx context.Context, account *citypes.Account, req *citypes.GetConnectionArtifactRequest) (*citypes.ConnectionArtifact, error)
GetConnectionArtifact(ctx context.Context, account *citypes.Account, req *citypes.ConnectionArtifactRequest) (*citypes.ConnectionArtifact, error)
// ListServicesMetadata returns the list of supported services' metadata for a cloud provider with optional filtering for a specific integration
// This just returns a summary of the service and not the whole service definition.
ListServicesMetadata(ctx context.Context, orgID valuer.UUID, provider citypes.CloudProviderType, integrationID *valuer.UUID) ([]*citypes.ServiceMetadata, error)
// ListServicesMetadata returns the list of services metadata for a cloud provider attached with the integrationID.
// This just returns a summary of the service and not the whole service definition
ListServicesMetadata(ctx context.Context, orgID valuer.UUID, integrationID *valuer.UUID) ([]*citypes.ServiceMetadata, error)
// GetService returns service definition details for a serviceID. This optionally returns the service config
// for integrationID if provided.
GetService(ctx context.Context, orgID valuer.UUID, integrationID *valuer.UUID, serviceID citypes.ServiceID, provider citypes.CloudProviderType) (*citypes.Service, error)
// CreateService creates a new service for a cloud integration account.
CreateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
// GetService returns service definition details for a serviceID. This returns config and
// other details required to show in service details page on web client.
GetService(ctx context.Context, orgID valuer.UUID, integrationID *valuer.UUID, serviceID string) (*citypes.Service, error)
// UpdateService updates cloud integration service
UpdateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
UpdateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService) error
// AgentCheckIn is called by agent to send heartbeat and get latest config in response.
AgentCheckIn(ctx context.Context, orgID valuer.UUID, provider citypes.CloudProviderType, req *citypes.AgentCheckInRequest) (*citypes.AgentCheckInResponse, error)
// AgentCheckIn is called by agent to heartbeat and get latest config in response.
AgentCheckIn(ctx context.Context, orgID valuer.UUID, req *citypes.AgentCheckInRequest) (*citypes.AgentCheckInResponse, error)
// GetDashboardByID returns dashboard JSON for a given dashboard id.
// this only returns the dashboard when the service (embedded in dashboard id) is enabled
@@ -57,22 +52,7 @@ type Module interface {
ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
}
type CloudProviderModule interface {
GetConnectionArtifact(ctx context.Context, account *citypes.Account, req *citypes.GetConnectionArtifactRequest) (*citypes.ConnectionArtifact, error)
// ListServiceDefinitions returns all service definitions for this cloud provider.
ListServiceDefinitions(ctx context.Context) ([]*citypes.ServiceDefinition, error)
// GetServiceDefinition returns the service definition for the given service ID.
GetServiceDefinition(ctx context.Context, serviceID citypes.ServiceID) (*citypes.ServiceDefinition, error)
// BuildIntegrationConfig compiles the provider-specific integration config from the account
// and list of configured services. This is the config returned to the agent on check-in.
BuildIntegrationConfig(ctx context.Context, account *citypes.Account, services []*citypes.StorableCloudIntegrationService) (*citypes.ProviderIntegrationConfig, error)
}
type Handler interface {
GetConnectionCredentials(http.ResponseWriter, *http.Request)
CreateAccount(http.ResponseWriter, *http.Request)
ListAccounts(http.ResponseWriter, *http.Request)
GetAccount(http.ResponseWriter, *http.Request)

View File

@@ -447,9 +447,9 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/ApplicationELB"
"Namespace": "AWS/ApplicationELB"
}
]
}

View File

@@ -171,14 +171,14 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/ApiGateway"
"Namespace": "AWS/ApiGateway"
}
]
},
"logs": {
"subscriptions": [
"cloudwatchLogsSubscriptions": [
{
"logGroupNamePrefix": "API-Gateway",
"filterPattern": ""

View File

@@ -374,9 +374,9 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/DynamoDB"
"Namespace": "AWS/DynamoDB"
}
]
}

View File

@@ -495,12 +495,12 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/EC2"
"Namespace": "AWS/EC2"
},
{
"namespace": "CWAgent"
"Namespace": "CWAgent"
}
]
}

View File

@@ -823,17 +823,17 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/ECS"
"Namespace": "AWS/ECS"
},
{
"namespace": "ECS/ContainerInsights"
"Namespace": "ECS/ContainerInsights"
}
]
},
"logs": {
"subscriptions": [
"cloudwatchLogsSubscriptions": [
{
"logGroupNamePrefix": "/ecs",
"filterPattern": ""

View File

@@ -2702,17 +2702,17 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/EKS"
"Namespace": "AWS/EKS"
},
{
"namespace": "ContainerInsights"
"Namespace": "ContainerInsights"
}
]
},
"logs": {
"subscriptions": [
"cloudwatchLogsSubscriptions": [
{
"logGroupNamePrefix": "/aws/containerinsights",
"filterPattern": ""

View File

@@ -1934,9 +1934,9 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/ElastiCache"
"Namespace": "AWS/ElastiCache"
}
]
}

View File

@@ -271,14 +271,14 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/Lambda"
"Namespace": "AWS/Lambda"
}
]
},
"logs": {
"subscriptions": [
"cloudwatchLogsSubscriptions": [
{
"logGroupNamePrefix": "/aws/lambda",
"filterPattern": ""

View File

@@ -1070,9 +1070,9 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/Kafka"
"Namespace": "AWS/Kafka"
}
]
}

View File

@@ -775,14 +775,14 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/RDS"
"Namespace": "AWS/RDS"
}
]
},
"logs": {
"subscriptions": [
"cloudwatchLogsSubscriptions": [
{
"logGroupNamePrefix": "/aws/rds",
"filterPattern": ""

View File

@@ -39,7 +39,7 @@
"telemetryCollectionStrategy": {
"aws": {
"logs": {
"subscriptions": [
"cloudwatchLogsSubscriptions": [
{
"logGroupNamePrefix": "x/signoz/forwarder",
"filterPattern": ""

View File

@@ -110,9 +110,9 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/SNS"
"Namespace": "AWS/SNS"
}
]
}

View File

@@ -230,9 +230,9 @@
"telemetryCollectionStrategy": {
"aws": {
"metrics": {
"streamFilters": [
"cloudwatchMetricStreamFilters": [
{
"namespace": "AWS/SQS"
"Namespace": "AWS/SQS"
}
]
}

View File

@@ -12,10 +12,6 @@ func NewHandler() cloudintegration.Handler {
return &handler{}
}
func (handler *handler) GetConnectionCredentials(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}
func (handler *handler) CreateAccount(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")

View File

@@ -34,25 +34,6 @@ func (store *store) GetAccountByID(ctx context.Context, orgID, id valuer.UUID, p
return account, nil
}
func (store *store) GetConnectedAccount(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, providerAccountID string) (*cloudintegrationtypes.StorableCloudIntegration, error) {
account := new(cloudintegrationtypes.StorableCloudIntegration)
err := store.
store.
BunDBCtx(ctx).
NewSelect().
Model(account).
Where("org_id = ?", orgID).
Where("provider = ?", provider).
Where("account_id = ?", providerAccountID).
Where("last_agent_report IS NOT NULL").
Where("removed_at IS NULL").
Scan(ctx)
if err != nil {
return nil, store.store.WrapNotFoundErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "connected account with provider account id %s not found", providerAccountID)
}
return account, nil
}
func (store *store) ListConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) ([]*cloudintegrationtypes.StorableCloudIntegration, error) {
var accounts []*cloudintegrationtypes.StorableCloudIntegration
err := store.
@@ -115,6 +96,25 @@ func (store *store) RemoveAccount(ctx context.Context, orgID, id valuer.UUID, pr
return err
}
func (store *store) GetConnectedAccount(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, providerAccountID string) (*cloudintegrationtypes.StorableCloudIntegration, error) {
account := new(cloudintegrationtypes.StorableCloudIntegration)
err := store.
store.
BunDBCtx(ctx).
NewSelect().
Model(account).
Where("org_id = ?", orgID).
Where("provider = ?", provider).
Where("account_id = ?", providerAccountID).
Where("last_agent_report IS NOT NULL").
Where("removed_at IS NULL").
Scan(ctx)
if err != nil {
return nil, store.store.WrapNotFoundErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "connected account with provider account id %s not found", providerAccountID)
}
return account, nil
}
func (store *store) GetServiceByServiceID(ctx context.Context, cloudIntegrationID valuer.UUID, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.StorableCloudIntegrationService, error) {
service := new(cloudintegrationtypes.StorableCloudIntegrationService)
err := store.
@@ -172,9 +172,3 @@ func (store *store) UpdateService(ctx context.Context, service *cloudintegration
Exec(ctx)
return err
}
func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) error) error {
return store.store.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
return cb(ctx)
})
}

View File

@@ -0,0 +1,33 @@
package inframonitoring
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
)
type Config struct {
TelemetryStore TelemetryStoreConfig `mapstructure:"telemetrystore"`
}
type TelemetryStoreConfig struct {
Threads int `mapstructure:"threads"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("inframonitoring"), newConfig)
}
func newConfig() factory.Config {
return Config{
TelemetryStore: TelemetryStoreConfig{
Threads: 8,
},
}
}
func (c Config) Validate() error {
if c.TelemetryStore.Threads <= 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "inframonitoring.telemetrystore.threads must be positive, got %d", c.TelemetryStore.Threads)
}
return nil
}

View File

@@ -0,0 +1,252 @@
package implinframonitoring
import (
"context"
"fmt"
"strings"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/huandu/go-sqlbuilder"
)
func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
expression := ""
if filter != nil {
expression = strings.TrimSpace(filter.Expression)
}
if expression == "" {
return sqlbuilder.NewWhereClause(), nil
}
whereClauseSelectors := querybuilder.QueryStringToKeysSelectors(expression)
for idx := range whereClauseSelectors {
whereClauseSelectors[idx].Signal = telemetrytypes.SignalMetrics
whereClauseSelectors[idx].SelectorMatchType = telemetrytypes.FieldSelectorMatchTypeExact
}
keys, _, err := m.telemetryMetadataStore.GetKeysMulti(ctx, whereClauseSelectors)
if err != nil {
return nil, err
}
opts := querybuilder.FilterExprVisitorOpts{
Context: ctx,
Logger: m.logger,
FieldMapper: m.fieldMapper,
ConditionBuilder: m.condBuilder,
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "metric_name", FieldContext: telemetrytypes.FieldContextMetric},
FieldKeys: keys,
StartNs: querybuilder.ToNanoSecs(uint64(startMillis)),
EndNs: querybuilder.ToNanoSecs(uint64(endMillis)),
}
whereClause, err := querybuilder.PrepareWhereClause(expression, opts)
if err != nil {
return nil, err
}
if whereClause == nil || whereClause.WhereClause == nil {
return sqlbuilder.NewWhereClause(), nil
}
return whereClause.WhereClause, nil
}
// getMetricsExistenceAndEarliestTime checks whether any of the given metric names
// have been reported, and returns the total count and the earliest first-reported timestamp.
// When count is 0, minFirstReportedUnixMilli is 0.
func (m *module) getMetricsExistenceAndEarliestTime(ctx context.Context, metricNames []string) (uint64, uint64, error) {
if len(metricNames) == 0 {
return 0, 0, nil
}
sb := sqlbuilder.NewSelectBuilder()
sb.Select("count(*) AS cnt", "min(first_reported_unix_milli) AS min_first_reported")
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.AttributesMetadataTableName))
sb.Where(sb.In("metric_name", sqlbuilder.List(metricNames)))
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
var count, minFirstReported uint64
err := m.telemetryStore.ClickhouseDB().QueryRow(ctx, query, args...).Scan(&count, &minFirstReported)
if err != nil {
return 0, 0, err
}
return count, minFirstReported, nil
}
// getMetadata fetches the latest values of additionalCols for each unique combination of groupBy keys,
// within the given time range and metric names. It uses argMax(tuple(...), unix_milli) to ensure
// we always pick attribute values from the latest timestamp for each group.
//
// The returned map has a composite key of groupBy column values joined by "\x00" (null byte),
// mapping to a flat map of col_name -> col_value (includes both groupBy and additional cols).
func (m *module) getMetadata(
ctx context.Context,
metricNames []string,
groupBy []qbtypes.GroupByKey,
additionalCols []string,
filter *qbtypes.Filter,
startMs, endMs int64,
) (map[string]map[string]string, error) {
if len(metricNames) == 0 {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "metricNames must not be empty")
}
if len(groupBy) == 0 {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "groupBy must not be empty")
}
// Pick the optimal timeseries table based on time range; also get adjusted start.
adjustedStart, adjustedEnd, distributedTableName, _ := telemetrymetrics.WhichTSTableToUse(
uint64(startMs), uint64(endMs), nil,
)
// Build a fingerprint subquery against the samples table using the original
// (non-adjusted) time range. The time_series tables are ReplacingMergeTrees
// with bucketed granularity, so WhichTSTableToUse widens the window — this
// subquery restricts to fingerprints actually active in the requested range.
samplesTableName := telemetrymetrics.WhichSamplesTableToUse(
uint64(startMs), uint64(endMs),
metrictypes.UnspecifiedType,
metrictypes.TimeAggregationUnspecified,
nil,
)
fpSB := sqlbuilder.NewSelectBuilder()
fpSB.Select("DISTINCT fingerprint")
fpSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTableName))
fpSB.Where(
fpSB.In("metric_name", sqlbuilder.List(metricNames)),
fpSB.GE("unix_milli", startMs),
fpSB.L("unix_milli", endMs),
)
// Flatten groupBy keys to string names for SQL expressions and result scanning.
groupByCols := make([]string, len(groupBy))
for i, key := range groupBy {
groupByCols[i] = key.Name
}
allCols := append(groupByCols, additionalCols...)
// --- Build inner query ---
// Inner SELECT columns: JSONExtractString for each groupBy col + argMax(tuple(...)) for additional cols
innerSelectCols := make([]string, 0, len(groupByCols)+1)
for _, col := range groupByCols {
innerSelectCols = append(innerSelectCols,
fmt.Sprintf("JSONExtractString(labels, '%s') AS `%s`", col, col),
)
}
// Build the argMax(tuple(...), unix_milli) expression for all additional cols
if len(additionalCols) > 0 {
tupleArgs := make([]string, 0, len(additionalCols))
for _, col := range additionalCols {
tupleArgs = append(tupleArgs, fmt.Sprintf("JSONExtractString(labels, '%s')", col))
}
innerSelectCols = append(innerSelectCols,
fmt.Sprintf("argMax(tuple(%s), unix_milli) AS latest_attrs", strings.Join(tupleArgs, ", ")),
)
}
innerSB := sqlbuilder.NewSelectBuilder()
innerSB.Select(innerSelectCols...)
innerSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTableName))
innerSB.Where(
innerSB.In("metric_name", sqlbuilder.List(metricNames)),
innerSB.GE("unix_milli", adjustedStart),
innerSB.L("unix_milli", adjustedEnd),
fmt.Sprintf("fingerprint GLOBAL IN (%s)", innerSB.Var(fpSB)), // TODO(nikhilmantri0902): check if this can be modified to be used with local table.
)
// Apply optional filter expression
if filter != nil && strings.TrimSpace(filter.Expression) != "" {
filterClause, err := m.buildFilterClause(ctx, filter, startMs, endMs)
if err != nil {
return nil, err
}
if filterClause != nil {
innerSB.AddWhereClause(sqlbuilder.CopyWhereClause(filterClause))
}
}
groupByAliases := make([]string, 0, len(groupByCols))
for _, col := range groupByCols {
groupByAliases = append(groupByAliases, fmt.Sprintf("`%s`", col))
}
innerSB.GroupBy(groupByAliases...)
innerQuery, innerArgs := innerSB.BuildWithFlavor(sqlbuilder.ClickHouse)
// --- Build outer query ---
// Outer SELECT columns: groupBy cols directly + tupleElement(latest_attrs, N) for each additionalCol
outerSelectCols := make([]string, 0, len(allCols))
for _, col := range groupByCols {
outerSelectCols = append(outerSelectCols, fmt.Sprintf("`%s`", col))
}
for i, col := range additionalCols {
outerSelectCols = append(outerSelectCols,
fmt.Sprintf("tupleElement(latest_attrs, %d) AS `%s`", i+1, col),
)
}
outerSB := sqlbuilder.NewSelectBuilder()
outerSB.Select(outerSelectCols...)
outerSB.From(fmt.Sprintf("(%s)", innerQuery))
outerQuery, _ := outerSB.BuildWithFlavor(sqlbuilder.ClickHouse)
// All ? params are in innerArgs; outer query introduces none of its own.
rows, err := m.telemetryStore.ClickhouseDB().Query(ctx, outerQuery, innerArgs...)
if err != nil {
return nil, err
}
defer rows.Close()
result := make(map[string]map[string]string)
for rows.Next() {
row := make([]string, len(allCols))
scanPtrs := make([]any, len(row))
for i := range row {
scanPtrs[i] = &row[i]
}
if err := rows.Scan(scanPtrs...); err != nil {
return nil, err
}
compositeKey := compositeKeyFromList(row[:len(groupByCols)])
attrMap := make(map[string]string, len(allCols))
for i, col := range allCols {
attrMap[col] = row[i]
}
result[compositeKey] = attrMap
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (m *module) validateOrderBy(orderBy *qbtypes.OrderBy, orderByToQueryNamesMap map[string][]string) error {
if orderBy == nil {
return nil
}
if _, exists := orderByToQueryNamesMap[orderBy.Key.Name]; !exists {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid order by key: %s", orderBy.Key.Name)
}
if orderBy.Direction != qbtypes.OrderDirectionAsc && orderBy.Direction != qbtypes.OrderDirectionDesc {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid order by direction: %s", orderBy.Direction)
}
return nil
}

View File

@@ -0,0 +1,48 @@
package implinframonitoring
import (
"net/http"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type handler struct {
module inframonitoring.Module
}
// NewHandler returns an inframonitoring.Handler implementation.
func NewHandler(m inframonitoring.Module) inframonitoring.Handler {
return &handler{
module: m,
}
}
func (h *handler) HostsList(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
var parsedReq inframonitoringtypes.HostsListRequest
if err := binding.JSON.BindBody(req.Body, &parsedReq); err != nil {
render.Error(rw, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to parse request body"))
return
}
result, err := h.module.HostsList(req.Context(), orgID, &parsedReq)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, result)
}

View File

@@ -0,0 +1,286 @@
package implinframonitoring
import (
"fmt"
"sort"
"strings"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
type rankedGroup struct {
labels map[string]string
value float64
}
func isKeyInGroupByAttrs(groupByAttrs []qbtypes.GroupByKey, key string) bool {
for _, groupBy := range groupByAttrs {
if groupBy.Name == key {
return true
}
}
return false
}
func mergeFilterExpressions(queryFilterExpr, reqFilterExpr string) string {
queryFilterExpr = strings.TrimSpace(queryFilterExpr)
reqFilterExpr = strings.TrimSpace(reqFilterExpr)
if queryFilterExpr == "" {
return reqFilterExpr
}
if reqFilterExpr == "" {
return queryFilterExpr
}
return fmt.Sprintf("(%s) AND (%s)", queryFilterExpr, reqFilterExpr)
}
// compositeKeyFromList builds a composite key by joining the given parts
// with a null byte separator. This is the canonical way to construct
// composite keys for group identification across the infra monitoring module.
func compositeKeyFromList(parts []string) string {
return strings.Join(parts, "\x00")
}
// compositeKeyFromLabels builds a composite key from a label map by extracting
// the value for each groupBy key in order and joining them via compositeKeyFromList.
func compositeKeyFromLabels(labels map[string]string, groupBy []qbtypes.GroupByKey) string {
parts := make([]string, len(groupBy))
for i, key := range groupBy {
parts[i] = labels[key.Name]
}
return compositeKeyFromList(parts)
}
// parseAndSortGroups extracts group label maps from a ScalarData response and
// sorts them by the ranking query's aggregation value.
func parseAndSortGroups(
resp *qbtypes.QueryRangeResponse,
rankingQueryName string,
groupBy []qbtypes.GroupByKey,
direction qbtypes.OrderDirection,
) []rankedGroup {
if resp == nil || len(resp.Data.Results) == 0 {
return nil
}
// Find the ScalarData that contains the ranking column.
var sd *qbtypes.ScalarData
for _, r := range resp.Data.Results {
candidate, ok := r.(*qbtypes.ScalarData)
if !ok || candidate == nil {
continue
}
for _, col := range candidate.Columns {
if col.Type == qbtypes.ColumnTypeAggregation && col.QueryName == rankingQueryName {
sd = candidate
break
}
}
if sd != nil {
break
}
}
if sd == nil || len(sd.Data) == 0 {
return nil
}
groupColIndices := make(map[string]int)
rankingColIdx := -1
for i, col := range sd.Columns {
if col.Type == qbtypes.ColumnTypeGroup {
groupColIndices[col.Name] = i
}
if col.Type == qbtypes.ColumnTypeAggregation && col.QueryName == rankingQueryName {
rankingColIdx = i
}
}
if rankingColIdx == -1 {
return nil
}
groups := make([]rankedGroup, 0, len(sd.Data))
for _, row := range sd.Data {
labels := make(map[string]string, len(groupBy))
for _, key := range groupBy {
if idx, ok := groupColIndices[key.Name]; ok && idx < len(row) {
labels[key.Name] = fmt.Sprintf("%v", row[idx])
}
}
var value float64
if rankingColIdx < len(row) {
if v, ok := row[rankingColIdx].(float64); ok {
value = v
}
}
groups = append(groups, rankedGroup{labels: labels, value: value})
}
sort.Slice(groups, func(i, j int) bool {
if direction == qbtypes.OrderDirectionAsc {
return groups[i].value < groups[j].value
}
return groups[i].value > groups[j].value
})
return groups
}
// paginateWithBackfill returns the page of groups for [offset, offset+limit).
// The virtual sorted list is: metric-ranked groups first, then metadata-only
// groups (those in metadataMap but not in metric results) sorted alphabetically.
func paginateWithBackfill(
metricGroups []rankedGroup,
metadataMap map[string]map[string]string,
groupBy []qbtypes.GroupByKey,
offset, limit int,
) []map[string]string {
metricKeySet := make(map[string]bool, len(metricGroups))
for _, g := range metricGroups {
metricKeySet[compositeKeyFromLabels(g.labels, groupBy)] = true
}
metadataOnlyKeys := make([]string, 0)
for compositeKey := range metadataMap {
if !metricKeySet[compositeKey] {
metadataOnlyKeys = append(metadataOnlyKeys, compositeKey)
}
}
sort.Strings(metadataOnlyKeys)
totalMetric := len(metricGroups)
totalAll := totalMetric + len(metadataOnlyKeys)
end := offset + limit
if end > totalAll {
end = totalAll
}
if offset >= totalAll {
return nil
}
pageGroups := make([]map[string]string, 0, end-offset)
for i := offset; i < end; i++ {
if i < totalMetric {
pageGroups = append(pageGroups, metricGroups[i].labels)
} else {
compositeKey := metadataOnlyKeys[i-totalMetric]
attrs := metadataMap[compositeKey]
labels := make(map[string]string, len(groupBy))
for _, key := range groupBy {
labels[key.Name] = attrs[key.Name]
}
pageGroups = append(pageGroups, labels)
}
}
return pageGroups
}
// buildFullQueryRequest creates a QueryRangeRequest for all metrics,
// restricted to the given page of groups via an IN filter.
// Accepts primitive fields so it can be reused across different v2 APIs
// (hosts, pods, etc.).
func buildFullQueryRequest(
start int64,
end int64,
filterExpr string,
groupBy []qbtypes.GroupByKey,
pageGroups []map[string]string,
tableListQuery *qbtypes.QueryRangeRequest,
) *qbtypes.QueryRangeRequest {
groupValues := make(map[string][]string)
for _, labels := range pageGroups {
for k, v := range labels {
groupValues[k] = append(groupValues[k], v)
}
}
inClauses := make([]string, 0, len(groupValues))
for key, values := range groupValues {
quoted := make([]string, len(values))
for i, v := range values {
quoted[i] = fmt.Sprintf("'%s'", v)
}
inClauses = append(inClauses, fmt.Sprintf("%s IN (%s)", key, strings.Join(quoted, ", ")))
}
inFilterExpr := strings.Join(inClauses, " AND ")
fullReq := &qbtypes.QueryRangeRequest{
Start: uint64(start),
End: uint64(end),
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: make([]qbtypes.QueryEnvelope, 0, len(tableListQuery.CompositeQuery.Queries)),
},
}
for _, envelope := range tableListQuery.CompositeQuery.Queries {
copied := envelope
if copied.Type == qbtypes.QueryTypeBuilder {
existingExpr := ""
if f := copied.GetFilter(); f != nil {
existingExpr = f.Expression
}
merged := mergeFilterExpressions(existingExpr, filterExpr)
merged = mergeFilterExpressions(merged, inFilterExpr)
copied.SetFilter(&qbtypes.Filter{Expression: merged})
copied.SetGroupBy(groupBy)
}
fullReq.CompositeQuery.Queries = append(fullReq.CompositeQuery.Queries, copied)
}
return fullReq
}
// parseFullQueryResponse extracts per-group metric values from the full
// composite query response. Returns compositeKey -> (queryName -> value).
// Each enabled query/formula produces its own ScalarData entry in Results,
// so we iterate over all of them and merge metrics per composite key.
func parseFullQueryResponse(
resp *qbtypes.QueryRangeResponse,
groupBy []qbtypes.GroupByKey,
) map[string]map[string]float64 {
result := make(map[string]map[string]float64)
if resp == nil || len(resp.Data.Results) == 0 {
return result
}
for _, r := range resp.Data.Results {
sd, ok := r.(*qbtypes.ScalarData)
if !ok || sd == nil {
continue
}
groupColIndices := make(map[string]int)
aggCols := make(map[int]string) // col index -> query name
for i, col := range sd.Columns {
if col.Type == qbtypes.ColumnTypeGroup {
groupColIndices[col.Name] = i
}
if col.Type == qbtypes.ColumnTypeAggregation {
aggCols[i] = col.QueryName
}
}
for _, row := range sd.Data {
labels := make(map[string]string, len(groupBy))
for _, key := range groupBy {
if idx, ok := groupColIndices[key.Name]; ok && idx < len(row) {
labels[key.Name] = fmt.Sprintf("%v", row[idx])
}
}
compositeKey := compositeKeyFromLabels(labels, groupBy)
if result[compositeKey] == nil {
result[compositeKey] = make(map[string]float64)
}
for idx, queryName := range aggCols {
if idx < len(row) {
if v, ok := row[idx].(float64); ok {
result[compositeKey][queryName] = v
}
}
}
}
}
return result
}

View File

@@ -0,0 +1,283 @@
package implinframonitoring
import (
"testing"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
)
func groupByKey(name string) qbtypes.GroupByKey {
return qbtypes.GroupByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: name},
}
}
func TestIsKeyInGroupByAttrs(t *testing.T) {
tests := []struct {
name string
groupByAttrs []qbtypes.GroupByKey
key string
expectedFound bool
}{
{
name: "key present in single-element list",
groupByAttrs: []qbtypes.GroupByKey{groupByKey("host.name")},
key: "host.name",
expectedFound: true,
},
{
name: "key present in multi-element list",
groupByAttrs: []qbtypes.GroupByKey{
groupByKey("host.name"),
groupByKey("os.type"),
groupByKey("k8s.cluster.name"),
},
key: "os.type",
expectedFound: true,
},
{
name: "key at last position",
groupByAttrs: []qbtypes.GroupByKey{
groupByKey("host.name"),
groupByKey("os.type"),
},
key: "os.type",
expectedFound: true,
},
{
name: "key not in list",
groupByAttrs: []qbtypes.GroupByKey{groupByKey("host.name")},
key: "os.type",
expectedFound: false,
},
{
name: "empty group by list",
groupByAttrs: []qbtypes.GroupByKey{},
key: "host.name",
expectedFound: false,
},
{
name: "nil group by list",
groupByAttrs: nil,
key: "host.name",
expectedFound: false,
},
{
name: "empty key string",
groupByAttrs: []qbtypes.GroupByKey{groupByKey("host.name")},
key: "",
expectedFound: false,
},
{
name: "empty key matches empty-named group by key",
groupByAttrs: []qbtypes.GroupByKey{groupByKey("")},
key: "",
expectedFound: true,
},
{
name: "partial match does not count",
groupByAttrs: []qbtypes.GroupByKey{
groupByKey("host"),
},
key: "host.name",
expectedFound: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isKeyInGroupByAttrs(tt.groupByAttrs, tt.key)
if got != tt.expectedFound {
t.Errorf("isKeyInGroupByAttrs(%v, %q) = %v, want %v",
tt.groupByAttrs, tt.key, got, tt.expectedFound)
}
})
}
}
func TestMergeFilterExpressions(t *testing.T) {
tests := []struct {
name string
queryFilterExpr string
reqFilterExpr string
expected string
}{
{
name: "both non-empty",
queryFilterExpr: "cpu > 50",
reqFilterExpr: "host.name = 'web-1'",
expected: "(cpu > 50) AND (host.name = 'web-1')",
},
{
name: "query empty, req non-empty",
queryFilterExpr: "",
reqFilterExpr: "host.name = 'web-1'",
expected: "host.name = 'web-1'",
},
{
name: "query non-empty, req empty",
queryFilterExpr: "cpu > 50",
reqFilterExpr: "",
expected: "cpu > 50",
},
{
name: "both empty",
queryFilterExpr: "",
reqFilterExpr: "",
expected: "",
},
{
name: "whitespace-only query treated as empty",
queryFilterExpr: " ",
reqFilterExpr: "host.name = 'web-1'",
expected: "host.name = 'web-1'",
},
{
name: "whitespace-only req treated as empty",
queryFilterExpr: "cpu > 50",
reqFilterExpr: " ",
expected: "cpu > 50",
},
{
name: "both whitespace-only",
queryFilterExpr: " ",
reqFilterExpr: " ",
expected: "",
},
{
name: "leading/trailing whitespace trimmed before merge",
queryFilterExpr: " cpu > 50 ",
reqFilterExpr: " mem < 80 ",
expected: "(cpu > 50) AND (mem < 80)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := mergeFilterExpressions(tt.queryFilterExpr, tt.reqFilterExpr)
if got != tt.expected {
t.Errorf("mergeFilterExpressions(%q, %q) = %q, want %q",
tt.queryFilterExpr, tt.reqFilterExpr, got, tt.expected)
}
})
}
}
func TestCompositeKeyFromList(t *testing.T) {
tests := []struct {
name string
parts []string
expected string
}{
{
name: "single part",
parts: []string{"web-1"},
expected: "web-1",
},
{
name: "multiple parts joined with null separator",
parts: []string{"web-1", "linux", "us-east"},
expected: "web-1\x00linux\x00us-east",
},
{
name: "empty slice returns empty string",
parts: []string{},
expected: "",
},
{
name: "nil slice returns empty string",
parts: nil,
expected: "",
},
{
name: "parts with empty strings",
parts: []string{"web-1", "", "us-east"},
expected: "web-1\x00\x00us-east",
},
{
name: "all empty strings",
parts: []string{"", ""},
expected: "\x00",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := compositeKeyFromList(tt.parts)
if got != tt.expected {
t.Errorf("compositeKeyFromList(%v) = %q, want %q",
tt.parts, got, tt.expected)
}
})
}
}
func TestCompositeKeyFromLabels(t *testing.T) {
tests := []struct {
name string
labels map[string]string
groupBy []qbtypes.GroupByKey
expected string
}{
{
name: "single group-by key",
labels: map[string]string{"host.name": "web-1"},
groupBy: []qbtypes.GroupByKey{groupByKey("host.name")},
expected: "web-1",
},
{
name: "multiple group-by keys joined with null separator",
labels: map[string]string{
"host.name": "web-1",
"os.type": "linux",
},
groupBy: []qbtypes.GroupByKey{groupByKey("host.name"), groupByKey("os.type")},
expected: "web-1\x00linux",
},
{
name: "missing label yields empty segment",
labels: map[string]string{"host.name": "web-1"},
groupBy: []qbtypes.GroupByKey{groupByKey("host.name"), groupByKey("os.type")},
expected: "web-1\x00",
},
{
name: "empty labels map",
labels: map[string]string{},
groupBy: []qbtypes.GroupByKey{groupByKey("host.name")},
expected: "",
},
{
name: "empty group-by slice",
labels: map[string]string{"host.name": "web-1"},
groupBy: []qbtypes.GroupByKey{},
expected: "",
},
{
name: "nil labels map",
labels: nil,
groupBy: []qbtypes.GroupByKey{groupByKey("host.name")},
expected: "",
},
{
name: "order matches group-by order, not map iteration order",
labels: map[string]string{
"z": "last",
"a": "first",
"m": "middle",
},
groupBy: []qbtypes.GroupByKey{groupByKey("a"), groupByKey("m"), groupByKey("z")},
expected: "first\x00middle\x00last",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := compositeKeyFromLabels(tt.labels, tt.groupBy)
if got != tt.expected {
t.Errorf("compositeKeyFromLabels(%v, %v) = %q, want %q",
tt.labels, tt.groupBy, got, tt.expected)
}
})
}
}

View File

@@ -0,0 +1,492 @@
package implinframonitoring
import (
"context"
"fmt"
"slices"
"strings"
"time"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/huandu/go-sqlbuilder"
)
var (
hostNameAttrKey = "host.name"
)
// Helper group-by key used across all queries.
var hostNameGroupByKey = qbtypes.GroupByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: hostNameAttrKey,
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
},
}
var hostsTableMetricNamesList = []string{
"system.cpu.time",
"system.memory.usage",
"system.cpu.load_average.15m",
"system.filesystem.usage",
}
var hostAttrKeysForMetadata = []string{
"os.type",
}
// orderByToHostsQueryNames maps the orderBy column to the query/formula names
// from HostsTableListQuery used for ranking host groups.
var orderByToHostsQueryNames = map[string][]string{
"cpu": {"A", "B", "F1"},
"memory": {"C", "D", "F2"},
"wait": {"E", "F", "F3"},
"disk_usage": {"H", "I", "F4"},
"load15": {"G"},
}
func (m *module) newHostsTableListQuery() *qbtypes.QueryRangeRequest {
return &qbtypes.QueryRangeRequest{
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: []qbtypes.QueryEnvelope{
// Query A: CPU usage logic (non-idle)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "A",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.cpu.time",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationRate,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
Filter: &qbtypes.Filter{
Expression: "state != 'idle'",
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Query B: CPU usage (all states)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "B",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.cpu.time",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationRate,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Formula F1: CPU Usage (%)
{
Type: qbtypes.QueryTypeFormula,
Spec: qbtypes.QueryBuilderFormula{
Name: "F1",
Expression: "A/B",
Legend: "CPU Usage (%)",
Disabled: false,
},
},
// Query C: Memory usage (state = used)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "C",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.memory.usage",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
Filter: &qbtypes.Filter{
Expression: "state = 'used'",
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Query D: Memory usage (all states)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "D",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.memory.usage",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Formula F2: Memory Usage (%)
{
Type: qbtypes.QueryTypeFormula,
Spec: qbtypes.QueryBuilderFormula{
Name: "F2",
Expression: "C/D",
Legend: "Memory Usage (%)",
Disabled: false,
},
},
// Query E: CPU Wait time (state = wait)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "E",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.cpu.time",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationRate,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
Filter: &qbtypes.Filter{
Expression: "state = 'wait'",
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Query F: CPU time (all states)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "F",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.cpu.time",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationRate,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Formula F3: CPU Wait Time (%)
{
Type: qbtypes.QueryTypeFormula,
Spec: qbtypes.QueryBuilderFormula{
Name: "F3",
Expression: "E/F",
Legend: "CPU Wait Time (%)",
Disabled: false,
},
},
// Query G: Load15
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "G",
Signal: telemetrytypes.SignalMetrics,
Legend: "CPU Load Average (15m)",
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.cpu.load_average.15m",
Temporality: metrictypes.Unspecified,
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: false,
},
},
// Query H: Filesystem Usage (state = used)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "H",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.filesystem.usage",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
Filter: &qbtypes.Filter{
Expression: "state = 'used'",
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Query I: Filesystem Usage (all states)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "I",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "system.filesystem.usage",
Temporality: metrictypes.Cumulative,
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{hostNameGroupByKey},
Disabled: true,
},
},
// Formula F4: Disk Usage (%)
{
Type: qbtypes.QueryTypeFormula,
Spec: qbtypes.QueryBuilderFormula{
Name: "F4",
Expression: "H/I",
Legend: "Disk Usage (%)",
Disabled: false,
},
},
},
},
}
}
// getTopHostGroups runs a ranking query for the ordering metric, sorts the
// results, paginates, and backfills from metadataMap when the page extends
// past the metric-ranked groups.
func (m *module) getTopHostGroups(
ctx context.Context,
orgID valuer.UUID,
req *inframonitoringtypes.HostsListRequest,
metadataMap map[string]map[string]string,
) ([]map[string]string, error) {
orderByKey := req.OrderBy.Key.Name
queryNamesForOrderBy := orderByToHostsQueryNames[orderByKey]
// The last entry is the formula/query whose value we sort by.
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
topReq := &qbtypes.QueryRangeRequest{
Start: uint64(req.Start),
End: uint64(req.End),
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: make([]qbtypes.QueryEnvelope, 0, len(queryNamesForOrderBy)),
},
}
for _, envelope := range m.newHostsTableListQuery().CompositeQuery.Queries {
if !slices.Contains(queryNamesForOrderBy, envelope.GetQueryName()) {
continue
}
copied := envelope
if copied.Type == qbtypes.QueryTypeBuilder {
existingExpr := ""
if f := copied.GetFilter(); f != nil {
existingExpr = f.Expression
}
reqFilterExpr := ""
if req.Filter != nil {
reqFilterExpr = req.Filter.Expression
}
merged := mergeFilterExpressions(existingExpr, reqFilterExpr)
copied.SetFilter(&qbtypes.Filter{Expression: merged})
copied.SetGroupBy(req.GroupBy)
}
topReq.CompositeQuery.Queries = append(topReq.CompositeQuery.Queries, copied)
}
resp, err := m.querier.QueryRange(ctx, orgID, topReq)
if err != nil {
return nil, err
}
allMetricGroups := parseAndSortGroups(resp, rankingQueryName, req.GroupBy, req.OrderBy.Direction)
return paginateWithBackfill(allMetricGroups, metadataMap, req.GroupBy, req.Offset, req.Limit), nil
}
// applyHostsActiveStatusFilter modifies req.Filter.Expression to include an IN/NOT IN
// clause based on FilterByStatus and the set of active hosts.
// Returns true if the caller should short-circuit with an empty result (ACTIVE
// requested but no hosts are active).
func (m *module) applyHostsActiveStatusFilter(req *inframonitoringtypes.HostsListRequest, activeHostsMap map[string]bool) (shouldShortCircuit bool) {
if req.FilterByStatus != inframonitoringtypes.HostStatusActive && req.FilterByStatus != inframonitoringtypes.HostStatusInactive {
return false
}
activeHosts := make([]string, 0, len(activeHostsMap))
for host := range activeHostsMap {
activeHosts = append(activeHosts, fmt.Sprintf("'%s'", host))
}
if len(activeHosts) == 0 {
return req.FilterByStatus == inframonitoringtypes.HostStatusActive
}
op := "IN"
if req.FilterByStatus == inframonitoringtypes.HostStatusInactive {
op = "NOT IN"
}
if req.Filter == nil {
req.Filter = &qbtypes.Filter{}
}
statusClause := fmt.Sprintf("%s %s (%s)", hostNameAttrKey, op, strings.Join(activeHosts, ", "))
req.Filter.Expression = mergeFilterExpressions(req.Filter.Expression, statusClause)
return false
}
func (m *module) getHostsTableMetadata(ctx context.Context, req *inframonitoringtypes.HostsListRequest) (map[string]map[string]string, error) {
var nonGroupByAttrs []string
for _, key := range hostAttrKeysForMetadata {
if !isKeyInGroupByAttrs(req.GroupBy, key) {
nonGroupByAttrs = append(nonGroupByAttrs, key)
}
}
metadataMap, err := m.getMetadata(ctx, hostsTableMetricNamesList, req.GroupBy, nonGroupByAttrs, req.Filter, req.Start, req.End)
if err != nil {
return nil, err
}
return metadataMap, nil
}
// buildHostRecords constructs the final list of HostRecords for a page.
// Groups that had no metric data get default values of -1.
func (m *module) buildHostRecords(
resp *qbtypes.QueryRangeResponse,
pageGroups []map[string]string,
groupBy []qbtypes.GroupByKey,
metadataMap map[string]map[string]string,
activeHostsMap map[string]bool,
) []inframonitoringtypes.HostRecord {
metricsMap := parseFullQueryResponse(resp, groupBy)
records := make([]inframonitoringtypes.HostRecord, 0, len(pageGroups))
for _, labels := range pageGroups {
compositeKey := compositeKeyFromLabels(labels, groupBy)
hostName := labels[hostNameAttrKey]
activeStatus := inframonitoringtypes.HostStatusNone
if hostName != "" {
if activeHostsMap[hostName] {
activeStatus = inframonitoringtypes.HostStatusActive
} else {
activeStatus = inframonitoringtypes.HostStatusInactive
}
}
record := inframonitoringtypes.HostRecord{
HostName: hostName,
Status: activeStatus,
CPU: -1,
Memory: -1,
Wait: -1,
Load15: -1,
DiskUsage: -1,
Meta: map[string]interface{}{},
}
if metrics, ok := metricsMap[compositeKey]; ok {
if v, exists := metrics["F1"]; exists {
record.CPU = v
}
if v, exists := metrics["F2"]; exists {
record.Memory = v
}
if v, exists := metrics["F3"]; exists {
record.Wait = v
}
if v, exists := metrics["F4"]; exists {
record.DiskUsage = v
}
if v, exists := metrics["G"]; exists {
record.Load15 = v
}
}
if attrs, ok := metadataMap[compositeKey]; ok {
for k, v := range attrs {
record.Meta[k] = v
}
}
records = append(records, record)
}
return records
}
// getActiveHosts returns a set of host names that have reported metrics recently (since sinceUnixMilli).
// It queries distributed_metadata for hosts where last_reported_unix_milli >= sinceUnixMilli.
// TODO(nikhilmantri0902): This method does not return active hosts numbers based on custom grouping by. So
// if we have a different group by key than host.name in the API, then this method's response, will be useless technically, because
// with a group-by different from host.name, we should show count of active-inactive hosts in that group.
// We should have a way to determine active groups based on the group by keys in the request.
func (m *module) getActiveHosts(ctx context.Context, metricNames []string, hostNameAttr string) (map[string]bool, error) {
sinceUnixMilli := time.Now().Add(-10 * time.Minute).UTC().UnixMilli()
sb := sqlbuilder.NewSelectBuilder()
sb.Distinct()
sb.Select("attr_string_value")
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.AttributesMetadataTableName))
sb.Where(
sb.In("metric_name", sqlbuilder.List(metricNames)),
sb.E("attr_name", hostNameAttr),
sb.GE("last_reported_unix_milli", sinceUnixMilli),
)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
rows, err := m.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
activeHosts := make(map[string]bool)
for rows.Next() {
var hostName string
if err := rows.Scan(&hostName); err != nil {
return nil, err
}
if hostName != "" {
activeHosts[hostName] = true
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return activeHosts, nil
}

View File

@@ -0,0 +1,148 @@
package implinframonitoring
import (
"context"
"log/slog"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
telemetryStore telemetrystore.TelemetryStore
telemetryMetadataStore telemetrytypes.MetadataStore
querier querier.Querier
fieldMapper qbtypes.FieldMapper
condBuilder qbtypes.ConditionBuilder
logger *slog.Logger
config inframonitoring.Config
}
// NewModule constructs the inframonitoring module with the provided dependencies.
func NewModule(
telemetryStore telemetrystore.TelemetryStore,
telemetryMetadataStore telemetrytypes.MetadataStore,
querier querier.Querier,
providerSettings factory.ProviderSettings,
cfg inframonitoring.Config,
) inframonitoring.Module {
fieldMapper := telemetrymetrics.NewFieldMapper()
condBuilder := telemetrymetrics.NewConditionBuilder(fieldMapper)
return &module{
telemetryStore: telemetryStore,
telemetryMetadataStore: telemetryMetadataStore,
querier: querier,
fieldMapper: fieldMapper,
condBuilder: condBuilder,
logger: providerSettings.Logger,
config: cfg,
}
}
func (m *module) HostsList(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.HostsListRequest) (*inframonitoringtypes.HostsListResponse, error) {
if err := req.Validate(); err != nil {
return nil, err
}
resp := &inframonitoringtypes.HostsListResponse{}
// default to cpu order by
if req.OrderBy == nil {
req.OrderBy = &qbtypes.OrderBy{
Key: qbtypes.OrderByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: "cpu",
},
},
Direction: qbtypes.OrderDirectionDesc,
}
}
if err := m.validateOrderBy(req.OrderBy, orderByToHostsQueryNames); err != nil {
return nil, err
}
// default to host name group by
if len(req.GroupBy) == 0 {
req.GroupBy = []qbtypes.GroupByKey{hostNameGroupByKey}
resp.Type = inframonitoringtypes.ResponseTypeList
} else {
resp.Type = inframonitoringtypes.ResponseTypeGroupedList
}
// 1. Check if any host metrics exist and get earliest retention time.
// If no host metrics exist, return early — the UI shows the onboarding guide.
// 2. If metrics exist but req.End is before the earliest reported time, convey retention boundary.
count, minFirstReportedUnixMilli, err := m.getMetricsExistenceAndEarliestTime(ctx, hostsTableMetricNamesList)
if err != nil {
return nil, err
}
if count == 0 {
resp.SentAnyMetricsData = false
resp.Records = []inframonitoringtypes.HostRecord{}
resp.Total = 0
return resp, nil
}
resp.SentAnyMetricsData = true
if req.End < int64(minFirstReportedUnixMilli) {
resp.EndTimeBeforeRetention = true
resp.Records = []inframonitoringtypes.HostRecord{}
resp.Total = 0
return resp, nil
}
// Determine active hosts: those with metrics reported in the last 10 minutes.
activeHostsMap, err := m.getActiveHosts(ctx, hostsTableMetricNamesList, hostNameAttrKey)
if err != nil {
return nil, err
}
// this check below modifies req.Filter by adding `AND active hosts filter` if req.FilterByStatus is set.
if m.applyHostsActiveStatusFilter(req, activeHostsMap) {
resp.Records = []inframonitoringtypes.HostRecord{}
resp.Total = 0
return resp, nil
}
metadataMap, err := m.getHostsTableMetadata(ctx, req)
if err != nil {
return nil, err
}
if metadataMap == nil {
metadataMap = make(map[string]map[string]string)
}
resp.Total = len(metadataMap)
pageGroups, err := m.getTopHostGroups(ctx, orgID, req, metadataMap)
if err != nil {
return nil, err
}
if len(pageGroups) == 0 {
resp.Records = []inframonitoringtypes.HostRecord{}
return resp, nil
}
hostsFilterExpr := ""
if req.Filter != nil {
hostsFilterExpr = req.Filter.Expression
}
fullQueryReq := buildFullQueryRequest(req.Start, req.End, hostsFilterExpr, req.GroupBy, pageGroups, m.newHostsTableListQuery())
queryResp, err := m.querier.QueryRange(ctx, orgID, fullQueryReq)
if err != nil {
return nil, err
}
resp.Records = m.buildHostRecords(queryResp, pageGroups, req.GroupBy, metadataMap, activeHostsMap)
resp.Warning = queryResp.Warning
return resp, nil
}

View File

@@ -0,0 +1,17 @@
package inframonitoring
import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Handler interface {
HostsList(http.ResponseWriter, *http.Request)
}
type Module interface {
HostsList(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.HostsListRequest) (*inframonitoringtypes.HostsListResponse, error)
}

View File

@@ -818,9 +818,9 @@ func (v *filterExpressionVisitor) VisitFunctionCall(ctx *grammar.FunctionCallCon
case "has":
cond = fmt.Sprintf("has(%s, %s)", fieldName, v.builder.Var(value[0]))
case "hasAny":
cond = fmt.Sprintf("hasAny(%s, %s)", fieldName, v.builder.Var(value))
cond = fmt.Sprintf("hasAny(%s, %s)", fieldName, v.builder.Var(value[0]))
case "hasAll":
cond = fmt.Sprintf("hasAll(%s, %s)", fieldName, v.builder.Var(value))
cond = fmt.Sprintf("hasAll(%s, %s)", fieldName, v.builder.Var(value[0]))
}
conds = append(conds, cond)
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/identn"
"github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/user"
@@ -112,6 +113,9 @@ type Config struct {
// MetricsExplorer config
MetricsExplorer metricsexplorer.Config `mapstructure:"metricsexplorer"`
// InfraMonitoring config
InfraMonitoring inframonitoring.Config `mapstructure:"inframonitoring"`
// Flagger config
Flagger flagger.Config `mapstructure:"flagger"`
@@ -149,6 +153,7 @@ func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.R
gateway.NewConfigFactory(),
tokenizer.NewConfigFactory(),
metricsexplorer.NewConfigFactory(),
inframonitoring.NewConfigFactory(),
flagger.NewConfigFactory(),
user.NewConfigFactory(),
identn.NewConfigFactory(),

View File

@@ -18,6 +18,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/fields/implfields"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring/implinframonitoring"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
@@ -51,6 +53,7 @@ type Handlers struct {
SpanPercentile spanpercentile.Handler
Services services.Handler
MetricsExplorer metricsexplorer.Handler
InfraMonitoring inframonitoring.Handler
Global global.Handler
FlaggerHandler flagger.Handler
GatewayHandler gateway.Handler
@@ -87,6 +90,7 @@ func NewHandlers(
RawDataExport: implrawdataexport.NewHandler(modules.RawDataExport),
Services: implservices.NewHandler(modules.Services),
MetricsExplorer: implmetricsexplorer.NewHandler(modules.MetricsExplorer),
InfraMonitoring: implinframonitoring.NewHandler(modules.InfraMonitoring),
SpanPercentile: implspanpercentile.NewHandler(modules.SpanPercentile),
Global: signozglobal.NewHandler(global),
FlaggerHandler: flagger.NewHandler(flaggerService),

View File

@@ -13,6 +13,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/authdomain"
"github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring/implinframonitoring"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/organization"
@@ -69,6 +71,7 @@ type Modules struct {
Services services.Module
SpanPercentile spanpercentile.Module
MetricsExplorer metricsexplorer.Module
InfraMonitoring inframonitoring.Module
Promote promote.Module
ServiceAccount serviceaccount.Module
RuleStateHistory rulestatehistory.Module
@@ -116,6 +119,7 @@ func NewModules(
SpanPercentile: implspanpercentile.NewModule(querier, providerSettings),
Services: implservices.NewModule(querier, telemetryStore),
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
InfraMonitoring: implinframonitoring.NewModule(telemetryStore, telemetryMetadataStore, querier, providerSettings, config.InfraMonitoring),
Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore),
ServiceAccount: implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), authz, cache, analytics, providerSettings, config.ServiceAccount),
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),

View File

@@ -21,6 +21,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/modules/promote"
@@ -59,6 +60,7 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ dashboard.Module }{},
struct{ dashboard.Handler }{},
struct{ metricsexplorer.Handler }{},
struct{ inframonitoring.Handler }{},
struct{ gateway.Handler }{},
struct{ fields.Handler }{},
struct{ authz.Handler }{},

View File

@@ -276,6 +276,7 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
modules.Dashboard,
handlers.Dashboard,
handlers.MetricsExplorer,
handlers.InfraMonitoring,
handlers.GatewayHandler,
handlers.Fields,
handlers.AuthzHandler,

View File

@@ -631,7 +631,7 @@ func TestJSONStmtBuilder_ArrayPaths(t *testing.T) {
filter: "hasAll(body.user.permissions, ['read', 'write'])",
expected: TestExpected{
WhereClause: "hasAll(dynamicElement(body_v2.`user.permissions`, 'Array(Nullable(String))'), ?)",
Args: []any{uint64(1747945619), uint64(1747983448), []any{[]any{"read", "write"}}, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
Args: []any{uint64(1747945619), uint64(1747983448), []any{"read", "write"}, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
},
},
@@ -757,7 +757,7 @@ func TestJSONStmtBuilder_ArrayPaths(t *testing.T) {
filter: "hasAny(education[].awards[].participated[].members, ['Piyush', 'Tushar'])",
expected: TestExpected{
WhereClause: "hasAny(arrayFlatten(arrayConcat(arrayMap(`body_v2.education`->arrayConcat(arrayMap(`body_v2.education[].awards`->arrayConcat(arrayMap(`body_v2.education[].awards[].participated`->dynamicElement(`body_v2.education[].awards[].participated`.`members`, 'Array(Nullable(String))'), dynamicElement(`body_v2.education[].awards`.`participated`, 'Array(JSON(max_dynamic_types=4, max_dynamic_paths=0))')), arrayMap(`body_v2.education[].awards[].participated`->dynamicElement(`body_v2.education[].awards[].participated`.`members`, 'Array(Nullable(String))'), arrayMap(x->assumeNotNull(dynamicElement(x, 'JSON')), arrayFilter(x->(dynamicType(x) = 'JSON'), dynamicElement(`body_v2.education[].awards`.`participated`, 'Array(Dynamic)'))))), dynamicElement(`body_v2.education`.`awards`, 'Array(JSON(max_dynamic_types=8, max_dynamic_paths=0))')), arrayMap(`body_v2.education[].awards`->arrayConcat(arrayMap(`body_v2.education[].awards[].participated`->dynamicElement(`body_v2.education[].awards[].participated`.`members`, 'Array(Nullable(String))'), dynamicElement(`body_v2.education[].awards`.`participated`, 'Array(JSON(max_dynamic_types=16, max_dynamic_paths=256))')), arrayMap(`body_v2.education[].awards[].participated`->dynamicElement(`body_v2.education[].awards[].participated`.`members`, 'Array(Nullable(String))'), arrayMap(x->assumeNotNull(dynamicElement(x, 'JSON')), arrayFilter(x->(dynamicType(x) = 'JSON'), dynamicElement(`body_v2.education[].awards`.`participated`, 'Array(Dynamic)'))))), arrayMap(x->assumeNotNull(dynamicElement(x, 'JSON')), arrayFilter(x->(dynamicType(x) = 'JSON'), dynamicElement(`body_v2.education`.`awards`, 'Array(Dynamic)'))))), dynamicElement(body_v2.`education`, 'Array(JSON(max_dynamic_types=16, max_dynamic_paths=0))')))), ?)",
Args: []any{uint64(1747945619), uint64(1747983448), []any{[]any{"Piyush", "Tushar"}}, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
Args: []any{uint64(1747945619), uint64(1747983448), []any{"Piyush", "Tushar"}, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
},
},
{

View File

@@ -1,10 +1,8 @@
package cloudintegrationtypes
import (
"encoding/json"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -31,285 +29,16 @@ type AccountConfig struct {
AWS *AWSAccountConfig `json:"aws" required:"true" nullable:"false"`
}
type AWSAccountConfig struct {
Regions []string `json:"regions" required:"true" nullable:"false"`
}
type PostableAccount struct {
Config *PostableAccountConfig `json:"config" required:"true"`
Credentials *Credentials `json:"credentials" required:"true"`
}
type PostableAccountConfig struct {
// as agent version is common for all providers, we can keep it at top level of this struct
AgentVersion string
Aws *AWSPostableAccountConfig `json:"aws" required:"true" nullable:"false"`
}
type Credentials struct {
SigNozAPIURL string `json:"sigNozApiUrl" required:"true"`
SigNozAPIKey string `json:"sigNozApiKey" required:"true"` // PAT
IngestionURL string `json:"ingestionUrl" required:"true"`
IngestionKey string `json:"ingestionKey" required:"true"`
}
type AWSPostableAccountConfig struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
Regions []string `json:"regions" required:"true" nullable:"false"`
}
type GettableAccountWithConnectionArtifact struct {
ID valuer.UUID `json:"id" required:"true"`
ConnectionArtifact *ConnectionArtifact `json:"connectionArtifact" required:"true"`
}
type ConnectionArtifact struct {
// required till new providers are added
Aws *AWSConnectionArtifact `json:"aws" required:"true" nullable:"false"`
}
type AWSConnectionArtifact struct {
ConnectionURL string `json:"connectionUrl" required:"true"`
}
type GetConnectionArtifactRequest = PostableAccount
type GettableAccounts struct {
Accounts []*Account `json:"accounts" required:"true" nullable:"false"`
}
type GettableAccount = Account
type UpdatableAccount struct {
Config *AccountConfig `json:"config" required:"true" nullable:"false"`
}
func NewAccount(orgID valuer.UUID, provider CloudProviderType, config *AccountConfig) *Account {
return &Account{
Identifiable: types.Identifiable{
ID: valuer.GenerateUUID(),
},
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
OrgID: orgID,
Provider: provider,
Config: config,
}
}
func NewAccountFromStorable(storableAccount *StorableCloudIntegration) (*Account, error) {
// config can not be empty
if storableAccount.Config == "" {
return nil, errors.NewInternalf(errors.CodeInternal, "config is empty for account with id: %s", storableAccount.ID)
}
account := &Account{
Identifiable: storableAccount.Identifiable,
TimeAuditable: storableAccount.TimeAuditable,
ProviderAccountID: storableAccount.AccountID,
Provider: storableAccount.Provider,
RemovedAt: storableAccount.RemovedAt,
OrgID: storableAccount.OrgID,
Config: new(AccountConfig),
}
switch storableAccount.Provider {
case CloudProviderTypeAWS:
awsConfig := new(AWSAccountConfig)
err := json.Unmarshal([]byte(storableAccount.Config), awsConfig)
if err != nil {
return nil, err
}
account.Config.AWS = awsConfig
}
if storableAccount.LastAgentReport != nil {
account.AgentReport = &AgentReport{
TimestampMillis: storableAccount.LastAgentReport.TimestampMillis,
Data: storableAccount.LastAgentReport.Data,
}
}
return account, nil
}
func NewAccountsFromStorables(storableAccounts []*StorableCloudIntegration) ([]*Account, error) {
accounts := make([]*Account, 0, len(storableAccounts))
for _, storableAccount := range storableAccounts {
account, err := NewAccountFromStorable(storableAccount)
if err != nil {
return nil, err
}
accounts = append(accounts, account)
}
return accounts, nil
}
func (account *Account) Update(config *AccountConfig) error {
if account.RemovedAt != nil {
return errors.New(errors.TypeUnsupported, ErrCodeCloudIntegrationRemoved, "this operation is not supported for a removed cloud integration account")
}
account.Config = config
account.UpdatedAt = time.Now()
return nil
}
func (account *Account) IsRemoved() bool {
return account.RemovedAt != nil
}
func NewAccountConfigFromPostable(provider CloudProviderType, config *PostableAccountConfig) (*AccountConfig, error) {
switch provider {
case CloudProviderTypeAWS:
if config.Aws == nil {
return nil, errors.NewInternalf(errors.CodeInternal, "AWS config is nil")
}
return &AccountConfig{
AWS: &AWSAccountConfig{
Regions: config.Aws.Regions,
},
}, nil
}
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
// func NewAccountFromPostableAccount(provider CloudProviderType, account *PostableAccount) (*Account, error) {
// req := &Account{
// Credentials: account.Credentials,
// }
// switch provider {
// case CloudProviderTypeAWS:
// req.Config = &ConnectionArtifactRequestConfig{
// Aws: &AWSConnectionArtifactRequest{
// DeploymentRegion: artifact.Config.Aws.DeploymentRegion,
// Regions: artifact.Config.Aws.Regions,
// },
// }
// return req, nil
// default:
// return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
// }
// }
func NewAgentReport(data map[string]any) *AgentReport {
return &AgentReport{
TimestampMillis: time.Now().UnixMilli(),
Data: data,
}
}
// ToJSON return JSON bytes for the provider's config
// thats why not naming it MarshalJSON(), as it will interfere with default JSON marshalling of AccountConfig struct.
// NOTE: this entertains first non-null provider's config.
func (config *AccountConfig) ToJSON() ([]byte, error) {
if config.AWS != nil {
return json.Marshal(config.AWS)
}
return nil, errors.NewInternalf(errors.CodeInternal, "no provider account config found")
}
func (config *PostableAccountConfig) AddAgentVersion(agentVersion string) {
config.AgentVersion = agentVersion
}
// Validate checks that the connection artifact request has a valid provider-specific block
// with non-empty, valid regions and a valid deployment region.
func (account *PostableAccount) Validate(provider CloudProviderType) error {
if account.Config == nil || account.Credentials == nil {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"config and credentials are required")
}
if account.Credentials.SigNozAPIURL == "" {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"sigNozApiURL can not be empty")
}
if account.Credentials.SigNozAPIKey == "" {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"sigNozApiKey can not be empty")
}
if account.Credentials.IngestionURL == "" {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"ingestionUrl can not be empty")
}
if account.Credentials.IngestionKey == "" {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"ingestionKey can not be empty")
}
switch provider {
case CloudProviderTypeAWS:
if account.Config.Aws == nil {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"aws configuration is required")
}
return account.Config.Aws.Validate()
}
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider)
}
// Validate checks that the AWS connection artifact request has a valid deployment region
// and a non-empty list of valid regions.
func (req *AWSPostableAccountConfig) Validate() error {
if req.DeploymentRegion == "" {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"deploymentRegion is required")
}
if _, ok := ValidAWSRegions[req.DeploymentRegion]; !ok {
return errors.Newf(errors.TypeInvalidInput, ErrCodeInvalidCloudRegion,
"invalid deployment region: %s", req.DeploymentRegion)
}
if len(req.Regions) == 0 {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"at least one region is required")
}
for _, region := range req.Regions {
if _, ok := ValidAWSRegions[region]; !ok {
return errors.Newf(errors.TypeInvalidInput, ErrCodeInvalidCloudRegion,
"invalid AWS region: %s", region)
}
}
return nil
}
func (updatable *UpdatableAccount) Validate(provider CloudProviderType) error {
if updatable.Config == nil {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"config is required")
}
switch provider {
case CloudProviderTypeAWS:
if updatable.Config.AWS == nil {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"aws configuration is required")
}
if len(updatable.Config.AWS.Regions) == 0 {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"at least one region is required")
}
for _, region := range updatable.Config.AWS.Regions {
if _, ok := ValidAWSRegions[region]; !ok {
return errors.Newf(errors.TypeInvalidInput, ErrCodeInvalidCloudRegion,
"invalid AWS region: %s", region)
}
}
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput,
"invalid cloud provider: %s", provider.StringValue())
}
return nil
type AWSAccountConfig struct {
Regions []string `json:"regions" required:"true" nullable:"false"`
}

View File

@@ -1,93 +0,0 @@
package cloudintegrationtypes
import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
)
type AgentCheckInRequest struct {
ProviderAccountID string `json:"providerAccountId" required:"false"`
CloudIntegrationID valuer.UUID `json:"cloudIntegrationId" required:"false"`
Data map[string]any `json:"data" required:"true" nullable:"true"`
}
type PostableAgentCheckIn struct {
AgentCheckInRequest
// following are backward compatible fields for older running agents, hence snake case JSON keys.
// Which get mapped to new fields in AgentCheckInRequest
ID string `json:"account_id" required:"false"` // => CloudIntegrationID
AccountID string `json:"cloud_account_id" required:"false"` // => ProviderAccountID
}
type AgentCheckInResponse struct {
CloudIntegrationID string `json:"cloudIntegrationId" required:"true"`
ProviderAccountID string `json:"providerAccountId" required:"true"`
IntegrationConfig *ProviderIntegrationConfig `json:"integrationConfig" required:"true"`
RemovedAt *time.Time `json:"removedAt" required:"true" nullable:"true"`
}
type GettableAgentCheckIn struct {
// Older fields for backward compatibility with existing AWS agents
AccountID string `json:"account_id" required:"true"`
CloudAccountID string `json:"cloud_account_id" required:"true"`
OlderIntegrationConfig *IntegrationConfig `json:"integration_config" required:"true" nullable:"true"`
OlderRemovedAt *time.Time `json:"removed_at" required:"true" nullable:"true"`
AgentCheckInResponse
}
// IntegrationConfig older integration config struct for backward compatibility,
// this will be eventually removed once agents are updated to use new struct.
type IntegrationConfig struct {
EnabledRegions []string `json:"enabled_regions" required:"true" nullable:"false"` // backward compatible
Telemetry *OldAWSCollectionStrategy `json:"telemetry" required:"true" nullable:"false"` // backward compatible
}
type ProviderIntegrationConfig struct {
AWS *AWSIntegrationConfig `json:"aws" required:"true" nullable:"false"`
}
type AWSIntegrationConfig struct {
EnabledRegions []string `json:"enabledRegions" required:"true" nullable:"false"`
TelemetryCollectionStrategy *AWSTelemetryCollectionStrategy `json:"telemetryCollectionStrategy" required:"true" nullable:"false"`
}
// NewGettableAgentCheckIn constructs a backward-compatible response from an AgentCheckInResponse.
// It populates the old snake_case fields (account_id, cloud_account_id, integration_config, removed_at)
// from the new camelCase fields so older agents continue to work unchanged.
// The provider parameter controls which provider-specific block is mapped into the legacy integration_config.
func NewGettableAgentCheckIn(provider CloudProviderType, resp *AgentCheckInResponse) *GettableAgentCheckIn {
gettable := &GettableAgentCheckIn{
AccountID: resp.CloudIntegrationID,
CloudAccountID: resp.ProviderAccountID,
OlderRemovedAt: resp.RemovedAt,
AgentCheckInResponse: *resp,
}
switch provider {
case CloudProviderTypeAWS:
gettable.OlderIntegrationConfig = awsOlderIntegrationConfig(resp.IntegrationConfig)
}
return gettable
}
// Validate checks that the request uses either old fields (account_id, cloud_account_id) or
// new fields (cloudIntegrationId, providerAccountId), never a mix of both.
func (req *PostableAgentCheckIn) Validate() error {
hasOldFields := req.ID != "" || req.AccountID != ""
hasNewFields := !req.CloudIntegrationID.IsZero() || req.ProviderAccountID != ""
if hasOldFields && hasNewFields {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"request must use either old fields (account_id, cloud_account_id) or new fields (cloudIntegrationId, providerAccountId), not both")
}
if !hasOldFields && !hasNewFields {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"request must provide either old fields (account_id, cloud_account_id) or new fields (cloudIntegrationId, providerAccountId)")
}
return nil
}

View File

@@ -13,16 +13,10 @@ import (
)
var (
ErrCodeUnsupported = errors.MustNewCode("cloud_integration_unsupported")
ErrCodeInvalidInput = errors.MustNewCode("cloud_integration_invalid_input")
ErrCodeCloudIntegrationNotFound = errors.MustNewCode("cloud_integration_not_found")
ErrCodeCloudIntegrationAlreadyExists = errors.MustNewCode("cloud_integration_already_exists")
ErrCodeCloudIntegrationAlreadyConnected = errors.MustNewCode("cloud_integration_already_connected")
ErrCodeCloudIntegrationInvalidConfig = errors.MustNewCode("cloud_integration_invalid_config")
ErrCodeCloudIntegrationRemoved = errors.MustNewCode("cloud_integration_removed")
ErrCodeCloudIntegrationServiceNotFound = errors.MustNewCode("cloud_integration_service_not_found")
ErrCodeCloudIntegrationServiceAlreadyExists = errors.MustNewCode("cloud_integration_service_already_exists")
ErrCodeServiceDefinitionNotFound = errors.MustNewCode("service_definition_not_found")
)
// StorableCloudIntegration represents a cloud integration stored in the database.
@@ -58,26 +52,6 @@ type StorableCloudIntegrationService struct {
CloudIntegrationID valuer.UUID `bun:"cloud_integration_id,type:text"`
}
// Following Service config types are only internally used to store service config in DB and use JSON snake case keys for backward compatibility.
type StorableServiceConfig struct {
AWS *StorableAWSServiceConfig
}
type StorableAWSServiceConfig struct {
Logs *StorableAWSLogsServiceConfig `json:"logs,omitempty"`
Metrics *StorableAWSMetricsServiceConfig `json:"metrics,omitempty"`
}
type StorableAWSLogsServiceConfig struct {
Enabled bool `json:"enabled"`
S3Buckets map[string][]string `json:"s3_buckets,omitempty"` // region -> list of buckets in that region
}
type StorableAWSMetricsServiceConfig struct {
Enabled bool `json:"enabled"`
}
// Scan scans value from DB.
func (r *StorableAgentReport) Scan(src any) error {
var data []byte
@@ -94,6 +68,10 @@ func (r *StorableAgentReport) Scan(src any) error {
// Value creates value to be stored in DB.
func (r *StorableAgentReport) Value() (driver.Value, error) {
if r == nil {
return nil, errors.NewInternalf(errors.CodeInternal, "agent report is nil")
}
serialized, err := json.Marshal(r)
if err != nil {
return nil, errors.WrapInternalf(
@@ -103,107 +81,3 @@ func (r *StorableAgentReport) Value() (driver.Value, error) {
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytes
return string(serialized), nil
}
func NewStorableCloudIntegration(account *Account) (*StorableCloudIntegration, error) {
configBytes, err := account.Config.ToJSON()
if err != nil {
return nil, err
}
storableAccount := &StorableCloudIntegration{
Identifiable: account.Identifiable,
TimeAuditable: account.TimeAuditable,
Provider: account.Provider,
Config: string(configBytes),
AccountID: account.ProviderAccountID,
OrgID: account.OrgID,
RemovedAt: account.RemovedAt,
}
if account.AgentReport != nil {
storableAccount.LastAgentReport = &StorableAgentReport{
TimestampMillis: account.AgentReport.TimestampMillis,
Data: account.AgentReport.Data,
}
}
return storableAccount, nil
}
// NewStorableCloudIntegrationService creates a new StorableCloudIntegrationService with
// generated ID and timestamps from a CloudIntegrationService and its serialized config JSON.
func NewStorableCloudIntegrationService(svc *CloudIntegrationService, configJSON string) *StorableCloudIntegrationService {
return &StorableCloudIntegrationService{
Identifiable: svc.Identifiable,
TimeAuditable: svc.TimeAuditable,
Type: svc.Type,
Config: configJSON,
CloudIntegrationID: svc.CloudIntegrationID,
}
}
func (account *StorableCloudIntegration) Update(providerAccountID *string, agentReport *AgentReport) {
account.AccountID = providerAccountID
if agentReport != nil {
account.LastAgentReport = &StorableAgentReport{
TimestampMillis: agentReport.TimestampMillis,
Data: agentReport.Data,
}
}
}
// following StorableServiceConfig related functions are helper functions to convert between JSON string and ServiceConfig domain struct.
func newStorableServiceConfig(provider CloudProviderType, serviceID ServiceID, serviceConfig *ServiceConfig, supportedSignals *SupportedSignals) *StorableServiceConfig {
switch provider {
case CloudProviderTypeAWS:
storableAWSServiceConfig := new(StorableAWSServiceConfig)
if supportedSignals.Logs {
storableAWSServiceConfig.Logs = &StorableAWSLogsServiceConfig{
Enabled: serviceConfig.AWS.Logs.Enabled,
}
if serviceID == AWSServiceS3Sync {
storableAWSServiceConfig.Logs.S3Buckets = serviceConfig.AWS.Logs.S3Buckets
}
}
if supportedSignals.Metrics {
storableAWSServiceConfig.Metrics = &StorableAWSMetricsServiceConfig{
Enabled: serviceConfig.AWS.Metrics.Enabled,
}
}
return &StorableServiceConfig{AWS: storableAWSServiceConfig}
default:
return nil
}
}
func newStorableServiceConfigFromJSON(provider CloudProviderType, jsonStr string) (*StorableServiceConfig, error) {
switch provider {
case CloudProviderTypeAWS:
awsConfig := new(StorableAWSServiceConfig)
err := json.Unmarshal([]byte(jsonStr), awsConfig)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't parse AWS service config JSON")
}
return &StorableServiceConfig{AWS: awsConfig}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
}
func (config *StorableServiceConfig) toJSON(provider CloudProviderType) ([]byte, error) {
switch provider {
case CloudProviderTypeAWS:
jsonBytes, err := json.Marshal(config.AWS)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't serialize AWS service config to JSON")
}
return jsonBytes, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
}

View File

@@ -1,8 +1,6 @@
package cloudintegrationtypes
import (
"fmt"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -16,13 +14,19 @@ var (
CloudProviderTypeAzure = CloudProviderType{valuer.NewString("azure")}
// errors.
ErrCodeCloudProviderInvalidInput = errors.MustNewCode("cloud_integration_invalid_cloud_provider")
ErrCodeCloudProviderInvalidInput = errors.MustNewCode("invalid_cloud_provider")
CloudFormationQuickCreateBaseURL = valuer.NewString("https://%s.console.aws.amazon.com/cloudformation/home")
AgentCloudFormationTemplateS3Path = valuer.NewString("https://signoz-integrations.s3.us-east-1.amazonaws.com/aws-quickcreate-template-%s.json")
AgentCloudFormationBaseStackName = valuer.NewString("signoz-integration")
AWSIntegrationUserEmail = valuer.MustNewEmail("aws-integration@signoz.io")
AzureIntegrationUserEmail = valuer.MustNewEmail("azure-integration@signoz.io")
)
// CloudIntegrationUserEmails is the list of valid emails for Cloud One Click integrations.
// This is used for validation and restrictions in different contexts, across codebase.
var CloudIntegrationUserEmails = []valuer.Email{
AWSIntegrationUserEmail,
AzureIntegrationUserEmail,
}
// NewCloudProvider returns a new CloudProviderType from a string.
// It validates the input and returns an error if the input is not valid cloud provider.
func NewCloudProvider(provider string) (CloudProviderType, error) {
@@ -35,7 +39,3 @@ func NewCloudProvider(provider string) (CloudProviderType, error) {
return CloudProviderType{}, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider)
}
}
func NewIngestionKeyName(provider CloudProviderType) string {
return fmt.Sprintf("%s-integration", provider.StringValue())
}

View File

@@ -0,0 +1,81 @@
package cloudintegrationtypes
import (
"time"
"github.com/SigNoz/signoz/pkg/valuer"
)
type ConnectionArtifactRequest struct {
// required till new providers are added
Aws *AWSConnectionArtifactRequest `json:"aws" required:"true" nullable:"false"`
}
type AWSConnectionArtifactRequest struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
Regions []string `json:"regions" required:"true" nullable:"false"`
}
type PostableConnectionArtifact = ConnectionArtifactRequest
type ConnectionArtifact struct {
// required till new providers are added
Aws *AWSConnectionArtifact `json:"aws" required:"true" nullable:"false"`
}
type AWSConnectionArtifact struct {
ConnectionURL string `json:"connectionURL" required:"true"`
}
type GettableAccountWithArtifact struct {
ID valuer.UUID `json:"id" required:"true"`
Artifact *ConnectionArtifact `json:"connectionArtifact" required:"true"`
}
type AgentCheckInRequest struct {
ProviderAccountID string `json:"providerAccountId" required:"false"`
CloudIntegrationID string `json:"cloudIntegrationId" required:"false"`
Data map[string]any `json:"data" required:"true" nullable:"true"`
}
type PostableAgentCheckInRequest struct {
AgentCheckInRequest
// following are backward compatible fields for older running agents
// which gets mapped to new fields in AgentCheckInRequest
ID string `json:"account_id" required:"false"` // => CloudIntegrationID
AccountID string `json:"cloud_account_id" required:"false"` // => ProviderAccountID
}
type AgentCheckInResponse struct {
CloudIntegrationID string `json:"cloudIntegrationId" required:"true"`
ProviderAccountID string `json:"providerAccountId" required:"true"`
IntegrationConfig *ProviderIntegrationConfig `json:"integrationConfig" required:"true"`
RemovedAt *time.Time `json:"removedAt" required:"true" nullable:"true"`
}
type GettableAgentCheckInResponse struct {
// Older fields for backward compatibility with existing AWS agents
AccountID string `json:"account_id" required:"true"`
CloudAccountID string `json:"cloud_account_id" required:"true"`
OlderIntegrationConfig *IntegrationConfig `json:"integration_config" required:"true" nullable:"true"`
OlderRemovedAt *time.Time `json:"removed_at" required:"true" nullable:"true"`
AgentCheckInResponse
}
// IntegrationConfig older integration config struct for backward compatibility,
// this will be eventually removed once agents are updated to use new struct.
type IntegrationConfig struct {
EnabledRegions []string `json:"enabled_regions" required:"true" nullable:"false"` // backward compatible
Telemetry *AWSCollectionStrategy `json:"telemetry" required:"true" nullable:"false"` // backward compatible
}
type ProviderIntegrationConfig struct {
AWS *AWSIntegrationConfig `json:"aws" required:"true" nullable:"false"`
}
type AWSIntegrationConfig struct {
EnabledRegions []string `json:"enabledRegions" required:"true" nullable:"false"`
Telemetry *AWSCollectionStrategy `json:"telemetry" required:"true" nullable:"false"`
}

View File

@@ -4,7 +4,10 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
)
var ErrCodeInvalidCloudRegion = errors.MustNewCode("invalid_cloud_region")
var (
ErrCodeInvalidCloudRegion = errors.MustNewCode("invalid_cloud_region")
ErrCodeMismatchCloudProvider = errors.MustNewCode("cloud_provider_mismatch")
)
// List of all valid cloud regions on Amazon Web Services.
var ValidAWSRegions = map[string]struct{}{

View File

@@ -2,7 +2,6 @@ package cloudintegrationtypes
import (
"fmt"
"strings"
"time"
"github.com/SigNoz/signoz/pkg/errors"
@@ -26,22 +25,6 @@ type ServiceConfig struct {
AWS *AWSServiceConfig `json:"aws" required:"true" nullable:"false"`
}
type AWSServiceConfig struct {
Logs *AWSServiceLogsConfig `json:"logs"`
Metrics *AWSServiceMetricsConfig `json:"metrics"`
}
// AWSServiceLogsConfig is AWS specific logs config for a service
// NOTE: the JSON keys are snake case for backward compatibility with existing agents.
type AWSServiceLogsConfig struct {
Enabled bool `json:"enabled"`
S3Buckets map[string][]string `json:"s3Buckets,omitempty"`
}
type AWSServiceMetricsConfig struct {
Enabled bool `json:"enabled"`
}
// ServiceMetadata helps to quickly list available services and whether it is enabled or not.
// As getting complete service definition is a heavy operation and the response is also large,
// initial integration page load can be very slow.
@@ -62,24 +45,24 @@ type GettableServicesMetadata struct {
Services []*ServiceMetadata `json:"services" required:"true" nullable:"false"`
}
// Service represents a cloud integration service with its definition,
// cloud integration service is non nil only when the service entry exists in DB with ANY config (enabled or disabled).
type Service struct {
ServiceDefinition
CloudIntegrationService *CloudIntegrationService `json:"cloudIntegrationService" required:"true" nullable:"true"`
ServiceConfig *ServiceConfig `json:"serviceConfig" required:"false" nullable:"false"`
}
type GettableService = Service
type UpdatableService struct {
Config *ServiceConfig `json:"config" required:"true" nullable:"false"`
}
type ServiceDefinition struct {
ServiceDefinitionMetadata
Overview string `json:"overview" required:"true"` // markdown
Assets Assets `json:"assets" required:"true"`
SupportedSignals SupportedSignals `json:"supportedSignals" required:"true"`
DataCollected DataCollected `json:"dataCollected" required:"true"`
TelemetryCollectionStrategy *TelemetryCollectionStrategy `json:"telemetryCollectionStrategy" required:"true" nullable:"false"`
Overview string `json:"overview" required:"true"` // markdown
Assets Assets `json:"assets" required:"true"`
SupportedSignals SupportedSignals `json:"supported_signals" required:"true"`
DataCollected DataCollected `json:"dataCollected" required:"true"`
Strategy *CollectionStrategy `json:"telemetryCollectionStrategy" required:"true" nullable:"false"`
}
// SupportedSignals for cloud provider's service.
@@ -94,10 +77,26 @@ type DataCollected struct {
Metrics []CollectedMetric `json:"metrics"`
}
// TelemetryCollectionStrategy is cloud provider specific configuration for signal collection,
// CollectionStrategy is cloud provider specific configuration for signal collection,
// this is used by agent to understand the nitty-gritty for collecting telemetry for the cloud provider.
type TelemetryCollectionStrategy struct {
AWS *AWSTelemetryCollectionStrategy `json:"aws" required:"true" nullable:"false"`
type CollectionStrategy struct {
AWS *AWSCollectionStrategy `json:"aws" required:"true" nullable:"false"`
}
type AWSServiceConfig struct {
Logs *AWSServiceLogsConfig `json:"logs"`
Metrics *AWSServiceMetricsConfig `json:"metrics"`
}
// AWSServiceLogsConfig is AWS specific logs config for a service
// NOTE: the JSON keys are snake case for backward compatibility with existing agents.
type AWSServiceLogsConfig struct {
Enabled bool `json:"enabled"`
S3Buckets map[string][]string `json:"s3_buckets,omitempty"`
}
type AWSServiceMetricsConfig struct {
Enabled bool `json:"enabled"`
}
// Assets represents the collection of dashboards.
@@ -121,65 +120,46 @@ type CollectedMetric struct {
Description string `json:"description"`
}
// OldAWSCollectionStrategy is the backward-compatible snake_case form of AWSCollectionStrategy,
// used in the legacy integration_config response field for older agents.
type OldAWSCollectionStrategy struct {
Provider string `json:"provider"`
Metrics *OldAWSMetricsStrategy `json:"aws_metrics,omitempty"`
Logs *OldAWSLogsStrategy `json:"aws_logs,omitempty"`
S3Buckets map[string][]string `json:"s3_buckets,omitempty"`
// AWSCollectionStrategy represents signal collection strategy for AWS services.
// this is AWS specific.
// NOTE: this structure is still using snake case, for backward compatibility,
// with existing agents.
type AWSCollectionStrategy struct {
Metrics *AWSMetricsStrategy `json:"aws_metrics,omitempty"`
Logs *AWSLogsStrategy `json:"aws_logs,omitempty"`
S3Buckets map[string][]string `json:"s3_buckets,omitempty"` // Only available in S3 Sync Service Type in AWS
}
// OldAWSMetricsStrategy is the snake_case form of AWSMetricsStrategy for older agents.
type OldAWSMetricsStrategy struct {
// AWSMetricsStrategy represents metrics collection strategy for AWS services.
// this is AWS specific.
// NOTE: this structure is still using snake case, for backward compatibility,
// with existing agents.
type AWSMetricsStrategy struct {
// to be used as https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-metricstream.html#cfn-cloudwatch-metricstream-includefilters
StreamFilters []struct {
// json tags here are in the shape expected by AWS API as detailed at
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-metricstream-metricstreamfilter.html
Namespace string `json:"Namespace"`
MetricNames []string `json:"MetricNames,omitempty"`
} `json:"cloudwatch_metric_stream_filters"`
}
// OldAWSLogsStrategy is the snake_case form of AWSLogsStrategy for older agents.
type OldAWSLogsStrategy struct {
// AWSLogsStrategy represents logs collection strategy for AWS services.
// this is AWS specific.
// NOTE: this structure is still using snake case, for backward compatibility,
// with existing agents.
type AWSLogsStrategy struct {
Subscriptions []struct {
// subscribe to all logs groups with specified prefix.
// eg: `/aws/rds/`
LogGroupNamePrefix string `json:"log_group_name_prefix"`
FilterPattern string `json:"filter_pattern"`
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html
// "" implies no filtering is required.
FilterPattern string `json:"filter_pattern"`
} `json:"cloudwatch_logs_subscriptions"`
}
// AWSTelemetryCollectionStrategy represents signal collection strategy for AWS services.
type AWSTelemetryCollectionStrategy struct {
Metrics *AWSMetricsCollectionStrategy `json:"metrics,omitempty" required:"false" nullable:"false"`
Logs *AWSLogsCollectionStrategy `json:"logs,omitempty" required:"false" nullable:"false"`
S3Buckets map[string][]string `json:"s3Buckets,omitempty" required:"false"` // Only available in S3 Sync Service Type in AWS
}
// AWSMetricsCollectionStrategy represents metrics collection strategy for AWS services.
type AWSMetricsCollectionStrategy struct {
// to be used as https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-metricstream.html#cfn-cloudwatch-metricstream-includefilters
StreamFilters []*AWSCloudWatchMetricStreamFilter `json:"streamFilters" required:"true" nullable:"false"`
}
type AWSCloudWatchMetricStreamFilter struct {
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-metricstream-metricstreamfilter.html
Namespace string `json:"namespace" required:"true"`
MetricNames []string `json:"metricNames,omitempty" required:"false" nullable:"false"`
}
// AWSLogsCollectionStrategy represents logs collection strategy for AWS services.
type AWSLogsCollectionStrategy struct {
Subscriptions []*AWSCloudWatchLogsSubscription `json:"subscriptions" required:"true" nullable:"false"`
}
type AWSCloudWatchLogsSubscription struct {
// subscribe to all logs groups with specified prefix.
// eg: `/aws/rds/`
LogGroupNamePrefix string `json:"logGroupNamePrefix" required:"true"`
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html
// "" implies no filtering is required
FilterPattern string `json:"filterPattern" required:"true"`
}
// Dashboard represents a dashboard definition for cloud integration.
// This is used to show available pre-made dashboards for a service,
// hence has additional fields like id, title and description.
@@ -190,156 +170,12 @@ type Dashboard struct {
Definition dashboardtypes.StorableDashboardData `json:"definition,omitempty"`
}
func NewCloudIntegrationService(serviceID ServiceID, cloudIntegrationID valuer.UUID, config *ServiceConfig) *CloudIntegrationService {
return &CloudIntegrationService{
Identifiable: types.Identifiable{
ID: valuer.GenerateUUID(),
},
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Type: serviceID,
Config: config,
CloudIntegrationID: cloudIntegrationID,
}
}
func NewCloudIntegrationServiceFromStorable(stored *StorableCloudIntegrationService, config *ServiceConfig) *CloudIntegrationService {
return &CloudIntegrationService{
Identifiable: stored.Identifiable,
TimeAuditable: stored.TimeAuditable,
Type: stored.Type,
Config: config,
CloudIntegrationID: stored.CloudIntegrationID,
}
}
func NewServiceMetadata(definition ServiceDefinition, enabled bool) *ServiceMetadata {
return &ServiceMetadata{
ServiceDefinitionMetadata: definition.ServiceDefinitionMetadata,
Enabled: enabled,
}
}
func NewService(def ServiceDefinition, storableService *CloudIntegrationService) *Service {
return &Service{
ServiceDefinition: def,
CloudIntegrationService: storableService,
}
}
func NewServiceConfigFromJSON(provider CloudProviderType, jsonString string) (*ServiceConfig, error) {
storableServiceConfig, err := newStorableServiceConfigFromJSON(provider, jsonString)
if err != nil {
return nil, err
}
switch provider {
case CloudProviderTypeAWS:
awsServiceConfig := new(AWSServiceConfig)
if storableServiceConfig.AWS.Logs != nil {
awsServiceConfig.Logs = &AWSServiceLogsConfig{
Enabled: storableServiceConfig.AWS.Logs.Enabled,
S3Buckets: storableServiceConfig.AWS.Logs.S3Buckets,
}
}
if storableServiceConfig.AWS.Metrics != nil {
awsServiceConfig.Metrics = &AWSServiceMetricsConfig{
Enabled: storableServiceConfig.AWS.Metrics.Enabled,
}
}
return &ServiceConfig{AWS: awsServiceConfig}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
}
// Update sets the service config.
func (service *CloudIntegrationService) Update(config *ServiceConfig) {
service.Config = config
service.UpdatedAt = time.Now()
}
// IsServiceEnabled returns true if the service has at least one signal (logs or metrics) enabled
// for the given cloud provider.
func (config *ServiceConfig) IsServiceEnabled(provider CloudProviderType) bool {
switch provider {
case CloudProviderTypeAWS:
logsEnabled := config.AWS.Logs != nil && config.AWS.Logs.Enabled
metricsEnabled := config.AWS.Metrics != nil && config.AWS.Metrics.Enabled
return logsEnabled || metricsEnabled
default:
return false
}
}
// IsMetricsEnabled returns true if metrics are explicitly enabled for the given cloud provider.
// Used to gate dashboard availability — dashboards are only shown when metrics are enabled.
func (config *ServiceConfig) IsMetricsEnabled(provider CloudProviderType) bool {
switch provider {
case CloudProviderTypeAWS:
return config.AWS.Metrics != nil && config.AWS.Metrics.Enabled
default:
return false
}
}
// IsLogsEnabled returns true if logs are explicitly enabled for the given cloud provider.
func (config *ServiceConfig) IsLogsEnabled(provider CloudProviderType) bool {
switch provider {
case CloudProviderTypeAWS:
return config.AWS.Logs != nil && config.AWS.Logs.Enabled
default:
return false
}
}
func (config *ServiceConfig) ToJSON(provider CloudProviderType, serviceID ServiceID, supportedSignals *SupportedSignals) ([]byte, error) {
storableServiceConfig := newStorableServiceConfig(provider, serviceID, config, supportedSignals)
return storableServiceConfig.toJSON(provider)
}
func (updatableService *UpdatableService) Validate(provider CloudProviderType, serviceID ServiceID) error {
switch provider {
case CloudProviderTypeAWS:
if updatableService.Config.AWS == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS config is required for AWS service")
}
if serviceID == AWSServiceS3Sync {
if updatableService.Config.AWS.Logs == nil || updatableService.Config.AWS.Logs.S3Buckets == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS S3 Sync service requires S3 bucket configuration for logs")
}
}
return nil
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
}
// UTILS
// GetCloudIntegrationDashboardID returns the dashboard id for a cloud integration, given the cloud provider, service id, and dashboard id.
// This is used to generate unique dashboard ids for cloud integration, and also to parse the dashboard id to get the cloud provider and service id when needed.
func GetCloudIntegrationDashboardID(cloudProvider CloudProviderType, svcID, dashboardID string) string {
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider.StringValue(), svcID, dashboardID)
}
// ParseCloudIntegrationDashboardID parses a dashboard id generated by GetCloudIntegrationDashboardID
// into its constituent parts (cloudProvider, serviceID, dashboardID).
func ParseCloudIntegrationDashboardID(id string) (CloudProviderType, string, string, error) {
parts := strings.SplitN(id, "--", 4)
if len(parts) != 4 || parts[0] != "cloud-integration" {
return CloudProviderType{}, "", "", errors.New(errors.TypeNotFound, ErrCodeCloudIntegrationNotFound, "invalid cloud integration dashboard id")
}
provider, err := NewCloudProvider(parts[1])
if err != nil {
return CloudProviderType{}, "", "", err
}
return provider, parts[2], parts[3], nil
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcID, dashboardID)
}
// GetDashboardsFromAssets returns the list of dashboards for the cloud provider service from definition.
@@ -353,9 +189,9 @@ func GetDashboardsFromAssets(
dashboards := make([]*dashboardtypes.Dashboard, 0)
for _, d := range assets.Dashboards {
author := fmt.Sprintf("%s-integration", cloudProvider.StringValue())
author := fmt.Sprintf("%s-integration", cloudProvider)
dashboards = append(dashboards, &dashboardtypes.Dashboard{
ID: d.ID,
ID: GetCloudIntegrationDashboardID(cloudProvider, svcID, d.ID),
Locked: true,
OrgID: orgID,
Data: d.Definition,
@@ -372,53 +208,3 @@ func GetDashboardsFromAssets(
return dashboards
}
// awsOlderIntegrationConfig converts a ProviderIntegrationConfig into the legacy snake_case
// IntegrationConfig format consumed by older AWS agents. Returns nil if AWS config is absent.
func awsOlderIntegrationConfig(cfg *ProviderIntegrationConfig) *IntegrationConfig {
if cfg == nil || cfg.AWS == nil {
return nil
}
awsCfg := cfg.AWS
older := &IntegrationConfig{
EnabledRegions: awsCfg.EnabledRegions,
}
if awsCfg.TelemetryCollectionStrategy == nil {
return older
}
// Older agents expect a "provider" field and fully snake_case keys inside telemetry.
oldTelemetry := &OldAWSCollectionStrategy{
Provider: CloudProviderTypeAWS.StringValue(),
S3Buckets: awsCfg.TelemetryCollectionStrategy.S3Buckets,
}
if awsCfg.TelemetryCollectionStrategy.Metrics != nil {
// Convert camelCase cloudwatchMetricStreamFilters → snake_case cloudwatch_metric_stream_filters
oldMetrics := &OldAWSMetricsStrategy{}
for _, f := range awsCfg.TelemetryCollectionStrategy.Metrics.StreamFilters {
oldMetrics.StreamFilters = append(oldMetrics.StreamFilters, struct {
Namespace string `json:"Namespace"`
MetricNames []string `json:"MetricNames,omitempty"`
}{Namespace: f.Namespace, MetricNames: f.MetricNames})
}
oldTelemetry.Metrics = oldMetrics
}
if awsCfg.TelemetryCollectionStrategy.Logs != nil {
// Convert camelCase cloudwatchLogsSubscriptions → snake_case cloudwatch_logs_subscriptions
oldLogs := &OldAWSLogsStrategy{}
for _, s := range awsCfg.TelemetryCollectionStrategy.Logs.Subscriptions {
oldLogs.Subscriptions = append(oldLogs.Subscriptions, struct {
LogGroupNamePrefix string `json:"log_group_name_prefix"`
FilterPattern string `json:"filter_pattern"`
}{LogGroupNamePrefix: s.LogGroupNamePrefix, FilterPattern: s.FilterPattern})
}
oldTelemetry.Logs = oldLogs
}
older.Telemetry = oldTelemetry
return older
}

View File

@@ -38,8 +38,6 @@ type Store interface {
// UpdateService updates an existing cloud integration service
UpdateService(ctx context.Context, service *StorableCloudIntegrationService) error
RunInTx(context.Context, func(ctx context.Context) error) error
}
type ServiceDefinitionStore interface {

View File

@@ -0,0 +1,21 @@
package inframonitoringtypes
import (
"github.com/SigNoz/signoz/pkg/valuer"
)
type ResponseType struct {
valuer.String
}
var (
ResponseTypeList = ResponseType{valuer.NewString("list")}
ResponseTypeGroupedList = ResponseType{valuer.NewString("grouped_list")}
)
func (ResponseType) Enum() []any {
return []any{
ResponseTypeList,
ResponseTypeGroupedList,
}
}

View File

@@ -0,0 +1,115 @@
package inframonitoringtypes
import (
"encoding/json"
"github.com/SigNoz/signoz/pkg/errors"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
)
type HostStatus struct {
valuer.String
}
var (
HostStatusActive = HostStatus{valuer.NewString("active")}
HostStatusInactive = HostStatus{valuer.NewString("inactive")}
HostStatusNone = HostStatus{valuer.NewString("")}
)
func (HostStatus) Enum() []any {
return []any{
HostStatusActive,
HostStatusInactive,
HostStatusNone,
}
}
type HostsListRequest struct {
Start int64 `json:"start"`
End int64 `json:"end"`
Filter *qbtypes.Filter `json:"filter"`
FilterByStatus HostStatus `json:"filterByStatus"`
GroupBy []qbtypes.GroupByKey `json:"groupBy"`
OrderBy *qbtypes.OrderBy `json:"orderBy"`
Offset int `json:"offset"`
Limit int `json:"limit"`
}
// Validate ensures HostsListRequest contains acceptable values.
func (req *HostsListRequest) Validate() error {
if req == nil {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")
}
if req.Start <= 0 {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid start time %d: start must be greater than 0",
req.Start,
)
}
if req.End <= 0 {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid end time %d: end must be greater than 0",
req.End,
)
}
if req.Start >= req.End {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid time range: start (%d) must be less than end (%d)",
req.Start,
req.End,
)
}
if req.Limit < 1 || req.Limit > 5000 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "limit must be between 1 and 5000")
}
if req.Offset < 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "offset cannot be negative")
}
if !req.FilterByStatus.IsZero() && req.FilterByStatus != HostStatusActive && req.FilterByStatus != HostStatusInactive {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid filter by status: %s", req.FilterByStatus)
}
return nil
}
// UnmarshalJSON validates input immediately after decoding.
func (req *HostsListRequest) UnmarshalJSON(data []byte) error {
type raw HostsListRequest
var decoded raw
if err := json.Unmarshal(data, &decoded); err != nil {
return err
}
*req = HostsListRequest(decoded)
return req.Validate()
}
type HostsListResponse struct {
Type ResponseType `json:"type"`
Records []HostRecord `json:"records"`
Total int `json:"total"`
SentAnyMetricsData bool `json:"sentAnyMetricsData"`
EndTimeBeforeRetention bool `json:"endTimeBeforeRetention"`
Warning *qbtypes.QueryWarnData `json:"warning,omitempty"`
}
type HostRecord struct {
HostName string `json:"hostName"`
Status HostStatus `json:"status"`
CPU float64 `json:"cpu"`
Memory float64 `json:"memory"`
Wait float64 `json:"wait"`
Load15 float64 `json:"load15"`
DiskUsage float64 `json:"diskUsage"`
Meta map[string]interface{} `json:"meta"`
}

View File

@@ -0,0 +1,178 @@
package inframonitoringtypes
import (
"testing"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestHostsListRequest_Validate(t *testing.T) {
tests := []struct {
name string
req *HostsListRequest
wantErr bool
}{
{
name: "valid request",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: false,
},
{
name: "nil request",
req: nil,
wantErr: true,
},
{
name: "start time zero",
req: &HostsListRequest{
Start: 0,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "start time negative",
req: &HostsListRequest{
Start: -1000,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "end time zero",
req: &HostsListRequest{
Start: 1000,
End: 0,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "start time greater than end time",
req: &HostsListRequest{
Start: 2000,
End: 1000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "start time equal to end time",
req: &HostsListRequest{
Start: 1000,
End: 1000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "limit zero",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 0,
Offset: 0,
},
wantErr: true,
},
{
name: "limit negative",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: -10,
Offset: 0,
},
wantErr: true,
},
{
name: "limit exceeds max",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 5001,
Offset: 0,
},
wantErr: true,
},
{
name: "offset negative",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 100,
Offset: -5,
},
wantErr: true,
},
{
name: "filter by status ACTIVE",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
FilterByStatus: HostStatusActive,
},
wantErr: false,
},
{
name: "filter by status INACTIVE",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
FilterByStatus: HostStatusInactive,
},
wantErr: false,
},
{
name: "filter by status empty (zero value)",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: false,
},
{
name: "filter by status invalid value",
req: &HostsListRequest{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
FilterByStatus: HostStatus{valuer.NewString("UNKNOWN")},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.req.Validate()
if tt.wantErr {
require.Error(t, err)
require.True(t, errors.Ast(err, errors.TypeInvalidInput), "expected error to be of type InvalidInput")
} else {
require.NoError(t, err)
}
})
}
}