Compare commits

..

1 Commits

Author SHA1 Message Date
vikrantgupta25
501ad64b9e test(wal): log the leaky queries 2026-04-10 00:00:20 +05:30
48 changed files with 362 additions and 3339 deletions

View File

@@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/cmd" "github.com/SigNoz/signoz/cmd"
"github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn" "github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz" "github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
@@ -18,15 +17,11 @@ import (
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway" "github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/gateway/noopgateway" "github.com/SigNoz/signoz/pkg/gateway/noopgateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing" "github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/querier" "github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
@@ -34,7 +29,6 @@ import (
"github.com/SigNoz/signoz/pkg/sqlschema" "github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus" "github.com/SigNoz/signoz/pkg/zeus"
"github.com/SigNoz/signoz/pkg/zeus/noopzeus" "github.com/SigNoz/signoz/pkg/zeus/noopzeus"
@@ -99,15 +93,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] { func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return noopgateway.NewProviderFactory() return noopgateway.NewProviderFactory()
}, },
func(_ licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]] {
return signoz.NewAuditorProviderFactories()
},
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler { func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
return querier.NewHandler(ps, q, a) return querier.NewHandler(ps, q, a)
}, },
func(_ cloudintegrationtypes.Store, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
return implcloudintegration.NewModule(), nil
},
) )
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err)) logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))

View File

@@ -8,7 +8,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/SigNoz/signoz/cmd" "github.com/SigNoz/signoz/cmd"
"github.com/SigNoz/signoz/ee/auditor/otlphttpauditor"
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn" "github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn" "github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
"github.com/SigNoz/signoz/ee/authz/openfgaauthz" "github.com/SigNoz/signoz/ee/authz/openfgaauthz"
@@ -17,8 +16,6 @@ import (
"github.com/SigNoz/signoz/ee/gateway/httpgateway" "github.com/SigNoz/signoz/ee/gateway/httpgateway"
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing" enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing" "github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration/implcloudprovider"
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
eequerier "github.com/SigNoz/signoz/ee/querier" eequerier "github.com/SigNoz/signoz/ee/querier"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app" enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
@@ -27,20 +24,15 @@ import (
enterprisezeus "github.com/SigNoz/signoz/ee/zeus" enterprisezeus "github.com/SigNoz/signoz/ee/zeus"
"github.com/SigNoz/signoz/ee/zeus/httpzeus" "github.com/SigNoz/signoz/ee/zeus/httpzeus"
"github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn" "github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway" "github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/querier" "github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
@@ -48,7 +40,6 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook" "github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus" "github.com/SigNoz/signoz/pkg/zeus"
) )
@@ -134,6 +125,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return nil, err return nil, err
} }
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, dashboardModule), nil return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, dashboardModule), nil
}, },
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module { func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing) return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
@@ -141,32 +133,12 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] { func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return httpgateway.NewProviderFactory(licensing) return httpgateway.NewProviderFactory(licensing)
}, },
func(licensing licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]] {
factories := signoz.NewAuditorProviderFactories()
if err := factories.Add(otlphttpauditor.NewFactory(licensing, version.Info)); err != nil {
panic(err)
}
return factories
},
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler { func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
communityHandler := querier.NewHandler(ps, q, a) communityHandler := querier.NewHandler(ps, q, a)
return eequerier.NewHandler(ps, q, communityHandler) return eequerier.NewHandler(ps, q, communityHandler)
}, },
func(store cloudintegrationtypes.Store, global global.Global, zeus zeus.Zeus, gateway gateway.Gateway, licensing licensing.Licensing, serviceAccount serviceaccount.Module, config cloudintegration.Config) (cloudintegration.Module, error) {
defStore := pkgcloudintegration.NewServiceDefinitionStore()
awsCloudProviderModule, err := implcloudprovider.NewAWSCloudProvider(defStore)
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
}
return implcloudintegration.NewModule(store, global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
},
) )
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err)) logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))
return err return err

View File

@@ -364,41 +364,3 @@ serviceaccount:
analytics: analytics:
# toggle service account analytics # toggle service account analytics
enabled: true enabled: true
##################### Auditor #####################
auditor:
# Specifies the auditor provider to use.
# noop: discards all audit events (community default).
# otlphttp: exports audit events via OTLP HTTP (enterprise).
provider: noop
# The async channel capacity for audit events. Events are dropped when full (fail-open).
buffer_size: 1000
# The maximum number of events per export batch.
batch_size: 100
# The maximum time between export flushes.
flush_interval: 1s
otlphttp:
# The target scheme://host:port/path of the OTLP HTTP endpoint.
endpoint: http://localhost:4318/v1/logs
# Whether to use HTTP instead of HTTPS.
insecure: false
# The maximum duration for an export attempt.
timeout: 10s
# Additional HTTP headers sent with every export request.
headers: {}
retry:
# Whether to retry on transient failures.
enabled: true
# The initial wait time before the first retry.
initial_interval: 5s
# The upper bound on backoff interval.
max_interval: 30s
# The total maximum time spent retrying.
max_elapsed_time: 60s
##################### Cloud Integration #####################
cloudintegration:
# cloud integration agent configuration
agent:
# The version of the cloud integration agent.
version: v0.0.8

View File

@@ -3309,7 +3309,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/CloudintegrationtypesPostableAccount' $ref: '#/components/schemas/CloudintegrationtypesPostableAccount'
responses: responses:
"201": "200":
content: content:
application/json: application/json:
schema: schema:
@@ -3322,7 +3322,7 @@ paths:
- status - status
- data - data
type: object type: object
description: Created description: OK
"401": "401":
content: content:
application/json: application/json:
@@ -3683,11 +3683,6 @@ paths:
provider provider
operationId: ListServicesMetadata operationId: ListServicesMetadata
parameters: parameters:
- in: query
name: cloud_integration_id
required: false
schema:
type: string
- in: path - in: path
name: cloud_provider name: cloud_provider
required: true required: true
@@ -3740,11 +3735,6 @@ paths:
description: This endpoint gets a service for the specified cloud provider description: This endpoint gets a service for the specified cloud provider
operationId: GetService operationId: GetService
parameters: parameters:
- in: query
name: cloud_integration_id
required: false
schema:
type: string
- in: path - in: path
name: cloud_provider name: cloud_provider
required: true required: true

View File

@@ -1,132 +0,0 @@
package implcloudprovider
import (
"context"
"fmt"
"net/url"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type awscloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) (cloudintegration.CloudProviderModule, error) {
return &awscloudprovider{serviceDefinitions: defStore}, nil
}
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.Aws.DeploymentRegion)
u, _ := url.Parse(baseURL)
q := u.Query()
q.Set("region", req.Config.Aws.DeploymentRegion)
u.Fragment = "/stacks/quickcreate"
u.RawQuery = q.Encode()
q = u.Query()
q.Set("stackName", cloudintegrationtypes.AgentCloudFormationBaseStackName.StringValue())
q.Set("templateURL", fmt.Sprintf(cloudintegrationtypes.AgentCloudFormationTemplateS3Path.StringValue(), req.Config.AgentVersion))
q.Set("param_SigNozIntegrationAgentVersion", req.Config.AgentVersion)
q.Set("param_SigNozApiUrl", req.Credentials.SigNozAPIURL)
q.Set("param_SigNozApiKey", req.Credentials.SigNozAPIKey)
q.Set("param_SigNozAccountId", account.ID.StringValue())
q.Set("param_IngestionUrl", req.Credentials.IngestionURL)
q.Set("param_IngestionKey", req.Credentials.IngestionKey)
return &cloudintegrationtypes.ConnectionArtifact{
Aws: &cloudintegrationtypes.AWSConnectionArtifact{
ConnectionURL: u.String() + "?&" + q.Encode(), // this format is required by AWS
},
}, nil
}
func (provider *awscloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAWS)
}
func (provider *awscloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAWS, serviceID)
if err != nil {
return nil, err
}
// override cloud integration dashboard id
for index, dashboard := range serviceDef.Assets.Dashboards {
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAWS, serviceID.StringValue(), dashboard.ID)
}
return serviceDef, nil
}
func (provider *awscloudprovider) BuildIntegrationConfig(
ctx context.Context,
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
// Sort services for deterministic output
sort.Slice(services, func(i, j int) bool {
return services[i].Type.StringValue() < services[j].Type.StringValue()
})
compiledMetrics := new(cloudintegrationtypes.AWSMetricsCollectionStrategy)
compiledLogs := new(cloudintegrationtypes.AWSLogsCollectionStrategy)
var compiledS3Buckets map[string][]string
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAWS, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
strategy := svcDef.TelemetryCollectionStrategy.AWS
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAWS)
// S3Sync: logs come directly from configured S3 buckets, not CloudWatch subscriptions
if storedSvc.Type == cloudintegrationtypes.AWSServiceS3Sync {
if logsEnabled && svcCfg.AWS.Logs.S3Buckets != nil {
compiledS3Buckets = svcCfg.AWS.Logs.S3Buckets
}
// no need to go ahead as the code block specifically checks for the S3Sync service
continue
}
if logsEnabled && strategy.Logs != nil {
compiledLogs.Subscriptions = append(compiledLogs.Subscriptions, strategy.Logs.Subscriptions...)
}
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAWS)
if metricsEnabled && strategy.Metrics != nil {
compiledMetrics.StreamFilters = append(compiledMetrics.StreamFilters, strategy.Metrics.StreamFilters...)
}
}
collectionStrategy := new(cloudintegrationtypes.AWSTelemetryCollectionStrategy)
if len(compiledMetrics.StreamFilters) > 0 {
collectionStrategy.Metrics = compiledMetrics
}
if len(compiledLogs.Subscriptions) > 0 {
collectionStrategy.Logs = compiledLogs
}
if compiledS3Buckets != nil {
collectionStrategy.S3Buckets = compiledS3Buckets
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
AWS: &cloudintegrationtypes.AWSIntegrationConfig{
EnabledRegions: account.Config.AWS.Regions,
TelemetryCollectionStrategy: collectionStrategy,
},
}, nil
}

View File

@@ -1,34 +0,0 @@
package implcloudprovider
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct{}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
panic("implement me")
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
ctx context.Context,
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
panic("implement me")
}

View File

@@ -1,521 +0,0 @@
package implcloudintegration
import (
"context"
"fmt"
"sort"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
)
type module struct {
store cloudintegrationtypes.Store
gateway gateway.Gateway
zeus zeus.Zeus
licensing licensing.Licensing
global global.Global
serviceAccount serviceaccount.Module
cloudProvidersMap map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule
config cloudintegration.Config
}
func NewModule(
store cloudintegrationtypes.Store,
global global.Global,
zeus zeus.Zeus,
gateway gateway.Gateway,
licensing licensing.Licensing,
serviceAccount serviceaccount.Module,
cloudProvidersMap map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule,
config cloudintegration.Config,
) (cloudintegration.Module, error) {
return &module{
store: store,
global: global,
zeus: zeus,
gateway: gateway,
licensing: licensing,
serviceAccount: serviceAccount,
cloudProvidersMap: cloudProvidersMap,
config: config,
}, nil
}
// GetConnectionCredentials returns credentials required to generate connection artifact. eg. apiKey, ingestionKey etc.
// It will return creds it can deduce and return empty value for others.
func (module *module) GetConnectionCredentials(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Credentials, error) {
// get license to get the deployment details
license, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
// get deployment details from zeus, ignore error
respBytes, _ := module.zeus.GetDeployment(ctx, license.Key)
var signozAPIURL string
if len(respBytes) > 0 {
// parse deployment details, ignore error, if client is asking api url every time check for possible error
deployment, _ := zeustypes.NewGettableDeployment(respBytes)
if deployment != nil {
signozAPIURL, _ = cloudintegrationtypes.GetSigNozAPIURLFromDeployment(deployment)
}
}
// ignore error
apiKey, _ := module.getOrCreateAPIKey(ctx, orgID, provider)
// ignore error
ingestionKey, _ := module.getOrCreateIngestionKey(ctx, orgID, provider)
return &cloudintegrationtypes.Credentials{
SigNozAPIURL: signozAPIURL,
SigNozAPIKey: apiKey,
IngestionURL: module.global.GetConfig(ctx).IngestionURL,
IngestionKey: ingestionKey,
}, nil
}
func (module *module) CreateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
_, err := module.licensing.GetActive(ctx, account.OrgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableCloudIntegration, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return err
}
return module.store.CreateAccount(ctx, storableCloudIntegration)
}
func (module *module) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
cloudProviderModule, err := module.getCloudProvider(account.Provider)
if err != nil {
return nil, err
}
req.Config.SetAgentVersion(module.config.Agent.Version)
return cloudProviderModule.GetConnectionArtifact(ctx, account, req)
}
func (module *module) GetAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Account, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableAccount, err := module.store.GetAccountByID(ctx, orgID, accountID, provider)
if err != nil {
return nil, err
}
return cloudintegrationtypes.NewAccountFromStorable(storableAccount)
}
// ListAccounts return only agent connected accounts.
func (module *module) ListAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) ([]*cloudintegrationtypes.Account, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
if err != nil {
return nil, err
}
return cloudintegrationtypes.NewAccountsFromStorables(storableAccounts)
}
func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, req *cloudintegrationtypes.AgentCheckInRequest) (*cloudintegrationtypes.AgentCheckInResponse, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
connectedAccount, err := module.store.GetConnectedAccount(ctx, orgID, provider, req.ProviderAccountID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
// If a different integration is already connected to this provider account ID, reject the check-in.
// Allow re-check-in from the same integration (e.g. agent restarting).
if connectedAccount != nil && connectedAccount.ID != req.CloudIntegrationID {
errMessage := fmt.Sprintf("provider account id %s is already connected to cloud integration id %s", req.ProviderAccountID, connectedAccount.ID)
return nil, errors.New(errors.TypeAlreadyExists, cloudintegrationtypes.ErrCodeCloudIntegrationAlreadyConnected, errMessage)
}
account, err := module.store.GetAccountByID(ctx, orgID, req.CloudIntegrationID, provider)
if err != nil {
return nil, err
}
// update account with cloud provider account id and agent report (heartbeat)
account.Update(&req.ProviderAccountID, cloudintegrationtypes.NewAgentReport(req.Data))
err = module.store.UpdateAccount(ctx, account)
if err != nil {
return nil, err
}
// If account has been removed (disconnected), return a minimal response with empty integration config.
// The agent doesn't act on config for removed accounts.
if account.RemovedAt != nil {
return &cloudintegrationtypes.AgentCheckInResponse{
CloudIntegrationID: account.ID.StringValue(),
ProviderAccountID: req.ProviderAccountID,
IntegrationConfig: new(cloudintegrationtypes.ProviderIntegrationConfig),
RemovedAt: account.RemovedAt,
}, nil
}
// Get account as domain object for config access (enabled regions, etc.)
domainAccount, err := cloudintegrationtypes.NewAccountFromStorable(account)
if err != nil {
return nil, err
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
storedServices, err := module.store.ListServices(ctx, req.CloudIntegrationID)
if err != nil {
return nil, err
}
// Delegate integration config building entirely to the provider module
integrationConfig, err := cloudProvider.BuildIntegrationConfig(ctx, domainAccount, storedServices)
if err != nil {
return nil, err
}
return &cloudintegrationtypes.AgentCheckInResponse{
CloudIntegrationID: account.ID.StringValue(),
ProviderAccountID: req.ProviderAccountID,
IntegrationConfig: integrationConfig,
RemovedAt: account.RemovedAt,
}, nil
}
func (module *module) UpdateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
_, err := module.licensing.GetActive(ctx, account.OrgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableAccount, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return err
}
return module.store.UpdateAccount(ctx, storableAccount)
}
func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) error {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return module.store.RemoveAccount(ctx, orgID, accountID, provider)
}
func (module *module) ListServicesMetadata(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, integrationID valuer.UUID) ([]*cloudintegrationtypes.ServiceMetadata, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
serviceDefinitions, err := cloudProvider.ListServiceDefinitions(ctx)
if err != nil {
return nil, err
}
enabledServiceIDs := map[string]bool{}
if !integrationID.IsZero() {
storedServices, err := module.store.ListServices(ctx, integrationID)
if err != nil {
return nil, err
}
for _, svc := range storedServices {
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, svc.Config)
if err != nil {
return nil, err
}
if serviceConfig.IsServiceEnabled(provider) {
enabledServiceIDs[svc.Type.StringValue()] = true
}
}
}
resp := make([]*cloudintegrationtypes.ServiceMetadata, 0, len(serviceDefinitions))
for _, serviceDefinition := range serviceDefinitions {
resp = append(resp, cloudintegrationtypes.NewServiceMetadata(*serviceDefinition, enabledServiceIDs[serviceDefinition.ID]))
}
return resp, nil
}
func (module *module) GetService(ctx context.Context, orgID valuer.UUID, serviceID cloudintegrationtypes.ServiceID, provider cloudintegrationtypes.CloudProviderType, cloudIntegrationID valuer.UUID) (*cloudintegrationtypes.Service, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, serviceID)
if err != nil {
return nil, err
}
var integrationService *cloudintegrationtypes.CloudIntegrationService
if !cloudIntegrationID.IsZero() {
storedService, err := module.store.GetServiceByServiceID(ctx, cloudIntegrationID, serviceID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
if storedService != nil {
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedService.Config)
if err != nil {
return nil, err
}
integrationService = cloudintegrationtypes.NewCloudIntegrationServiceFromStorable(storedService, serviceConfig)
}
}
return cloudintegrationtypes.NewService(*serviceDefinition, integrationService), nil
}
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return err
}
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, service.Type)
if err != nil {
return err
}
configJSON, err := service.Config.ToJSON(provider, service.Type, &serviceDefinition.SupportedSignals)
if err != nil {
return err
}
return module.store.CreateService(ctx, cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON)))
}
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, integrationService *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return err
}
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, integrationService.Type)
if err != nil {
return err
}
configJSON, err := integrationService.Config.ToJSON(provider, integrationService.Type, &serviceDefinition.SupportedSignals)
if err != nil {
return err
}
storableService := cloudintegrationtypes.NewStorableCloudIntegrationService(integrationService, string(configJSON))
return module.store.UpdateService(ctx, storableService)
}
func (module *module) GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
_, _, _, err = cloudintegrationtypes.ParseCloudIntegrationDashboardID(id)
if err != nil {
return nil, err
}
allDashboards, err := module.listDashboards(ctx, orgID)
if err != nil {
return nil, err
}
for _, d := range allDashboards {
if d.ID == id {
return d, nil
}
}
return nil, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration dashboard not found")
}
func (module *module) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
_, err := module.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return module.listDashboards(ctx, orgID)
}
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
stats := make(map[string]any)
// get connected accounts for AWS
awsAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAWS)
if err == nil {
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
}
// NOTE: not adding stats for services for now.
// TODO: add more cloud providers when supported
return stats, nil
}
func (module *module) getCloudProvider(provider cloudintegrationtypes.CloudProviderType) (cloudintegration.CloudProviderModule, error) {
if cloudProviderModule, ok := module.cloudProvidersMap[provider]; ok {
return cloudProviderModule, nil
}
return nil, errors.NewInvalidInputf(cloudintegrationtypes.ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
func (module *module) getOrCreateIngestionKey(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (string, error) {
keyName := cloudintegrationtypes.NewIngestionKeyName(provider)
result, err := module.gateway.SearchIngestionKeysByName(ctx, orgID, keyName, 1, 10)
if err != nil {
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't search ingestion keys")
}
// ideally there should be only one key per cloud integration provider
if len(result.Keys) > 0 {
return result.Keys[0].Value, nil
}
createdIngestionKey, err := module.gateway.CreateIngestionKey(ctx, orgID, keyName, []string{"integration"}, time.Time{})
if err != nil {
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't create ingestion key")
}
return createdIngestionKey.Value, nil
}
func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (string, error) {
domain := module.serviceAccount.Config().Email.Domain
serviceAccount := serviceaccounttypes.NewServiceAccount("integration", domain, serviceaccounttypes.ServiceAccountStatusActive, orgID)
serviceAccount, err := module.serviceAccount.GetOrCreate(ctx, orgID, serviceAccount)
if err != nil {
return "", err
}
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
if err != nil {
return "", err
}
factorAPIKey, err := serviceAccount.NewFactorAPIKey(provider.StringValue(), 0)
if err != nil {
return "", err
}
factorAPIKey, err = module.serviceAccount.GetOrCreateFactorAPIKey(ctx, factorAPIKey)
if err != nil {
return "", err
}
return factorAPIKey.Key, nil
}
func (module *module) listDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
var allDashboards []*dashboardtypes.Dashboard
for provider := range module.cloudProvidersMap {
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
connectedAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
if err != nil {
return nil, err
}
for _, storableAccount := range connectedAccounts {
storedServices, err := module.store.ListServices(ctx, storableAccount.ID)
if err != nil {
return nil, err
}
for _, storedSvc := range storedServices {
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedSvc.Config)
if err != nil || !serviceConfig.IsMetricsEnabled(provider) {
continue
}
svcDef, err := cloudProvider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil || svcDef == nil {
continue
}
dashboards := cloudintegrationtypes.GetDashboardsFromAssets(
storedSvc.Type.StringValue(),
orgID,
provider,
storableAccount.CreatedAt,
svcDef.Assets,
)
allDashboards = append(allDashboards, dashboards...)
}
}
}
sort.Slice(allDashboards, func(i, j int) bool {
return allDashboards[i].ID < allDashboards[j].ID
})
return allDashboards, nil
}

View File

@@ -227,7 +227,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
s.config.APIServer.Timeout.Default, s.config.APIServer.Timeout.Default,
s.config.APIServer.Timeout.Max, s.config.APIServer.Timeout.Max,
).Wrap) ).Wrap)
r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, s.signoz.Auditor).Wrap) r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, nil).Wrap)
r.Use(middleware.NewComment().Wrap) r.Use(middleware.NewComment().Wrap)
apiHandler.RegisterRoutes(r, am) apiHandler.RegisterRoutes(r, am)

View File

@@ -28,7 +28,7 @@ import type {
CloudintegrationtypesPostableAgentCheckInDTO, CloudintegrationtypesPostableAgentCheckInDTO,
CloudintegrationtypesUpdatableAccountDTO, CloudintegrationtypesUpdatableAccountDTO,
CloudintegrationtypesUpdatableServiceDTO, CloudintegrationtypesUpdatableServiceDTO,
CreateAccount201, CreateAccount200,
CreateAccountPathParameters, CreateAccountPathParameters,
DisconnectAccountPathParameters, DisconnectAccountPathParameters,
GetAccount200, GetAccount200,
@@ -36,12 +36,10 @@ import type {
GetConnectionCredentials200, GetConnectionCredentials200,
GetConnectionCredentialsPathParameters, GetConnectionCredentialsPathParameters,
GetService200, GetService200,
GetServiceParams,
GetServicePathParameters, GetServicePathParameters,
ListAccounts200, ListAccounts200,
ListAccountsPathParameters, ListAccountsPathParameters,
ListServicesMetadata200, ListServicesMetadata200,
ListServicesMetadataParams,
ListServicesMetadataPathParameters, ListServicesMetadataPathParameters,
RenderErrorResponseDTO, RenderErrorResponseDTO,
UpdateAccountPathParameters, UpdateAccountPathParameters,
@@ -262,7 +260,7 @@ export const createAccount = (
cloudintegrationtypesPostableAccountDTO: BodyType<CloudintegrationtypesPostableAccountDTO>, cloudintegrationtypesPostableAccountDTO: BodyType<CloudintegrationtypesPostableAccountDTO>,
signal?: AbortSignal, signal?: AbortSignal,
) => { ) => {
return GeneratedAPIInstance<CreateAccount201>({ return GeneratedAPIInstance<CreateAccount200>({
url: `/api/v1/cloud_integrations/${cloudProvider}/accounts`, url: `/api/v1/cloud_integrations/${cloudProvider}/accounts`,
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -942,25 +940,19 @@ export const invalidateGetConnectionCredentials = async (
*/ */
export const listServicesMetadata = ( export const listServicesMetadata = (
{ cloudProvider }: ListServicesMetadataPathParameters, { cloudProvider }: ListServicesMetadataPathParameters,
params?: ListServicesMetadataParams,
signal?: AbortSignal, signal?: AbortSignal,
) => { ) => {
return GeneratedAPIInstance<ListServicesMetadata200>({ return GeneratedAPIInstance<ListServicesMetadata200>({
url: `/api/v1/cloud_integrations/${cloudProvider}/services`, url: `/api/v1/cloud_integrations/${cloudProvider}/services`,
method: 'GET', method: 'GET',
params,
signal, signal,
}); });
}; };
export const getListServicesMetadataQueryKey = ( export const getListServicesMetadataQueryKey = ({
{ cloudProvider }: ListServicesMetadataPathParameters, cloudProvider,
params?: ListServicesMetadataParams, }: ListServicesMetadataPathParameters) => {
) => { return [`/api/v1/cloud_integrations/${cloudProvider}/services`] as const;
return [
`/api/v1/cloud_integrations/${cloudProvider}/services`,
...(params ? [params] : []),
] as const;
}; };
export const getListServicesMetadataQueryOptions = < export const getListServicesMetadataQueryOptions = <
@@ -968,7 +960,6 @@ export const getListServicesMetadataQueryOptions = <
TError = ErrorType<RenderErrorResponseDTO> TError = ErrorType<RenderErrorResponseDTO>
>( >(
{ cloudProvider }: ListServicesMetadataPathParameters, { cloudProvider }: ListServicesMetadataPathParameters,
params?: ListServicesMetadataParams,
options?: { options?: {
query?: UseQueryOptions< query?: UseQueryOptions<
Awaited<ReturnType<typeof listServicesMetadata>>, Awaited<ReturnType<typeof listServicesMetadata>>,
@@ -980,12 +971,11 @@ export const getListServicesMetadataQueryOptions = <
const { query: queryOptions } = options ?? {}; const { query: queryOptions } = options ?? {};
const queryKey = const queryKey =
queryOptions?.queryKey ?? queryOptions?.queryKey ?? getListServicesMetadataQueryKey({ cloudProvider });
getListServicesMetadataQueryKey({ cloudProvider }, params);
const queryFn: QueryFunction< const queryFn: QueryFunction<
Awaited<ReturnType<typeof listServicesMetadata>> Awaited<ReturnType<typeof listServicesMetadata>>
> = ({ signal }) => listServicesMetadata({ cloudProvider }, params, signal); > = ({ signal }) => listServicesMetadata({ cloudProvider }, signal);
return { return {
queryKey, queryKey,
@@ -1013,7 +1003,6 @@ export function useListServicesMetadata<
TError = ErrorType<RenderErrorResponseDTO> TError = ErrorType<RenderErrorResponseDTO>
>( >(
{ cloudProvider }: ListServicesMetadataPathParameters, { cloudProvider }: ListServicesMetadataPathParameters,
params?: ListServicesMetadataParams,
options?: { options?: {
query?: UseQueryOptions< query?: UseQueryOptions<
Awaited<ReturnType<typeof listServicesMetadata>>, Awaited<ReturnType<typeof listServicesMetadata>>,
@@ -1024,7 +1013,6 @@ export function useListServicesMetadata<
): UseQueryResult<TData, TError> & { queryKey: QueryKey } { ): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListServicesMetadataQueryOptions( const queryOptions = getListServicesMetadataQueryOptions(
{ cloudProvider }, { cloudProvider },
params,
options, options,
); );
@@ -1043,11 +1031,10 @@ export function useListServicesMetadata<
export const invalidateListServicesMetadata = async ( export const invalidateListServicesMetadata = async (
queryClient: QueryClient, queryClient: QueryClient,
{ cloudProvider }: ListServicesMetadataPathParameters, { cloudProvider }: ListServicesMetadataPathParameters,
params?: ListServicesMetadataParams,
options?: InvalidateOptions, options?: InvalidateOptions,
): Promise<QueryClient> => { ): Promise<QueryClient> => {
await queryClient.invalidateQueries( await queryClient.invalidateQueries(
{ queryKey: getListServicesMetadataQueryKey({ cloudProvider }, params) }, { queryKey: getListServicesMetadataQueryKey({ cloudProvider }) },
options, options,
); );
@@ -1060,24 +1047,21 @@ export const invalidateListServicesMetadata = async (
*/ */
export const getService = ( export const getService = (
{ cloudProvider, serviceId }: GetServicePathParameters, { cloudProvider, serviceId }: GetServicePathParameters,
params?: GetServiceParams,
signal?: AbortSignal, signal?: AbortSignal,
) => { ) => {
return GeneratedAPIInstance<GetService200>({ return GeneratedAPIInstance<GetService200>({
url: `/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`, url: `/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`,
method: 'GET', method: 'GET',
params,
signal, signal,
}); });
}; };
export const getGetServiceQueryKey = ( export const getGetServiceQueryKey = ({
{ cloudProvider, serviceId }: GetServicePathParameters, cloudProvider,
params?: GetServiceParams, serviceId,
) => { }: GetServicePathParameters) => {
return [ return [
`/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`, `/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`,
...(params ? [params] : []),
] as const; ] as const;
}; };
@@ -1086,7 +1070,6 @@ export const getGetServiceQueryOptions = <
TError = ErrorType<RenderErrorResponseDTO> TError = ErrorType<RenderErrorResponseDTO>
>( >(
{ cloudProvider, serviceId }: GetServicePathParameters, { cloudProvider, serviceId }: GetServicePathParameters,
params?: GetServiceParams,
options?: { options?: {
query?: UseQueryOptions< query?: UseQueryOptions<
Awaited<ReturnType<typeof getService>>, Awaited<ReturnType<typeof getService>>,
@@ -1098,12 +1081,11 @@ export const getGetServiceQueryOptions = <
const { query: queryOptions } = options ?? {}; const { query: queryOptions } = options ?? {};
const queryKey = const queryKey =
queryOptions?.queryKey ?? queryOptions?.queryKey ?? getGetServiceQueryKey({ cloudProvider, serviceId });
getGetServiceQueryKey({ cloudProvider, serviceId }, params);
const queryFn: QueryFunction<Awaited<ReturnType<typeof getService>>> = ({ const queryFn: QueryFunction<Awaited<ReturnType<typeof getService>>> = ({
signal, signal,
}) => getService({ cloudProvider, serviceId }, params, signal); }) => getService({ cloudProvider, serviceId }, signal);
return { return {
queryKey, queryKey,
@@ -1129,7 +1111,6 @@ export function useGetService<
TError = ErrorType<RenderErrorResponseDTO> TError = ErrorType<RenderErrorResponseDTO>
>( >(
{ cloudProvider, serviceId }: GetServicePathParameters, { cloudProvider, serviceId }: GetServicePathParameters,
params?: GetServiceParams,
options?: { options?: {
query?: UseQueryOptions< query?: UseQueryOptions<
Awaited<ReturnType<typeof getService>>, Awaited<ReturnType<typeof getService>>,
@@ -1140,7 +1121,6 @@ export function useGetService<
): UseQueryResult<TData, TError> & { queryKey: QueryKey } { ): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetServiceQueryOptions( const queryOptions = getGetServiceQueryOptions(
{ cloudProvider, serviceId }, { cloudProvider, serviceId },
params,
options, options,
); );
@@ -1159,11 +1139,10 @@ export function useGetService<
export const invalidateGetService = async ( export const invalidateGetService = async (
queryClient: QueryClient, queryClient: QueryClient,
{ cloudProvider, serviceId }: GetServicePathParameters, { cloudProvider, serviceId }: GetServicePathParameters,
params?: GetServiceParams,
options?: InvalidateOptions, options?: InvalidateOptions,
): Promise<QueryClient> => { ): Promise<QueryClient> => {
await queryClient.invalidateQueries( await queryClient.invalidateQueries(
{ queryKey: getGetServiceQueryKey({ cloudProvider, serviceId }, params) }, { queryKey: getGetServiceQueryKey({ cloudProvider, serviceId }) },
options, options,
); );

View File

@@ -3589,7 +3589,7 @@ export type ListAccounts200 = {
export type CreateAccountPathParameters = { export type CreateAccountPathParameters = {
cloudProvider: string; cloudProvider: string;
}; };
export type CreateAccount201 = { export type CreateAccount200 = {
data: CloudintegrationtypesGettableAccountWithConnectionArtifactDTO; data: CloudintegrationtypesGettableAccountWithConnectionArtifactDTO;
/** /**
* @type string * @type string
@@ -3647,14 +3647,6 @@ export type GetConnectionCredentials200 = {
export type ListServicesMetadataPathParameters = { export type ListServicesMetadataPathParameters = {
cloudProvider: string; cloudProvider: string;
}; };
export type ListServicesMetadataParams = {
/**
* @type string
* @description undefined
*/
cloud_integration_id?: string;
};
export type ListServicesMetadata200 = { export type ListServicesMetadata200 = {
data: CloudintegrationtypesGettableServicesMetadataDTO; data: CloudintegrationtypesGettableServicesMetadataDTO;
/** /**
@@ -3667,14 +3659,6 @@ export type GetServicePathParameters = {
cloudProvider: string; cloudProvider: string;
serviceId: string; serviceId: string;
}; };
export type GetServiceParams = {
/**
* @type string
* @description undefined
*/
cloud_integration_id?: string;
};
export type GetService200 = { export type GetService200 = {
data: CloudintegrationtypesServiceDTO; data: CloudintegrationtypesServiceDTO;
/** /**

View File

@@ -677,18 +677,6 @@ function NewWidget({
queryType: currentQuery.queryType, queryType: currentQuery.queryType,
isNewPanel, isNewPanel,
dataSource: currentQuery?.builder?.queryData?.[0]?.dataSource, dataSource: currentQuery?.builder?.queryData?.[0]?.dataSource,
...(currentQuery.queryType === EQueryType.CLICKHOUSE && {
clickhouseQueryCount: currentQuery.clickhouse_sql.length,
clickhouseQueries: currentQuery.clickhouse_sql.map((q) => ({
name: q.name,
query: (q.query ?? '')
.replace(/--[^\n]*/g, '') // strip line comments
.replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
.replace(/'(?:[^'\\]|\\.|'')*'/g, "'?'") // replace single-quoted strings (handles \' and '' escapes)
.replace(/\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/g, '?'), // replace numeric literals (int, float, scientific)
disabled: q.disabled,
})),
}),
}); });
setSaveModal(true); setSaveModal(true);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -41,7 +41,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
RequestContentType: "application/json", RequestContentType: "application/json",
Response: new(citypes.GettableAccountWithConnectionArtifact), Response: new(citypes.GettableAccountWithConnectionArtifact),
ResponseContentType: "application/json", ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated, SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{}, ErrorStatusCodes: []int{},
Deprecated: false, Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin), SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
@@ -138,7 +138,6 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Summary: "List services metadata", Summary: "List services metadata",
Description: "This endpoint lists the services metadata for the specified cloud provider", Description: "This endpoint lists the services metadata for the specified cloud provider",
Request: nil, Request: nil,
RequestQuery: new(citypes.ListServicesMetadataParams),
RequestContentType: "", RequestContentType: "",
Response: new(citypes.GettableServicesMetadata), Response: new(citypes.GettableServicesMetadata),
ResponseContentType: "application/json", ResponseContentType: "application/json",
@@ -159,7 +158,6 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
Summary: "Get service", Summary: "Get service",
Description: "This endpoint gets a service for the specified cloud provider", Description: "This endpoint gets a service for the specified cloud provider",
Request: nil, Request: nil,
RequestQuery: new(citypes.GetServiceParams),
RequestContentType: "", RequestContentType: "",
Response: new(citypes.Service), Response: new(citypes.Service),
ResponseContentType: "application/json", ResponseContentType: "application/json",

View File

@@ -63,7 +63,6 @@ type RetryConfig struct {
func newConfig() factory.Config { func newConfig() factory.Config {
return Config{ return Config{
Provider: "noop",
BufferSize: 1000, BufferSize: 1000,
BatchSize: 100, BatchSize: 100,
FlushInterval: time.Second, FlushInterval: time.Second,

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/SigNoz/signoz/pkg/statsreporter"
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes" citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes" "github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
@@ -33,11 +32,11 @@ type Module interface {
// ListServicesMetadata returns the list of supported services' metadata for a cloud provider with optional filtering for a specific integration // 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. // 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(ctx context.Context, orgID valuer.UUID, provider citypes.CloudProviderType, integrationID *valuer.UUID) ([]*citypes.ServiceMetadata, error)
// GetService returns service definition details for a serviceID. This optionally returns the service config // GetService returns service definition details for a serviceID. This optionally returns the service config
// for integrationID if provided. // for integrationID if provided.
GetService(ctx context.Context, orgID valuer.UUID, serviceID citypes.ServiceID, provider citypes.CloudProviderType, integrationID valuer.UUID) (*citypes.Service, error) 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 creates a new service for a cloud integration account.
CreateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error CreateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
@@ -56,8 +55,6 @@ type Module interface {
// ListDashboards returns list of dashboards across all connected cloud integration accounts // ListDashboards returns list of dashboards across all connected cloud integration accounts
// for enabled services in the org. This list gets added to dashboard list page // for enabled services in the org. This list gets added to dashboard list page
ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
statsreporter.StatsCollector
} }
type CloudProviderModule interface { type CloudProviderModule interface {

View File

@@ -1,32 +0,0 @@
package cloudintegration
import (
"github.com/SigNoz/signoz/pkg/factory"
)
type Config struct {
// Agent config for cloud integration
Agent AgentConfig `mapstructure:"agent"`
}
type AgentConfig struct {
Version string `mapstructure:"version"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("cloudintegration"), newConfig)
}
func newConfig() factory.Config {
return &Config{
Agent: AgentConfig{
// we will maintain the latest version of cloud integration agent from here,
// till we automate it externally or figure out a way to validate it.
Version: "v0.0.8",
},
}
}
func (c Config) Validate() error {
return nil
}

View File

@@ -1,176 +1,21 @@
package implcloudintegration package implcloudintegration
import ( import (
"bytes"
"context" "context"
"embed"
"encoding/base64"
"encoding/json"
"fmt"
"io/fs"
"path"
"sort"
"strings"
"github.com/SigNoz/signoz/pkg/errors"
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes" citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
) )
const definitionsRoot = "fs/definitions"
//go:embed fs/definitions/*
var definitionFiles embed.FS
type definitionStore struct{} type definitionStore struct{}
// NewServiceDefinitionStore creates a new ServiceDefinitionStore backed by the embedded filesystem. func NewDefinitionStore() citypes.ServiceDefinitionStore {
func NewServiceDefinitionStore() citypes.ServiceDefinitionStore {
return &definitionStore{} return &definitionStore{}
} }
// Get reads and hydrates the service definition for the given provider and service ID. func (d *definitionStore) Get(ctx context.Context, provider citypes.CloudProviderType, serviceID citypes.ServiceID) (*citypes.ServiceDefinition, error) {
func (s *definitionStore) Get(ctx context.Context, provider citypes.CloudProviderType, serviceID citypes.ServiceID) (*citypes.ServiceDefinition, error) { panic("unimplemented")
svcDir := path.Join(definitionsRoot, provider.StringValue(), serviceID.StringValue())
def, err := readServiceDefinition(svcDir)
if err != nil {
return nil, errors.New(errors.TypeNotFound, citypes.ErrCodeServiceDefinitionNotFound, fmt.Sprintf("service definition not found for service id %q", serviceID.StringValue()))
}
return def, nil
} }
// List reads and hydrates all service definitions for the given provider, sorted by ID. func (d *definitionStore) List(ctx context.Context, provider citypes.CloudProviderType) ([]*citypes.ServiceDefinition, error) {
func (s *definitionStore) List(ctx context.Context, provider citypes.CloudProviderType) ([]*citypes.ServiceDefinition, error) { panic("unimplemented")
providerDir := path.Join(definitionsRoot, provider.StringValue())
entries, err := fs.ReadDir(definitionFiles, providerDir)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read service definition dirs for %s", provider.StringValue())
}
var result []*citypes.ServiceDefinition
for _, entry := range entries {
if !entry.IsDir() {
continue
}
svcDir := path.Join(providerDir, entry.Name())
def, err := readServiceDefinition(svcDir)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read service definition for %s/%s", provider.StringValue(), entry.Name())
}
result = append(result, def)
}
sort.Slice(result, func(i, j int) bool {
return result[i].ID < result[j].ID
})
return result, nil
}
// following are helper functions for reading and hydrating service definitions,
// not keeping this in types as this is an implementation detail of the definition store.
func readServiceDefinition(svcDir string) (*citypes.ServiceDefinition, error) {
integrationJSONPath := path.Join(svcDir, "integration.json")
raw, err := definitionFiles.ReadFile(integrationJSONPath)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read %s", integrationJSONPath)
}
var specMap map[string]any
if err := json.Unmarshal(raw, &specMap); err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't parse %s", integrationJSONPath)
}
hydrated, err := hydrateFileURIs(specMap, definitionFiles, svcDir)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't hydrate file URIs in %s", integrationJSONPath)
}
reEncoded, err := json.Marshal(hydrated)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't re-encode hydrated spec from %s", integrationJSONPath)
}
var def citypes.ServiceDefinition
decoder := json.NewDecoder(bytes.NewReader(reEncoded))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&def); err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't decode service definition from %s", integrationJSONPath)
}
if err := validateServiceDefinition(&def); err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "invalid service definition in %s", svcDir)
}
return &def, nil
}
func validateServiceDefinition(def *citypes.ServiceDefinition) error {
if def.TelemetryCollectionStrategy == nil {
return errors.NewInternalf(errors.CodeInternal, "telemetryCollectionStrategy is required")
}
seenDashboardIDs := map[string]struct{}{}
for _, d := range def.Assets.Dashboards {
if _, seen := seenDashboardIDs[d.ID]; seen {
return errors.NewInternalf(errors.CodeInternal, "duplicate dashboard id %q", d.ID)
}
seenDashboardIDs[d.ID] = struct{}{}
}
return nil
}
// hydrateFileURIs walks a JSON-decoded value and replaces any "file://<path>" strings
// with the actual file contents (text for .md, base64 data URI for .svg, parsed JSON for .json).
func hydrateFileURIs(v any, embeddedFS embed.FS, basedir string) (any, error) {
switch val := v.(type) {
case map[string]any:
result := make(map[string]any, len(val))
for k, child := range val {
hydrated, err := hydrateFileURIs(child, embeddedFS, basedir)
if err != nil {
return nil, err
}
result[k] = hydrated
}
return result, nil
case []any:
result := make([]any, len(val))
for i, child := range val {
hydrated, err := hydrateFileURIs(child, embeddedFS, basedir)
if err != nil {
return nil, err
}
result[i] = hydrated
}
return result, nil
case string:
if !strings.HasPrefix(val, "file://") {
return val, nil
}
return readEmbeddedFile(embeddedFS, path.Join(basedir, val[len("file://"):]))
}
return v, nil
}
func readEmbeddedFile(embeddedFS embed.FS, filePath string) (any, error) {
contents, err := embeddedFS.ReadFile(filePath)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read embedded file %s", filePath)
}
switch {
case strings.HasSuffix(filePath, ".md"):
return string(contents), nil
case strings.HasSuffix(filePath, ".svg"):
return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(contents)), nil
case strings.HasSuffix(filePath, ".json"):
var parsed any
if err := json.Unmarshal(contents, &parsed); err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't parse JSON file %s", filePath)
}
return parsed, nil
default:
return nil, errors.NewInternalf(errors.CodeInternal, "unsupported file type for embedded reference: %s", filePath)
}
} }

View File

@@ -1,457 +1,62 @@
package implcloudintegration package implcloudintegration
import ( import (
"context"
"net/http" "net/http"
"time"
"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/cloudintegration" "github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
) )
type handler struct { type handler struct{}
module cloudintegration.Module
func NewHandler() cloudintegration.Handler {
return &handler{}
} }
func NewHandler(module cloudintegration.Module) cloudintegration.Handler { func (handler *handler) GetConnectionCredentials(http.ResponseWriter, *http.Request) {
return &handler{ panic("unimplemented")
module: module,
}
} }
func (handler *handler) GetConnectionCredentials(rw http.ResponseWriter, r *http.Request) { func (handler *handler) CreateAccount(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
creds, err := handler.module.GetConnectionCredentials(ctx, valuer.MustNewUUID(claims.OrgID), provider)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, creds)
} }
func (handler *handler) CreateAccount(rw http.ResponseWriter, r *http.Request) { func (handler *handler) ListAccounts(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
postableAccount := new(cloudintegrationtypes.PostableAccount)
err = binding.JSON.BindBody(r.Body, postableAccount)
if err != nil {
render.Error(rw, err)
return
}
accountConfig, err := cloudintegrationtypes.NewAccountConfigFromPostable(provider, postableAccount.Config)
if err != nil {
render.Error(rw, err)
return
}
account := cloudintegrationtypes.NewAccount(valuer.MustNewUUID(claims.OrgID), provider, accountConfig)
err = handler.module.CreateAccount(ctx, account)
if err != nil {
render.Error(rw, err)
return
}
connectionArtifact, err := handler.module.GetConnectionArtifact(ctx, account, postableAccount)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusCreated, cloudintegrationtypes.NewGettableAccountWithConnectionArtifact(account, connectionArtifact))
} }
func (handler *handler) GetAccount(rw http.ResponseWriter, r *http.Request) { func (handler *handler) GetAccount(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
accountID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(rw, err)
return
}
account, err := handler.module.GetAccount(ctx, valuer.MustNewUUID(claims.OrgID), accountID, provider)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, account)
} }
func (handler *handler) ListAccounts(rw http.ResponseWriter, r *http.Request) { func (handler *handler) UpdateAccount(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
accounts, err := handler.module.ListAccounts(ctx, valuer.MustNewUUID(claims.OrgID), provider)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableAccounts(accounts))
} }
func (handler *handler) UpdateAccount(rw http.ResponseWriter, r *http.Request) { func (handler *handler) DisconnectAccount(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
cloudIntegrationID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(rw, err)
return
}
req := new(cloudintegrationtypes.UpdatableAccount)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
account, err := handler.module.GetAccount(ctx, valuer.MustNewUUID(claims.OrgID), cloudIntegrationID, provider)
if err != nil {
render.Error(rw, err)
return
}
if account.IsRemoved() {
render.Error(rw, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration account is removed"))
return
}
accountConfig, err := cloudintegrationtypes.NewAccountConfigFromUpdatable(provider, req)
if err != nil {
render.Error(rw, err)
return
}
if err := account.Update(provider, accountConfig); err != nil {
render.Error(rw, err)
return
}
err = handler.module.UpdateAccount(ctx, account)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
} }
func (handler *handler) DisconnectAccount(rw http.ResponseWriter, r *http.Request) { func (handler *handler) ListServicesMetadata(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
cloudIntegrationID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(rw, err)
return
}
err = handler.module.DisconnectAccount(ctx, valuer.MustNewUUID(claims.OrgID), cloudIntegrationID, provider)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
} }
func (handler *handler) ListServicesMetadata(rw http.ResponseWriter, r *http.Request) { func (handler *handler) GetService(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
queryParams := new(cloudintegrationtypes.ListServicesMetadataParams)
if err := binding.Query.BindQuery(r.URL.Query(), queryParams); err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
// check if integration account exists and is not removed.
if !queryParams.CloudIntegrationID.IsZero() {
account, err := handler.module.GetAccount(ctx, orgID, queryParams.CloudIntegrationID, provider)
if err != nil {
render.Error(rw, err)
return
}
if account.IsRemoved() {
render.Error(rw, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration account is removed"))
return
}
}
services, err := handler.module.ListServicesMetadata(ctx, orgID, provider, queryParams.CloudIntegrationID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableServicesMetadata(services))
} }
func (handler *handler) GetService(rw http.ResponseWriter, r *http.Request) { func (handler *handler) UpdateService(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
serviceID, err := cloudintegrationtypes.NewServiceID(provider, mux.Vars(r)["service_id"])
if err != nil {
render.Error(rw, err)
return
}
queryParams := new(cloudintegrationtypes.GetServiceParams)
if err := binding.Query.BindQuery(r.URL.Query(), queryParams); err != nil {
render.Error(rw, err)
return
}
// check if integration account exists and is not removed.
if !queryParams.CloudIntegrationID.IsZero() {
account, err := handler.module.GetAccount(ctx, valuer.MustNewUUID(claims.OrgID), queryParams.CloudIntegrationID, provider)
if err != nil {
render.Error(rw, err)
return
}
if account.IsRemoved() {
render.Error(rw, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration account is removed"))
return
}
}
svc, err := handler.module.GetService(ctx, valuer.MustNewUUID(claims.OrgID), serviceID, provider, queryParams.CloudIntegrationID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, svc)
} }
func (handler *handler) UpdateService(rw http.ResponseWriter, r *http.Request) { func (handler *handler) AgentCheckIn(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) // TODO implement me
defer cancel() panic("implement me")
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
serviceID, err := cloudintegrationtypes.NewServiceID(provider, mux.Vars(r)["service_id"])
if err != nil {
render.Error(rw, err)
return
}
req := new(cloudintegrationtypes.UpdatableService)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
cloudIntegrationID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
// check if integration account exists and is not removed.
account, err := handler.module.GetAccount(ctx, orgID, cloudIntegrationID, provider)
if err != nil {
render.Error(rw, err)
return
}
if account.IsRemoved() {
render.Error(rw, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration account is removed"))
return
}
svc, err := handler.module.GetService(ctx, orgID, serviceID, provider, cloudIntegrationID)
if err != nil {
render.Error(rw, err)
return
}
// update or create service
if svc.CloudIntegrationService == nil {
cloudIntegrationService := cloudintegrationtypes.NewCloudIntegrationService(serviceID, cloudIntegrationID, req.Config)
err = handler.module.CreateService(ctx, orgID, cloudIntegrationService, provider)
} else {
err = svc.CloudIntegrationService.Update(provider, serviceID, req.Config)
if err != nil {
render.Error(rw, err)
return
}
err = handler.module.UpdateService(ctx, orgID, svc.CloudIntegrationService, provider)
}
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
func (handler *handler) AgentCheckIn(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
req := new(cloudintegrationtypes.PostableAgentCheckIn)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
// Map old fields → new fields for backward compatibility with old agents
// Old agents send account_id (=> cloudIntegrationId) and cloud_account_id (=> providerAccountId)
if req.ID != "" {
id, err := valuer.NewUUID(req.ID)
if err != nil {
render.Error(rw, err)
return
}
req.CloudIntegrationID = id
req.ProviderAccountID = req.AccountID
}
resp, err := handler.module.AgentCheckIn(ctx, valuer.MustNewUUID(claims.OrgID), provider, &req.AgentCheckInRequest)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableAgentCheckIn(provider, resp))
} }

View File

@@ -1,77 +0,0 @@
package implcloudintegration
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct{}
func NewModule() cloudintegration.Module {
return &module{}
}
func (module *module) GetConnectionCredentials(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Credentials, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "get connection credentials is not supported")
}
func (module *module) CreateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "create account is not supported")
}
func (module *module) GetAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Account, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "get account is not supported")
}
func (module *module) ListAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) ([]*cloudintegrationtypes.Account, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "list accounts is not supported")
}
func (module *module) UpdateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "update account is not supported")
}
func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) error {
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "disconnect account is not supported")
}
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "create service is not supported")
}
func (module *module) GetService(ctx context.Context, orgID valuer.UUID, serviceID cloudintegrationtypes.ServiceID, provider cloudintegrationtypes.CloudProviderType, integrationID valuer.UUID) (*cloudintegrationtypes.Service, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "get service is not supported")
}
func (module *module) ListServicesMetadata(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, integrationID valuer.UUID) ([]*cloudintegrationtypes.ServiceMetadata, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "list services metadata is not supported")
}
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "update service is not supported")
}
func (module *module) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "get connection artifact is not supported")
}
func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, req *cloudintegrationtypes.AgentCheckInRequest) (*cloudintegrationtypes.AgentCheckInResponse, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "agent check-in is not supported")
}
func (module *module) GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "get dashboard by ID is not supported")
}
func (module *module) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "list dashboards is not supported")
}
func (module *module) Collect(context.Context, valuer.UUID) (map[string]any, error) {
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "stats collection is not supported")
}

View File

@@ -73,26 +73,6 @@ func (store *store) ListConnectedAccounts(ctx context.Context, orgID valuer.UUID
return accounts, nil return accounts, nil
} }
func (store *store) CountConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (int, error) {
storable := new(cloudintegrationtypes.StorableCloudIntegration)
count, err := store.
store.
BunDBCtx(ctx).
NewSelect().
Model(storable).
Where("org_id = ?", orgID).
Where("provider = ?", provider).
Where("removed_at IS NULL").
Where("account_id IS NOT NULL").
Where("last_agent_report IS NOT NULL").
Count(ctx)
if err != nil {
return 0, err
}
return count, nil
}
func (store *store) CreateAccount(ctx context.Context, account *cloudintegrationtypes.StorableCloudIntegration) error { func (store *store) CreateAccount(ctx context.Context, account *cloudintegrationtypes.StorableCloudIntegration) error {
_, err := store. _, err := store.
store. store.

View File

@@ -63,7 +63,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/postprocess" "github.com/SigNoz/signoz/pkg/query-service/postprocess"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes" "github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes" "github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes" "github.com/SigNoz/signoz/pkg/types/featuretypes"
@@ -1138,19 +1137,12 @@ func (aH *APIHandler) Get(rw http.ResponseWriter, r *http.Request) {
} }
dashboard := new(dashboardtypes.Dashboard) dashboard := new(dashboardtypes.Dashboard)
if aH.CloudIntegrationsController.IsCloudIntegrationDashboardUuid(id) {
if _, _, _, err := cloudintegrationtypes.ParseCloudIntegrationDashboardID(id); err == nil { cloudIntegrationDashboard, apiErr := aH.CloudIntegrationsController.GetDashboardById(ctx, orgID, id)
cloudIntegrationDashboard, err := aH.Signoz.Modules.CloudIntegration.GetDashboardByID(ctx, orgID, id) if apiErr != nil {
if err != nil && !errorsV2.Ast(err, errorsV2.TypeLicenseUnavailable) { render.Error(rw, errorsV2.Wrapf(apiErr, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to get dashboard"))
render.Error(rw, errorsV2.Wrapf(err, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to get dashboard"))
return return
} }
if cloudIntegrationDashboard == nil {
render.Error(rw, errorsV2.Newf(errorsV2.TypeNotFound, errorsV2.CodeNotFound, "dashboard not found"))
return
}
dashboard = cloudIntegrationDashboard dashboard = cloudIntegrationDashboard
} else if aH.IntegrationsController.IsInstalledIntegrationDashboardID(id) { } else if aH.IntegrationsController.IsInstalledIntegrationDashboardID(id) {
integrationDashboard, apiErr := aH.IntegrationsController.GetInstalledIntegrationDashboardById(ctx, orgID, id) integrationDashboard, apiErr := aH.IntegrationsController.GetInstalledIntegrationDashboardById(ctx, orgID, id)
@@ -1215,11 +1207,9 @@ func (aH *APIHandler) List(rw http.ResponseWriter, r *http.Request) {
dashboards = append(dashboards, installedIntegrationDashboards...) dashboards = append(dashboards, installedIntegrationDashboards...)
} }
cloudIntegrationDashboards, err := aH.Signoz.Modules.CloudIntegration.ListDashboards(ctx, orgID) cloudIntegrationDashboards, apiErr := aH.CloudIntegrationsController.AvailableDashboards(ctx, orgID)
if err != nil { if apiErr != nil {
if !errors.Ast(err, errorsV2.TypeLicenseUnavailable) { aH.logger.ErrorContext(ctx, "failed to get dashboards for cloud integrations", errors.Attr(apiErr))
aH.logger.ErrorContext(ctx, "failed to get dashboards for cloud integrations", errors.Attr(err))
}
} else { } else {
dashboards = append(dashboards, cloudIntegrationDashboards...) dashboards = append(dashboards, cloudIntegrationDashboards...)
} }

View File

@@ -208,7 +208,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
s.config.APIServer.Timeout.Default, s.config.APIServer.Timeout.Default,
s.config.APIServer.Timeout.Max, s.config.APIServer.Timeout.Max,
).Wrap) ).Wrap)
r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, s.signoz.Auditor).Wrap) r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, nil).Wrap)
r.Use(middleware.NewComment().Wrap) r.Use(middleware.NewComment().Wrap)
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz) am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz)

View File

@@ -11,7 +11,6 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/apiserver" "github.com/SigNoz/signoz/pkg/apiserver"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/config" "github.com/SigNoz/signoz/pkg/config"
"github.com/SigNoz/signoz/pkg/emailing" "github.com/SigNoz/signoz/pkg/emailing"
@@ -22,7 +21,6 @@ import (
"github.com/SigNoz/signoz/pkg/global" "github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/identn" "github.com/SigNoz/signoz/pkg/identn"
"github.com/SigNoz/signoz/pkg/instrumentation" "github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer" "github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount" "github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/user" "github.com/SigNoz/signoz/pkg/modules/user"
@@ -125,12 +123,6 @@ type Config struct {
// ServiceAccount config // ServiceAccount config
ServiceAccount serviceaccount.Config `mapstructure:"serviceaccount"` ServiceAccount serviceaccount.Config `mapstructure:"serviceaccount"`
// Auditor config
Auditor auditor.Config `mapstructure:"auditor"`
// CloudIntegration config
CloudIntegration cloudintegration.Config `mapstructure:"cloudintegration"`
} }
func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.ResolverConfig) (Config, error) { func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.ResolverConfig) (Config, error) {
@@ -161,8 +153,6 @@ func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.R
user.NewConfigFactory(), user.NewConfigFactory(),
identn.NewConfigFactory(), identn.NewConfigFactory(),
serviceaccount.NewConfigFactory(), serviceaccount.NewConfigFactory(),
auditor.NewConfigFactory(),
cloudintegration.NewConfigFactory(),
} }
conf, err := config.New(ctx, resolverConfig, configFactories) conf, err := config.New(ctx, resolverConfig, configFactories)
@@ -305,6 +295,7 @@ func mergeAndEnsureBackwardCompatibility(ctx context.Context, logger *slog.Logge
} }
config.Flagger.Config.Boolean[flagger.FeatureKafkaSpanEval.String()] = os.Getenv("KAFKA_SPAN_EVAL") == "true" config.Flagger.Config.Boolean[flagger.FeatureKafkaSpanEval.String()] = os.Getenv("KAFKA_SPAN_EVAL") == "true"
} }
} }
func (config Config) Collect(_ context.Context, _ valuer.UUID) (map[string]any, error) { func (config Config) Collect(_ context.Context, _ valuer.UUID) (map[string]any, error) {

View File

@@ -97,7 +97,7 @@ func NewHandlers(
QuerierHandler: querierHandler, QuerierHandler: querierHandler,
ServiceAccountHandler: implserviceaccount.NewHandler(modules.ServiceAccount), ServiceAccountHandler: implserviceaccount.NewHandler(modules.ServiceAccount),
RegistryHandler: registryHandler, RegistryHandler: registryHandler,
CloudIntegrationHandler: implcloudintegration.NewHandler(),
RuleStateHistory: implrulestatehistory.NewHandler(modules.RuleStateHistory), RuleStateHistory: implrulestatehistory.NewHandler(modules.RuleStateHistory),
CloudIntegrationHandler: implcloudintegration.NewHandler(modules.CloudIntegration),
} }
} }

View File

@@ -52,7 +52,8 @@ func TestNewHandlers(t *testing.T) {
userRoleStore := impluser.NewUserRoleStore(sqlstore, providerSettings) userRoleStore := impluser.NewUserRoleStore(sqlstore, providerSettings)
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), userRoleStore, flagger) userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), userRoleStore, flagger)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, nil, nil)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore)
querierHandler := querier.NewHandler(providerSettings, nil, nil) querierHandler := querier.NewHandler(providerSettings, nil, nil)
registryHandler := factory.NewHandler(nil) registryHandler := factory.NewHandler(nil)

View File

@@ -12,7 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex" "github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
"github.com/SigNoz/signoz/pkg/modules/authdomain" "github.com/SigNoz/signoz/pkg/modules/authdomain"
"github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain" "github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer" "github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer" "github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
@@ -31,6 +30,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/savedview" "github.com/SigNoz/signoz/pkg/modules/savedview"
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview" "github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount" "github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/services" "github.com/SigNoz/signoz/pkg/modules/services"
"github.com/SigNoz/signoz/pkg/modules/services/implservices" "github.com/SigNoz/signoz/pkg/modules/services/implservices"
"github.com/SigNoz/signoz/pkg/modules/session" "github.com/SigNoz/signoz/pkg/modules/session"
@@ -71,7 +71,6 @@ type Modules struct {
MetricsExplorer metricsexplorer.Module MetricsExplorer metricsexplorer.Module
Promote promote.Module Promote promote.Module
ServiceAccount serviceaccount.Module ServiceAccount serviceaccount.Module
CloudIntegration cloudintegration.Module
RuleStateHistory rulestatehistory.Module RuleStateHistory rulestatehistory.Module
} }
@@ -94,8 +93,6 @@ func NewModules(
dashboard dashboard.Module, dashboard dashboard.Module,
userGetter user.Getter, userGetter user.Getter,
userRoleStore authtypes.UserRoleStore, userRoleStore authtypes.UserRoleStore,
serviceAccount serviceaccount.Module,
cloudIntegrationModule cloudintegration.Module,
) Modules { ) Modules {
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore)) quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter) orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
@@ -120,8 +117,7 @@ func NewModules(
Services: implservices.NewModule(querier, telemetryStore), Services: implservices.NewModule(querier, telemetryStore),
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer), MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore), Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore),
ServiceAccount: serviceAccount, ServiceAccount: implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), authz, cache, analytics, providerSettings, config.ServiceAccount),
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)), RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),
CloudIntegration: cloudIntegrationModule,
} }
} }

View File

@@ -13,11 +13,8 @@ import (
"github.com/SigNoz/signoz/pkg/factory/factorytest" "github.com/SigNoz/signoz/pkg/factory/factorytest"
"github.com/SigNoz/signoz/pkg/flagger" "github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/modules/user/impluser"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
@@ -54,9 +51,7 @@ func TestNewModules(t *testing.T) {
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), userRoleStore, flagger) userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), userRoleStore, flagger)
serviceAccount := implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), nil, nil, nil, providerSettings, serviceaccount.Config{}) modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, serviceAccount, implcloudintegration.NewModule())
reflectVal := reflect.ValueOf(modules) reflectVal := reflect.ValueOf(modules)
for i := 0; i < reflectVal.NumField(); i++ { for i := 0; i < reflectVal.NumField(); i++ {

View File

@@ -3,8 +3,6 @@ package signoz
import ( import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/nfmanager" "github.com/SigNoz/signoz/pkg/alertmanager/nfmanager"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/auditor/noopauditor"
"github.com/SigNoz/signoz/pkg/alertmanager/nfmanager/rulebasednotification" "github.com/SigNoz/signoz/pkg/alertmanager/nfmanager/rulebasednotification"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/analytics"
@@ -314,12 +312,6 @@ func NewGlobalProviderFactories(identNConfig identn.Config) factory.NamedMap[fac
) )
} }
func NewAuditorProviderFactories() factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]] {
return factory.MustNewNamedMap(
noopauditor.NewFactory(),
)
}
func NewFlaggerProviderFactories(registry featuretypes.Registry) factory.NamedMap[factory.ProviderFactory[flagger.FlaggerProvider, flagger.Config]] { func NewFlaggerProviderFactories(registry featuretypes.Registry) factory.NamedMap[factory.ProviderFactory[flagger.FlaggerProvider, flagger.Config]] {
return factory.MustNewNamedMap( return factory.MustNewNamedMap(
configflagger.NewFactory(registry), configflagger.NewFactory(registry),

View File

@@ -9,7 +9,6 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager/nfmanager/nfroutingstore/sqlroutingstore" "github.com/SigNoz/signoz/pkg/alertmanager/nfmanager/nfroutingstore/sqlroutingstore"
"github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/apiserver" "github.com/SigNoz/signoz/pkg/apiserver"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn" "github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/authn/authnstore/sqlauthnstore" "github.com/SigNoz/signoz/pkg/authn/authnstore/sqlauthnstore"
"github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/authz"
@@ -18,17 +17,12 @@ import (
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger" "github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/gateway" "github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/identn" "github.com/SigNoz/signoz/pkg/identn"
"github.com/SigNoz/signoz/pkg/instrumentation" "github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
pkgimplcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/modules/user/impluser"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier" "github.com/SigNoz/signoz/pkg/querier"
@@ -48,7 +42,6 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrytraces" "github.com/SigNoz/signoz/pkg/telemetrytraces"
pkgtokenizer "github.com/SigNoz/signoz/pkg/tokenizer" pkgtokenizer "github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes" "github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus" "github.com/SigNoz/signoz/pkg/zeus"
@@ -82,7 +75,6 @@ type SigNoz struct {
QueryParser queryparser.QueryParser QueryParser queryparser.QueryParser
Flagger flagger.Flagger Flagger flagger.Flagger
Gateway gateway.Gateway Gateway gateway.Gateway
Auditor auditor.Auditor
} }
func New( func New(
@@ -102,9 +94,7 @@ func New(
authzCallback func(context.Context, sqlstore.SQLStore, licensing.Licensing, dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error), authzCallback func(context.Context, sqlstore.SQLStore, licensing.Licensing, dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error),
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module, dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config], gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
auditorProviderFactories func(licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]],
querierHandlerCallback func(factory.ProviderSettings, querier.Querier, analytics.Analytics) querier.Handler, querierHandlerCallback func(factory.ProviderSettings, querier.Querier, analytics.Analytics) querier.Handler,
cloudIntegrationCallback func(cloudintegrationtypes.Store, global.Global, zeus.Zeus, gateway.Gateway, licensing.Licensing, serviceaccount.Module, cloudintegration.Config) (cloudintegration.Module, error),
) (*SigNoz, error) { ) (*SigNoz, error) {
// Initialize instrumentation // Initialize instrumentation
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz") instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
@@ -381,12 +371,6 @@ func New(
return nil, err return nil, err
} }
// Initialize auditor from the variant-specific provider factories
auditor, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.Auditor, auditorProviderFactories(licensing), config.Auditor.Provider)
if err != nil {
return nil, err
}
// Initialize authns // Initialize authns
store := sqlauthnstore.NewStore(sqlstore) store := sqlauthnstore.NewStore(sqlstore)
authNs, err := authNsCallback(ctx, providerSettings, store, licensing) authNs, err := authNsCallback(ctx, providerSettings, store, licensing)
@@ -433,19 +417,11 @@ func New(
return nil, err return nil, err
} }
serviceAccount := implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), authz, cache, analytics, providerSettings, config.ServiceAccount)
cloudIntegrationStore := pkgimplcloudintegration.NewStore(sqlstore)
cloudIntegrationModule, err := cloudIntegrationCallback(cloudIntegrationStore, global, zeus, gateway, licensing, serviceAccount, config.CloudIntegration)
if err != nil {
return nil, err
}
// Initialize all modules // Initialize all modules
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule) modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore)
// Initialize identN resolver // Initialize identN resolver
identNFactories := NewIdentNProviderFactories(tokenizer, serviceAccount, orgGetter, userGetter, config.User) identNFactories := NewIdentNProviderFactories(tokenizer, modules.ServiceAccount, orgGetter, userGetter, config.User)
identNResolver, err := identn.NewIdentNResolver(ctx, providerSettings, config.IdentN, identNFactories) identNResolver, err := identn.NewIdentNResolver(ctx, providerSettings, config.IdentN, identNFactories)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -467,8 +443,7 @@ func New(
tokenizer, tokenizer,
config, config,
modules.AuthDomain, modules.AuthDomain,
serviceAccount, modules.ServiceAccount,
cloudIntegrationModule,
} }
// Initialize stats reporter from the available stats reporter provider factories // Initialize stats reporter from the available stats reporter provider factories
@@ -495,7 +470,6 @@ func New(
factory.NewNamedService(factory.MustNewName("tokenizer"), tokenizer), factory.NewNamedService(factory.MustNewName("tokenizer"), tokenizer),
factory.NewNamedService(factory.MustNewName("authz"), authz), factory.NewNamedService(factory.MustNewName("authz"), authz),
factory.NewNamedService(factory.MustNewName("user"), userService, factory.MustNewName("authz")), factory.NewNamedService(factory.MustNewName("user"), userService, factory.MustNewName("authz")),
factory.NewNamedService(factory.MustNewName("auditor"), auditor),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -542,6 +516,5 @@ func New(
QueryParser: queryParser, QueryParser: queryParser,
Flagger: flagger, Flagger: flagger,
Gateway: gateway, Gateway: gateway,
Auditor: auditor,
}, nil }, nil
} }

View File

@@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/url" "net/url"
"os"
"time"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
@@ -23,6 +25,7 @@ type provider struct {
bundb *sqlstore.BunDB bundb *sqlstore.BunDB
dialect *dialect dialect *dialect
formatter sqlstore.SQLFormatter formatter sqlstore.SQLFormatter
done chan struct{}
} }
func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] {
@@ -59,13 +62,19 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
sqliteDialect := sqlitedialect.New() sqliteDialect := sqlitedialect.New()
bunDB := sqlstore.NewBunDB(settings, sqldb, sqliteDialect, hooks) bunDB := sqlstore.NewBunDB(settings, sqldb, sqliteDialect, hooks)
return &provider{
done := make(chan struct{})
p := &provider{
settings: settings, settings: settings,
sqldb: sqldb, sqldb: sqldb,
bundb: bunDB, bundb: bunDB,
dialect: new(dialect), dialect: new(dialect),
formatter: newFormatter(bunDB.Dialect()), formatter: newFormatter(bunDB.Dialect()),
}, nil done: done,
}
go p.walDiagnosticLoop(config.Sqlite.Path)
return p, nil
} }
func (provider *provider) BunDB() *bun.DB { func (provider *provider) BunDB() *bun.DB {
@@ -109,3 +118,73 @@ func (provider *provider) WrapAlreadyExistsErrf(err error, code errors.Code, for
return err return err
} }
// walDiagnosticLoop periodically logs pool stats, WAL file size, and busy prepared statements
// to help diagnose WAL checkpoint failures caused by permanent read locks.
func (provider *provider) walDiagnosticLoop(dbPath string) {
ticker := time.NewTicker(60 * time.Second)
defer ticker.Stop()
logger := provider.settings.Logger()
walPath := dbPath + "-wal"
for {
select {
case <-provider.done:
return
case <-ticker.C:
// 1. Log pool stats (no SQL needed)
stats := provider.sqldb.Stats()
logger.Info("sqlite_pool_stats",
slog.Int("max_open", stats.MaxOpenConnections),
slog.Int("open", stats.OpenConnections),
slog.Int("in_use", stats.InUse),
slog.Int("idle", stats.Idle),
slog.Int64("wait_count", stats.WaitCount),
slog.String("wait_duration", stats.WaitDuration.String()),
slog.Int64("max_idle_closed", stats.MaxIdleClosed),
slog.Int64("max_idle_time_closed", stats.MaxIdleTimeClosed),
slog.Int64("max_lifetime_closed", stats.MaxLifetimeClosed),
)
// 2. Log WAL file size (no SQL needed)
if info, err := os.Stat(walPath); err == nil {
logger.Info("sqlite_wal_size",
slog.Int64("bytes", info.Size()),
slog.String("path", walPath),
)
}
// 3. Check for busy prepared statements on a single pool connection
provider.checkBusyStatements(logger)
}
}
}
func (provider *provider) checkBusyStatements(logger *slog.Logger) {
conn, err := provider.sqldb.Conn(context.Background())
if err != nil {
logger.Warn("sqlite_diag_conn_error", slog.String("error", err.Error()))
return
}
defer conn.Close()
rows, err := conn.QueryContext(context.Background(), "SELECT sql FROM sqlite_stmt WHERE busy")
if err != nil {
logger.Warn("sqlite_diag_query_error", slog.String("error", err.Error()))
return
}
defer rows.Close()
for rows.Next() {
var stmtSQL string
if err := rows.Scan(&stmtSQL); err != nil {
logger.Warn("sqlite_diag_scan_error", slog.String("error", err.Error()))
continue
}
logger.Warn("leaked_busy_statement", slog.String("sql", stmtSQL))
}
if err := rows.Err(); err != nil {
logger.Warn("sqlite_diag_rows_error", slog.String("error", err.Error()))
}
}

View File

@@ -2,12 +2,10 @@ package cloudintegrationtypes
import ( import (
"encoding/json" "encoding/json"
"fmt"
"time" "time"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
@@ -148,85 +146,10 @@ func NewAccountsFromStorables(storableAccounts []*StorableCloudIntegration) ([]*
return accounts, nil return accounts, nil
} }
func NewGettableAccountWithConnectionArtifact(account *Account, connectionArtifact *ConnectionArtifact) *GettableAccountWithConnectionArtifact { func (account *Account) Update(config *AccountConfig) error {
return &GettableAccountWithConnectionArtifact{ if account.RemovedAt != nil {
ID: account.ID, return errors.New(errors.TypeUnsupported, ErrCodeCloudIntegrationRemoved, "this operation is not supported for a removed cloud integration account")
ConnectionArtifact: connectionArtifact,
} }
}
func NewGettableAccounts(accounts []*Account) *GettableAccounts {
return &GettableAccounts{
Accounts: accounts,
}
}
func NewAccountConfigFromPostable(provider CloudProviderType, config *PostableAccountConfig) (*AccountConfig, error) {
switch provider {
case CloudProviderTypeAWS:
if config.Aws == nil {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "AWS config can not be nil for AWS provider")
}
if err := validateAWSRegion(config.Aws.DeploymentRegion); err != nil {
return nil, err
}
if len(config.Aws.Regions) == 0 {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "at least one region is required")
}
for _, region := range config.Aws.Regions {
if err := validateAWSRegion(region); err != nil {
return nil, err
}
}
return &AccountConfig{AWS: &AWSAccountConfig{Regions: config.Aws.Regions}}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
}
func NewAccountConfigFromUpdatable(provider CloudProviderType, config *UpdatableAccount) (*AccountConfig, error) {
switch provider {
case CloudProviderTypeAWS:
if config.Config.AWS == nil {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "AWS config can not be nil for AWS provider")
}
if len(config.Config.AWS.Regions) == 0 {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "at least one region is required")
}
for _, region := range config.Config.AWS.Regions {
if err := validateAWSRegion(region); err != nil {
return nil, err
}
}
return &AccountConfig{AWS: &AWSAccountConfig{Regions: config.Config.AWS.Regions}}, 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,
}
}
func GetSigNozAPIURLFromDeployment(deployment *zeustypes.GettableDeployment) (string, error) {
if deployment.Name == "" || deployment.Cluster.Region.DNS == "" {
return "", errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput, "invalid deployment: missing name or DNS")
}
return fmt.Sprintf("https://%s.%s", deployment.Name, deployment.Cluster.Region.DNS), nil
}
func (account *Account) Update(provider CloudProviderType, config *AccountConfig) error {
account.Config = config account.Config = config
account.UpdatedAt = time.Now() account.UpdatedAt = time.Now()
return nil return nil
@@ -236,56 +159,47 @@ func (account *Account) IsRemoved() bool {
return account.RemovedAt != nil return account.RemovedAt != nil
} }
func (postableAccount *PostableAccount) UnmarshalJSON(data []byte) error { func NewAccountConfigFromPostable(provider CloudProviderType, config *PostableAccountConfig) (*AccountConfig, error) {
type Alias PostableAccount switch provider {
case CloudProviderTypeAWS:
var temp Alias if config.Aws == nil {
if err := json.Unmarshal(data, &temp); err != nil { return nil, errors.NewInternalf(errors.CodeInternal, "AWS config is nil")
return err }
return &AccountConfig{
AWS: &AWSAccountConfig{
Regions: config.Aws.Regions,
},
}, nil
} }
if temp.Config == nil || temp.Credentials == nil { return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
return errors.NewInvalidInputf(ErrCodeInvalidInput, "config and credentials are required")
}
if temp.Credentials.SigNozAPIURL == "" {
return errors.NewInvalidInputf(ErrCodeInvalidInput, "sigNozApiURL can not be empty")
}
if temp.Credentials.SigNozAPIKey == "" {
return errors.NewInvalidInputf(ErrCodeInvalidInput, "sigNozApiKey can not be empty")
}
if temp.Credentials.IngestionURL == "" {
return errors.NewInvalidInputf(ErrCodeInvalidInput, "ingestionUrl can not be empty")
}
if temp.Credentials.IngestionKey == "" {
return errors.NewInvalidInputf(ErrCodeInvalidInput, "ingestionKey can not be empty")
}
*postableAccount = PostableAccount(temp)
return nil
} }
func (updatableAccount *UpdatableAccount) UnmarshalJSON(data []byte) error { // func NewAccountFromPostableAccount(provider CloudProviderType, account *PostableAccount) (*Account, error) {
type Alias UpdatableAccount // req := &Account{
// Credentials: account.Credentials,
// }
var temp Alias // switch provider {
if err := json.Unmarshal(data, &temp); err != nil { // case CloudProviderTypeAWS:
return err // 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,
} }
if temp.Config == nil {
return errors.NewInvalidInputf(ErrCodeInvalidInput, "config is required")
}
*updatableAccount = UpdatableAccount(temp)
return nil
}
func (config *PostableAccountConfig) SetAgentVersion(agentVersion string) {
config.AgentVersion = agentVersion
} }
// ToJSON return JSON bytes for the provider's config // ToJSON return JSON bytes for the provider's config
@@ -298,3 +212,104 @@ func (config *AccountConfig) ToJSON() ([]byte, error) {
return nil, errors.NewInternalf(errors.CodeInternal, "no provider account config found") 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
}

View File

@@ -1,7 +1,6 @@
package cloudintegrationtypes package cloudintegrationtypes
import ( import (
"encoding/json"
"time" "time"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
@@ -76,17 +75,11 @@ func NewGettableAgentCheckIn(provider CloudProviderType, resp *AgentCheckInRespo
return gettable return gettable
} }
func (postable *PostableAgentCheckIn) UnmarshalJSON(data []byte) error { // Validate checks that the request uses either old fields (account_id, cloud_account_id) or
type Alias PostableAgentCheckIn // new fields (cloudIntegrationId, providerAccountId), never a mix of both.
func (req *PostableAgentCheckIn) Validate() error {
var temp Alias hasOldFields := req.ID != "" || req.AccountID != ""
err := json.Unmarshal(data, &temp) hasNewFields := !req.CloudIntegrationID.IsZero() || req.ProviderAccountID != ""
if err != nil {
return err
}
hasOldFields := temp.ID != "" || temp.AccountID != ""
hasNewFields := !temp.CloudIntegrationID.IsZero() || temp.ProviderAccountID != ""
if hasOldFields && hasNewFields { if hasOldFields && hasNewFields {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput, return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
@@ -96,7 +89,5 @@ func (postable *PostableAgentCheckIn) UnmarshalJSON(data []byte) error {
return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput, return errors.New(errors.TypeInvalidInput, ErrCodeInvalidInput,
"request must provide either old fields (account_id, cloud_account_id) or new fields (cloudIntegrationId, providerAccountId)") "request must provide either old fields (account_id, cloud_account_id) or new fields (cloudIntegrationId, providerAccountId)")
} }
*postable = PostableAgentCheckIn(temp)
return nil return nil
} }

View File

@@ -153,42 +153,30 @@ func (account *StorableCloudIntegration) Update(providerAccountID *string, agent
} }
// following StorableServiceConfig related functions are helper functions to convert between JSON string and ServiceConfig domain struct. // 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, error) { func newStorableServiceConfig(provider CloudProviderType, serviceID ServiceID, serviceConfig *ServiceConfig, supportedSignals *SupportedSignals) *StorableServiceConfig {
switch provider { switch provider {
case CloudProviderTypeAWS: case CloudProviderTypeAWS:
storableAWSServiceConfig := new(StorableAWSServiceConfig) storableAWSServiceConfig := new(StorableAWSServiceConfig)
if supportedSignals.Logs { if supportedSignals.Logs {
if serviceConfig.AWS.Logs == nil {
return nil, errors.NewInvalidInputf(ErrCodeCloudIntegrationInvalidConfig, "logs config is required for AWS service: %s", serviceID.StringValue())
}
storableAWSServiceConfig.Logs = &StorableAWSLogsServiceConfig{ storableAWSServiceConfig.Logs = &StorableAWSLogsServiceConfig{
Enabled: serviceConfig.AWS.Logs.Enabled, Enabled: serviceConfig.AWS.Logs.Enabled,
} }
if serviceID == AWSServiceS3Sync { if serviceID == AWSServiceS3Sync {
if serviceConfig.AWS.Logs.S3Buckets == nil {
return nil, errors.NewInvalidInputf(ErrCodeCloudIntegrationInvalidConfig, "s3 buckets config is required for AWS S3 Sync service")
}
storableAWSServiceConfig.Logs.S3Buckets = serviceConfig.AWS.Logs.S3Buckets storableAWSServiceConfig.Logs.S3Buckets = serviceConfig.AWS.Logs.S3Buckets
} }
} }
if supportedSignals.Metrics { if supportedSignals.Metrics {
if serviceConfig.AWS.Metrics == nil {
return nil, errors.NewInvalidInputf(ErrCodeCloudIntegrationInvalidConfig, "metrics config is required for AWS service: %s", serviceID.StringValue())
}
storableAWSServiceConfig.Metrics = &StorableAWSMetricsServiceConfig{ storableAWSServiceConfig.Metrics = &StorableAWSMetricsServiceConfig{
Enabled: serviceConfig.AWS.Metrics.Enabled, Enabled: serviceConfig.AWS.Metrics.Enabled,
} }
} }
return &StorableServiceConfig{AWS: storableAWSServiceConfig}, nil return &StorableServiceConfig{AWS: storableAWSServiceConfig}
default: default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue()) return nil
} }
} }

View File

@@ -98,11 +98,3 @@ var ValidAzureRegions = map[string]struct{}{
"westus2": {}, // West US 2 "westus2": {}, // West US 2
"westus3": {}, // West US 3 "westus3": {}, // West US 3
} }
func validateAWSRegion(region string) error {
_, ok := ValidAWSRegions[region]
if !ok {
return errors.NewInvalidInputf(ErrCodeInvalidCloudRegion, "invalid AWS region: %s", region)
}
return nil
}

View File

@@ -1,7 +1,6 @@
package cloudintegrationtypes package cloudintegrationtypes
import ( import (
"encoding/json"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@@ -63,10 +62,6 @@ type GettableServicesMetadata struct {
Services []*ServiceMetadata `json:"services" required:"true" nullable:"false"` Services []*ServiceMetadata `json:"services" required:"true" nullable:"false"`
} }
type ListServicesMetadataParams struct {
CloudIntegrationID valuer.UUID `query:"cloud_integration_id" required:"false"`
}
// Service represents a cloud integration service with its definition, // 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). // cloud integration service is non nil only when the service entry exists in DB with ANY config (enabled or disabled).
type Service struct { type Service struct {
@@ -74,10 +69,6 @@ type Service struct {
CloudIntegrationService *CloudIntegrationService `json:"cloudIntegrationService" required:"true" nullable:"true"` CloudIntegrationService *CloudIntegrationService `json:"cloudIntegrationService" required:"true" nullable:"true"`
} }
type GetServiceParams struct {
CloudIntegrationID valuer.UUID `query:"cloud_integration_id" required:"false"`
}
type UpdatableService struct { type UpdatableService struct {
Config *ServiceConfig `json:"config" required:"true" nullable:"false"` Config *ServiceConfig `json:"config" required:"true" nullable:"false"`
} }
@@ -238,12 +229,6 @@ func NewService(def ServiceDefinition, storableService *CloudIntegrationService)
} }
} }
func NewGettableServicesMetadata(services []*ServiceMetadata) *GettableServicesMetadata {
return &GettableServicesMetadata{
Services: services,
}
}
func NewServiceConfigFromJSON(provider CloudProviderType, jsonString string) (*ServiceConfig, error) { func NewServiceConfigFromJSON(provider CloudProviderType, jsonString string) (*ServiceConfig, error) {
storableServiceConfig, err := newStorableServiceConfigFromJSON(provider, jsonString) storableServiceConfig, err := newStorableServiceConfigFromJSON(provider, jsonString)
if err != nil { if err != nil {
@@ -274,27 +259,9 @@ func NewServiceConfigFromJSON(provider CloudProviderType, jsonString string) (*S
} }
// Update sets the service config. // Update sets the service config.
func (service *CloudIntegrationService) Update(provider CloudProviderType, serviceID ServiceID, config *ServiceConfig) error { func (service *CloudIntegrationService) Update(config *ServiceConfig) {
switch provider {
case CloudProviderTypeAWS:
if config.AWS == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS config is required for AWS service")
}
if serviceID == AWSServiceS3Sync {
if config.AWS.Logs == nil || config.AWS.Logs.S3Buckets == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS S3 Sync service requires S3 bucket configuration for logs")
}
}
// other validations happen in newStorableServiceConfig
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
service.Config = config service.Config = config
service.UpdatedAt = time.Now() service.UpdatedAt = time.Now()
return nil
} }
// IsServiceEnabled returns true if the service has at least one signal (logs or metrics) enabled // IsServiceEnabled returns true if the service has at least one signal (logs or metrics) enabled
@@ -332,32 +299,29 @@ func (config *ServiceConfig) IsLogsEnabled(provider CloudProviderType) bool {
} }
func (config *ServiceConfig) ToJSON(provider CloudProviderType, serviceID ServiceID, supportedSignals *SupportedSignals) ([]byte, error) { func (config *ServiceConfig) ToJSON(provider CloudProviderType, serviceID ServiceID, supportedSignals *SupportedSignals) ([]byte, error) {
storableServiceConfig, err := newStorableServiceConfig(provider, serviceID, config, supportedSignals) storableServiceConfig := newStorableServiceConfig(provider, serviceID, config, supportedSignals)
if err != nil {
return nil, err
}
return storableServiceConfig.toJSON(provider) return storableServiceConfig.toJSON(provider)
} }
func (updatableService *UpdatableService) UnmarshalJSON(data []byte) error { func (updatableService *UpdatableService) Validate(provider CloudProviderType, serviceID ServiceID) error {
type Alias UpdatableService switch provider {
case CloudProviderTypeAWS:
if updatableService.Config.AWS == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS config is required for AWS service")
}
var temp Alias if serviceID == AWSServiceS3Sync {
if err := json.Unmarshal(data, &temp); err != nil { if updatableService.Config.AWS.Logs == nil || updatableService.Config.AWS.Logs.S3Buckets == nil {
return err 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())
} }
if temp.Config == nil {
return errors.NewInvalidInputf(ErrCodeInvalidInput, "config is required")
}
*updatableService = UpdatableService(temp)
return nil
} }
// UTILITIES
// GetCloudIntegrationDashboardID returns the dashboard id for a cloud integration, given the cloud provider, service id, and dashboard id. // 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. // 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 { func GetCloudIntegrationDashboardID(cloudProvider CloudProviderType, svcID, dashboardID string) string {

View File

@@ -16,9 +16,6 @@ type Store interface {
// ListConnectedAccounts returns all the cloud integration accounts for the org and cloud provider // ListConnectedAccounts returns all the cloud integration accounts for the org and cloud provider
ListConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider CloudProviderType) ([]*StorableCloudIntegration, error) ListConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider CloudProviderType) ([]*StorableCloudIntegration, error)
// CountConnectedAccounts returns the count of connected accounts for the org and cloud provider
CountConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider CloudProviderType) (int, error)
// CreateAccount creates a new cloud integration account // CreateAccount creates a new cloud integration account
CreateAccount(ctx context.Context, account *StorableCloudIntegration) error CreateAccount(ctx context.Context, account *StorableCloudIntegration) error

View File

@@ -1,11 +1,8 @@
package zeustypes package zeustypes
import ( import (
"encoding/json"
"net/url" "net/url"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
@@ -59,53 +56,3 @@ func NewGettableHost(data []byte) *GettableHost {
Hosts: hosts, Hosts: hosts,
} }
} }
// GettableDeployment represents the parsed deployment info from zeus.GetDeployment.
// NOTE: break down deployment into multiple structs if needed.
type GettableDeployment struct {
ID string `json:"id"`
Name string `json:"name"`
State string `json:"state"`
Tier string `json:"tier"`
User string `json:"user"`
LicenseID string `json:"license_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ClusterID string `json:"cluster_id"`
Hosts []struct {
Name string `json:"name"`
IsDefault bool `json:"is_default"`
} `json:"hosts"`
Cluster struct {
ID string `json:"id"`
Name string `json:"name"`
CloudProvider string `json:"cloud_provider"`
CloudAccountID string `json:"cloud_account_id"`
CloudRegion string `json:"cloud_region"`
Address string `json:"address"`
Ca string `json:"ca"`
Buffer int `json:"buffer"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
RegionID string `json:"region_id"`
Region struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
DNS string `json:"dns"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
} `json:"region"`
} `json:"cluster"`
}
// NewGettableDeployment parses raw GetDeployment bytes into a GettableDeployment.
func NewGettableDeployment(data []byte) (*GettableDeployment, error) {
deployment := new(GettableDeployment)
err := json.Unmarshal(data, deployment)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to unmarshal deployment response")
}
return deployment, nil
}

View File

@@ -14,7 +14,7 @@ logger = setup_logger(__name__)
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def deprecated_create_cloud_integration_account( def create_cloud_integration_account(
request: pytest.FixtureRequest, request: pytest.FixtureRequest,
signoz: types.SigNoz, signoz: types.SigNoz,
) -> Callable[[str, str], dict]: ) -> Callable[[str, str], dict]:
@@ -78,78 +78,3 @@ def deprecated_create_cloud_integration_account(
logger.info("Cleaned up test account: %s", account_id) logger.info("Cleaned up test account: %s", account_id)
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
logger.info("Post-test disconnect cleanup failed: %s", exc) logger.info("Post-test disconnect cleanup failed: %s", exc)
@pytest.fixture(scope="function")
def create_cloud_integration_account(
request: pytest.FixtureRequest,
signoz: types.SigNoz,
) -> Callable[[str, str], dict]:
created_accounts: list[tuple[str, str]] = []
def _create(
admin_token: str,
cloud_provider: str = "aws",
deployment_region: str = "us-east-1",
regions: list[str] | None = None,
) -> dict:
if regions is None:
regions = ["us-east-1"]
endpoint = f"/api/v1/cloud_integrations/{cloud_provider}/accounts"
request_payload = {
"config": {
cloud_provider: {
"deploymentRegion": deployment_region,
"regions": regions,
}
},
"credentials": {
"sigNozApiURL": "https://test-deployment.test.signoz.cloud",
"sigNozApiKey": "test-api-key-789",
"ingestionUrl": "https://ingest.test.signoz.cloud",
"ingestionKey": "test-ingestion-key-123456",
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=request_payload,
timeout=10,
)
assert (
response.status_code == HTTPStatus.CREATED
), f"Failed to create test account: {response.status_code}: {response.text}"
data = response.json()["data"]
created_accounts.append((data["id"], cloud_provider))
return data
yield _create
if created_accounts:
get_token = request.getfixturevalue("get_token")
try:
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
for account_id, cloud_provider in created_accounts:
delete_endpoint = (
f"/api/v1/cloud_integrations/{cloud_provider}/accounts/{account_id}"
)
r = requests.delete(
signoz.self.host_configs["8080"].get(delete_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
if r.status_code != HTTPStatus.NO_CONTENT:
logger.info(
"Delete cleanup returned %s for account %s",
r.status_code,
account_id,
)
logger.info("Cleaned up test account: %s", account_id)
except Exception as exc: # pylint: disable=broad-except
logger.info("Post-test delete cleanup failed: %s", exc)

View File

@@ -1,15 +1,6 @@
"""Fixtures for cloud integration tests.""" """Fixtures for cloud integration tests."""
from typing import Callable
import requests import requests
from wiremock.client import (
HttpMethods,
Mapping,
MappingRequest,
MappingResponse,
WireMockMatchers,
)
from fixtures import types from fixtures import types
from fixtures.logger import setup_logger from fixtures.logger import setup_logger
@@ -17,7 +8,7 @@ from fixtures.logger import setup_logger
logger = setup_logger(__name__) logger = setup_logger(__name__)
def deprecated_simulate_agent_checkin( def simulate_agent_checkin(
signoz: types.SigNoz, signoz: types.SigNoz,
admin_token: str, admin_token: str,
cloud_provider: str, cloud_provider: str,
@@ -47,108 +38,3 @@ def deprecated_simulate_agent_checkin(
) )
return response return response
def setup_create_account_mocks(
signoz: types.SigNoz,
make_http_mocks: Callable,
) -> None:
"""Set up Zeus and Gateway mocks required by the CreateAccount endpoint."""
make_http_mocks(
signoz.zeus,
[
Mapping(
request=MappingRequest(
method=HttpMethods.GET,
url="/v2/deployments/me",
headers={
"X-Signoz-Cloud-Api-Key": {
WireMockMatchers.EQUAL_TO: "secret-key"
}
},
),
response=MappingResponse(
status=200,
json_body={
"status": "success",
"data": {
"name": "test-deployment",
"cluster": {"region": {"dns": "test.signoz.cloud"}},
},
},
),
persistent=False,
)
],
)
make_http_mocks(
signoz.gateway,
[
Mapping(
request=MappingRequest(
method=HttpMethods.GET,
url="/v1/workspaces/me/keys/search?name=aws-integration&page=1&per_page=10",
),
response=MappingResponse(
status=200,
json_body={
"status": "success",
"data": [],
"_pagination": {"page": 1, "per_page": 10, "total": 0},
},
),
persistent=False,
),
Mapping(
request=MappingRequest(
method=HttpMethods.POST,
url="/v1/workspaces/me/keys",
),
response=MappingResponse(
status=200,
json_body={
"status": "success",
"data": {
"name": "aws-integration",
"value": "test-ingestion-key-123456",
},
"error": "",
},
),
persistent=False,
),
],
)
def simulate_agent_checkin(
signoz: types.SigNoz,
admin_token: str,
cloud_provider: str,
account_id: str,
cloud_account_id: str,
data: dict | None = None,
) -> requests.Response:
endpoint = f"/api/v1/cloud_integrations/{cloud_provider}/accounts/check_in"
checkin_payload = {
"cloudIntegrationId": account_id,
"providerAccountId": cloud_account_id,
"data": data or {},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=checkin_payload,
timeout=10,
)
if not response.ok:
logger.error(
"Agent check-in failed: %s, response: %s",
response.status_code,
response.text,
)
return response

View File

@@ -76,7 +76,6 @@ def create_signoz(
"SIGNOZ_ALERTMANAGER_SIGNOZ_POLL__INTERVAL": "5s", "SIGNOZ_ALERTMANAGER_SIGNOZ_POLL__INTERVAL": "5s",
"SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_GROUP__WAIT": "1s", "SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_GROUP__WAIT": "1s",
"SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_GROUP__INTERVAL": "5s", "SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_GROUP__INTERVAL": "5s",
"SIGNOZ_CLOUDINTEGRATION_AGENT_VERSION": "v0.0.8",
} }
| sqlstore.env | sqlstore.env
| clickhouse.env | clickhouse.env

View File

@@ -6,7 +6,7 @@ import requests
from fixtures import types from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.cloudintegrationsutils import deprecated_simulate_agent_checkin from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger from fixtures.logger import setup_logger
logger = setup_logger(__name__) logger = setup_logger(__name__)
@@ -150,7 +150,7 @@ def test_duplicate_cloud_account_checkins(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test that two accounts cannot check in with the same cloud_account_id.""" """Test that two accounts cannot check in with the same cloud_account_id."""
@@ -159,16 +159,16 @@ def test_duplicate_cloud_account_checkins(
same_cloud_account_id = str(uuid.uuid4()) same_cloud_account_id = str(uuid.uuid4())
# Create two separate cloud integration accounts via generate-connection-url # Create two separate cloud integration accounts via generate-connection-url
account1 = deprecated_create_cloud_integration_account(admin_token, cloud_provider) account1 = create_cloud_integration_account(admin_token, cloud_provider)
account1_id = account1["account_id"] account1_id = account1["account_id"]
account2 = deprecated_create_cloud_integration_account(admin_token, cloud_provider) account2 = create_cloud_integration_account(admin_token, cloud_provider)
account2_id = account2["account_id"] account2_id = account2["account_id"]
assert account1_id != account2_id, "Two accounts should have different internal IDs" assert account1_id != account2_id, "Two accounts should have different internal IDs"
# First check-in succeeds: account1 claims cloud_account_id # First check-in succeeds: account1 claims cloud_account_id
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account1_id, same_cloud_account_id signoz, admin_token, cloud_provider, account1_id, same_cloud_account_id
) )
assert ( assert (
@@ -176,7 +176,7 @@ def test_duplicate_cloud_account_checkins(
), f"Expected 200 for first check-in, got {response.status_code}: {response.text}" ), f"Expected 200 for first check-in, got {response.status_code}: {response.text}"
# #
# Second check-in should fail: account2 tries to use the same cloud_account_id # Second check-in should fail: account2 tries to use the same cloud_account_id
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account2_id, same_cloud_account_id signoz, admin_token, cloud_provider, account2_id, same_cloud_account_id
) )

View File

@@ -6,7 +6,7 @@ import requests
from fixtures import types from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.cloudintegrationsutils import deprecated_simulate_agent_checkin from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger from fixtures.logger import setup_logger
logger = setup_logger(__name__) logger = setup_logger(__name__)
@@ -45,21 +45,19 @@ def test_list_connected_accounts_with_account(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test listing connected accounts after creating one.""" """Test listing connected accounts after creating one."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account # Create a test account
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
# Simulate agent check-in to mark as connected # Simulate agent check-in to mark as connected
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (
@@ -95,15 +93,13 @@ def test_get_account_status(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test getting the status of a specific account.""" """Test getting the status of a specific account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account (no check-in needed for status check) # Create a test account (no check-in needed for status check)
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
# Get account status # Get account status
@@ -156,21 +152,19 @@ def test_update_account_config(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test updating account configuration.""" """Test updating account configuration."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account # Create a test account
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
# Simulate agent check-in to mark as connected # Simulate agent check-in to mark as connected
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (
@@ -226,21 +220,19 @@ def test_disconnect_account(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test disconnecting an account.""" """Test disconnecting an account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account # Create a test account
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
# Simulate agent check-in to mark as connected # Simulate agent check-in to mark as connected
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (

View File

@@ -6,7 +6,7 @@ import requests
from fixtures import types from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.cloudintegrationsutils import deprecated_simulate_agent_checkin from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger from fixtures.logger import setup_logger
logger = setup_logger(__name__) logger = setup_logger(__name__)
@@ -50,20 +50,18 @@ def test_list_services_with_account(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test listing services for a specific connected account.""" """Test listing services for a specific connected account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in # Create a test account and do check-in
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (
@@ -146,20 +144,18 @@ def test_get_service_details_with_account(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test getting service details for a specific connected account.""" """Test getting service details for a specific connected account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in # Create a test account and do check-in
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (
@@ -252,20 +248,18 @@ def test_update_service_config(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test updating service configuration for a connected account.""" """Test updating service configuration for a connected account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in # Create a test account and do check-in
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (
@@ -369,20 +363,18 @@ def test_update_service_config_invalid_service(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test updating config for a non-existent service should fail.""" """Test updating config for a non-existent service should fail."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in # Create a test account and do check-in
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (
@@ -418,20 +410,18 @@ def test_update_service_config_disable_service(
signoz: types.SigNoz, signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str], get_token: Callable[[str, str], str],
deprecated_create_cloud_integration_account: Callable, create_cloud_integration_account: Callable,
) -> None: ) -> None:
"""Test disabling a service by updating config with enabled=false.""" """Test disabling a service by updating config with enabled=false."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD) admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in # Create a test account and do check-in
cloud_provider = "aws" cloud_provider = "aws"
account_data = deprecated_create_cloud_integration_account( account_data = create_cloud_integration_account(admin_token, cloud_provider)
admin_token, cloud_provider
)
account_id = account_data["account_id"] account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4()) cloud_account_id = str(uuid.uuid4())
response = deprecated_simulate_agent_checkin( response = simulate_agent_checkin(
signoz, admin_token, cloud_provider, account_id, cloud_account_id signoz, admin_token, cloud_provider, account_id, cloud_account_id
) )
assert ( assert (

View File

@@ -1,164 +0,0 @@
from http import HTTPStatus
from typing import Callable
import requests
from wiremock.client import (
HttpMethods,
Mapping,
MappingRequest,
MappingResponse,
)
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.cloudintegrationsutils import setup_create_account_mocks
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
CLOUD_PROVIDER = "aws"
CREDENTIALS_ENDPOINT = f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/credentials"
def test_apply_license(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Apply a license so that subsequent cloud integration calls succeed."""
add_license(signoz, make_http_mocks, get_token)
def test_get_credentials_success(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Happy path: all four credential fields are returned when Zeus and Gateway respond."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
setup_create_account_mocks(signoz, make_http_mocks)
response = requests.get(
signoz.self.host_configs["8080"].get(CREDENTIALS_ENDPOINT),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}: {response.text}"
data = response.json()["data"]
for field in ("sigNozApiUrl", "sigNozApiKey", "ingestionUrl", "ingestionKey"):
assert field in data, f"Response should contain '{field}'"
assert isinstance(data[field], str), f"'{field}' should be a string"
assert (
len(data[field]) > 0
), f"'{field}' should be non-empty when mocks are set up"
def test_get_credentials_partial_when_zeus_unavailable(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""When Zeus is unavailable, server still returns 200 with partial credentials.
The server silently ignores errors from individual credential lookups and returns
whatever it could resolve. The frontend is responsible for prompting the user to
fill in any empty fields.
"""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Reset Zeus mappings so no prior mock bleeds into this test,
# ensuring sigNozApiUrl cannot be resolved and will be returned as empty.
requests.post(
signoz.zeus.host_configs["8080"].get("/__admin/reset"),
timeout=10,
)
# Only set up Gateway mocks — Zeus has no mapping, so sigNozApiUrl will be empty
make_http_mocks(
signoz.gateway,
[
Mapping(
request=MappingRequest(
method=HttpMethods.GET,
url="/v1/workspaces/me/keys/search?name=aws-integration&page=1&per_page=10",
),
response=MappingResponse(
status=200,
json_body={
"status": "success",
"data": [],
"_pagination": {"page": 1, "per_page": 10, "total": 0},
},
),
persistent=False,
),
Mapping(
request=MappingRequest(
method=HttpMethods.POST,
url="/v1/workspaces/me/keys",
),
response=MappingResponse(
status=200,
json_body={
"status": "success",
"data": {
"name": "aws-integration",
"value": "test-ingestion-key-123456",
},
"error": "",
},
),
persistent=False,
),
],
)
response = requests.get(
signoz.self.host_configs["8080"].get(CREDENTIALS_ENDPOINT),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200 even without Zeus, got {response.status_code}: {response.text}"
data = response.json()["data"]
for field in ("sigNozApiUrl", "sigNozApiKey", "ingestionUrl", "ingestionKey"):
assert field in data, f"Response should always contain '{field}' key"
assert isinstance(data[field], str), f"'{field}' should be a string"
# sigNozApiUrl comes from Zeus, which is unavailable, so it should be empty
assert (
data["sigNozApiUrl"] == ""
), "sigNozApiUrl should be empty when Zeus is unavailable"
def test_get_credentials_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Unsupported cloud provider returns 400."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get(
"/api/v1/cloud_integrations/gcp/credentials"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400 for unsupported provider, got {response.status_code}"
assert "error" in response.json(), "Response should contain 'error' field"

View File

@@ -1,92 +0,0 @@
from http import HTTPStatus
from typing import Callable
import requests
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def test_apply_license(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Apply a license so that subsequent cloud integration calls succeed."""
add_license(signoz, make_http_mocks, get_token)
def test_create_account(
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test creating a new cloud integration account for AWS."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
data = create_cloud_integration_account(
admin_token,
cloud_provider,
deployment_region="us-east-1",
regions=["us-east-1", "us-west-2"],
)
assert "id" in data, "Response data should contain 'id' field"
assert len(data["id"]) > 0, "id should be a non-empty UUID string"
assert (
"connectionArtifact" in data
), "Response data should contain 'connectionArtifact' field"
artifact = data["connectionArtifact"]
assert "aws" in artifact, "connectionArtifact should contain 'aws' field"
assert (
"connectionUrl" in artifact["aws"]
), "connectionArtifact.aws should contain 'connectionUrl'"
connection_url = artifact["aws"]["connectionUrl"]
assert (
"console.aws.amazon.com/cloudformation" in connection_url
), "connectionUrl should be an AWS CloudFormation URL"
assert (
"region=us-east-1" in connection_url
), "connectionUrl should contain the deployment region"
def test_create_account_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test that creating an account with an unsupported cloud provider returns 400."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "gcp"
endpoint = f"/api/v1/cloud_integrations/{cloud_provider}/accounts"
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json={
"config": {
"gcp": {"deploymentRegion": "us-central1", "regions": ["us-central1"]}
},
"credentials": {
"sigNozApiURL": "https://test.signoz.cloud",
"sigNozApiKey": "test-key",
"ingestionUrl": "https://ingest.test.signoz.cloud",
"ingestionKey": "test-ingestion-key",
},
},
timeout=10,
)
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400 for unsupported provider, got {response.status_code}"
response_data = response.json()
assert "error" in response_data, "Response should contain 'error' field"

View File

@@ -1,129 +0,0 @@
import uuid
from http import HTTPStatus
from typing import Callable
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
CLOUD_PROVIDER = "aws"
def test_apply_license(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Apply a license so that subsequent cloud integration calls succeed."""
add_license(signoz, make_http_mocks, get_token)
def test_agent_check_in(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test agent check-in with new camelCase fields returns 200 with expected response shape."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(
admin_token, CLOUD_PROVIDER, regions=["us-east-1"]
)
account_id = account["id"]
provider_account_id = str(uuid.uuid4())
response = simulate_agent_checkin(
signoz,
admin_token,
CLOUD_PROVIDER,
account_id,
provider_account_id,
data={"version": "v0.0.8"},
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}: {response.text}"
data = response.json()["data"]
# New camelCase fields
assert data["cloudIntegrationId"] == account_id, "cloudIntegrationId should match"
assert (
data["providerAccountId"] == provider_account_id
), "providerAccountId should match"
assert "integrationConfig" in data, "Response should contain 'integrationConfig'"
assert data["removedAt"] is None, "removedAt should be null for a live account"
# Backward-compat snake_case fields
assert data["account_id"] == account_id, "account_id (compat) should match"
assert (
data["cloud_account_id"] == provider_account_id
), "cloud_account_id (compat) should match"
assert (
"integration_config" in data
), "Response should contain 'integration_config' (compat)"
assert "removed_at" in data, "Response should contain 'removed_at' (compat)"
# integrationConfig should reflect the configured regions
integration_config = data["integrationConfig"]
assert "aws" in integration_config, "integrationConfig should contain 'aws' block"
assert integration_config["aws"]["enabledRegions"] == [
"us-east-1"
], "enabledRegions should match account config"
def test_agent_check_in_account_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test that check-in with an unknown cloudIntegrationId returns 404."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
fake_id = str(uuid.uuid4())
response = simulate_agent_checkin(
signoz, admin_token, CLOUD_PROVIDER, fake_id, str(uuid.uuid4())
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}: {response.text}"
def test_duplicate_cloud_account_checkins(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test that two different accounts cannot check in with the same providerAccountId."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account1 = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account2 = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
assert account1["id"] != account2["id"], "Two accounts should have different IDs"
same_provider_account_id = str(uuid.uuid4())
# First check-in: account1 claims the provider account ID
response = simulate_agent_checkin(
signoz, admin_token, CLOUD_PROVIDER, account1["id"], same_provider_account_id
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200 for first check-in, got {response.status_code}: {response.text}"
# Second check-in: account2 tries to claim the same provider account ID → 409
response = simulate_agent_checkin(
signoz, admin_token, CLOUD_PROVIDER, account2["id"], same_provider_account_id
)
assert (
response.status_code == HTTPStatus.CONFLICT
), f"Expected 409 for duplicate providerAccountId, got {response.status_code}: {response.text}"

View File

@@ -1,341 +0,0 @@
import uuid
from http import HTTPStatus
from typing import Callable
import requests
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
CLOUD_PROVIDER = "aws"
def test_apply_license(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Apply a license so that subsequent cloud integration calls succeed."""
add_license(signoz, make_http_mocks, get_token)
def test_list_accounts_empty(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""List accounts returns an empty list when no accounts have checked in."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
assert "accounts" in data, "Response should contain 'accounts' field"
assert isinstance(data["accounts"], list), "accounts should be a list"
assert (
len(data["accounts"]) == 0
), "accounts list should be empty when no accounts have checked in"
def test_list_accounts_after_checkin(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""List accounts returns an account after it has checked in."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(
admin_token, CLOUD_PROVIDER, regions=["us-east-1"]
)
account_id = account["id"]
provider_account_id = str(uuid.uuid4())
checkin = simulate_agent_checkin(
signoz, admin_token, CLOUD_PROVIDER, account_id, provider_account_id
)
assert checkin.status_code == HTTPStatus.OK, f"Check-in failed: {checkin.text}"
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
found = next((a for a in data["accounts"] if a["id"] == account_id), None)
assert (
found is not None
), f"Account {account_id} should appear in list after check-in"
assert (
found["providerAccountId"] == provider_account_id
), "providerAccountId should match"
assert found["config"]["aws"]["regions"] == [
"us-east-1"
], "regions should match account config"
assert (
found["agentReport"] is not None
), "agentReport should be present after check-in"
assert found["removedAt"] is None, "removedAt should be null for a live account"
def test_get_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Get a specific account by ID returns the account with correct fields."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(
admin_token, CLOUD_PROVIDER, regions=["us-east-1", "eu-west-1"]
)
account_id = account["id"]
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
assert data["id"] == account_id, "id should match"
assert data["config"]["aws"]["regions"] == [
"us-east-1",
"eu-west-1",
], "regions should match"
assert data["removedAt"] is None, "removedAt should be null"
def test_get_account_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Get a non-existent account returns 404."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{uuid.uuid4()}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_update_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Update account config and verify the change is persisted via GET."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(
admin_token, CLOUD_PROVIDER, regions=["us-east-1"]
)
account_id = account["id"]
updated_regions = ["us-east-1", "us-west-2", "eu-west-1"]
response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
json={"config": {"aws": {"regions": updated_regions}}},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204, got {response.status_code}"
get_response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert get_response.status_code == HTTPStatus.OK
assert (
get_response.json()["data"]["config"]["aws"]["regions"] == updated_regions
), "Regions should reflect the update"
def test_update_account_after_checkin_preserves_connected_status(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Updating config after agent check-in must not remove the account from the connected list.
Regression test: previously, updating an account would reset account_id to NULL,
causing the account to disappear from the connected accounts listing
(which filters on account_id IS NOT NULL).
"""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# 1. Create account
account = create_cloud_integration_account(
admin_token, CLOUD_PROVIDER, regions=["us-east-1"]
)
account_id = account["id"]
provider_account_id = str(uuid.uuid4())
# 2. Agent checks in — sets account_id and last_agent_report
checkin = simulate_agent_checkin(
signoz, admin_token, CLOUD_PROVIDER, account_id, provider_account_id
)
assert checkin.status_code == HTTPStatus.OK, f"Check-in failed: {checkin.text}"
# 3. Verify the account appears in the connected list
list_response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert list_response.status_code == HTTPStatus.OK
accounts_before = list_response.json()["data"]["accounts"]
found_before = next((a for a in accounts_before if a["id"] == account_id), None)
assert found_before is not None, "Account should be listed after check-in"
# 4. Update account config
updated_regions = ["us-east-1", "us-west-2"]
update_response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
json={"config": {"aws": {"regions": updated_regions}}},
timeout=10,
)
assert (
update_response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204, got {update_response.status_code}"
# 5. Verify the account still appears in the connected list with correct fields
list_response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert list_response.status_code == HTTPStatus.OK
accounts_after = list_response.json()["data"]["accounts"]
found_after = next((a for a in accounts_after if a["id"] == account_id), None)
assert (
found_after is not None
), "Account must still be listed after config update (account_id should not be reset)"
assert (
found_after["providerAccountId"] == provider_account_id
), "providerAccountId should be preserved after update"
assert (
found_after["agentReport"] is not None
), "agentReport should be preserved after update"
assert (
found_after["config"]["aws"]["regions"] == updated_regions
), "Config should reflect the update"
assert found_after["removedAt"] is None, "removedAt should still be null"
def test_disconnect_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Disconnect an account removes it from the connected list."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
checkin = simulate_agent_checkin(
signoz, admin_token, CLOUD_PROVIDER, account_id, str(uuid.uuid4())
)
assert checkin.status_code == HTTPStatus.OK, f"Check-in failed: {checkin.text}"
response = requests.delete(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204, got {response.status_code}"
list_response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
accounts = list_response.json()["data"]["accounts"]
assert not any(
a["id"] == account_id for a in accounts
), "Disconnected account should not appear in the connected list"
def test_disconnect_account_idempotent(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Disconnect on a non-existent account ID returns 204 (blind update, no existence check)."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.delete(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{uuid.uuid4()}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204, got {response.status_code}"

View File

@@ -1,445 +0,0 @@
import uuid
from http import HTTPStatus
from typing import Callable
import requests
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
CLOUD_PROVIDER = "aws"
SERVICE_ID = "rds"
def test_apply_license(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Apply a license so that subsequent cloud integration calls succeed."""
add_license(signoz, make_http_mocks, get_token)
def test_list_services_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""List available services without specifying a cloud_integration_id."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
assert "services" in data, "Response should contain 'services' field"
assert isinstance(data["services"], list), "services should be a list"
assert len(data["services"]) > 0, "services list should be non-empty"
service = data["services"][0]
assert "id" in service, "Service should have 'id' field"
assert "title" in service, "Service should have 'title' field"
assert "icon" in service, "Service should have 'icon' field"
assert "enabled" in service, "Service should have 'enabled' field"
def test_list_services_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""List services filtered to a specific account — all disabled by default."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services?cloud_integration_id={account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
assert "services" in data, "Response should contain 'services' field"
assert len(data["services"]) > 0, "services list should be non-empty"
for svc in data["services"]:
assert "enabled" in svc, "Each service should have 'enabled' field"
assert (
svc["enabled"] is False
), f"Service {svc['id']} should be disabled before any config is set"
def test_get_service_details_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Get full service definition without specifying an account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services/{SERVICE_ID}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
assert data["id"] == SERVICE_ID, f"id should be '{SERVICE_ID}'"
assert "title" in data, "Service should have 'title'"
assert "overview" in data, "Service should have 'overview' (markdown)"
assert "assets" in data, "Service should have 'assets'"
assert isinstance(
data["assets"]["dashboards"], list
), "assets.dashboards should be a list"
assert (
"telemetryCollectionStrategy" in data
), "Service should have 'telemetryCollectionStrategy'"
assert (
data["cloudIntegrationService"] is None
), "cloudIntegrationService should be null without account context"
def test_get_service_details_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Get service details with account context — cloudIntegrationService is null before first UpdateService."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services/{SERVICE_ID}"
f"?cloud_integration_id={account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
data = response.json()["data"]
assert data["id"] == SERVICE_ID
assert (
data["cloudIntegrationService"] is None
), "cloudIntegrationService should be null before any service config is set"
def test_get_service_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Get a non-existent service ID returns 400 (invalid service ID is a bad request)."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services/non-existent-service"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400, got {response.status_code}"
def test_update_service_config(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Enable a service and verify the config is persisted via GET."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
put_response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}/services/{SERVICE_ID}"
),
headers={"Authorization": f"Bearer {admin_token}"},
json={
"config": {"aws": {"metrics": {"enabled": True}, "logs": {"enabled": True}}}
},
timeout=10,
)
assert (
put_response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204, got {put_response.status_code}: {put_response.text}"
get_response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services/{SERVICE_ID}"
f"?cloud_integration_id={account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert get_response.status_code == HTTPStatus.OK
data = get_response.json()["data"]
svc = data["cloudIntegrationService"]
assert (
svc is not None
), "cloudIntegrationService should be non-null after UpdateService"
assert (
svc["config"]["aws"]["metrics"]["enabled"] is True
), "metrics should be enabled"
assert svc["config"]["aws"]["logs"]["enabled"] is True, "logs should be enabled"
assert (
svc["cloudIntegrationId"] == account_id
), "cloudIntegrationId should match the account"
def test_update_service_config_disable(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Enable then disable a service — config change is persisted."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
endpoint = signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}/services/{SERVICE_ID}"
)
# Enable
r = requests.put(
endpoint,
headers={"Authorization": f"Bearer {admin_token}"},
json={
"config": {"aws": {"metrics": {"enabled": True}, "logs": {"enabled": True}}}
},
timeout=10,
)
assert (
r.status_code == HTTPStatus.NO_CONTENT
), f"Enable failed: {r.status_code}: {r.text}"
# Disable
r = requests.put(
endpoint,
headers={"Authorization": f"Bearer {admin_token}"},
json={
"config": {
"aws": {"metrics": {"enabled": False}, "logs": {"enabled": False}}
}
},
timeout=10,
)
assert (
r.status_code == HTTPStatus.NO_CONTENT
), f"Disable failed: {r.status_code}: {r.text}"
get_response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services/{SERVICE_ID}"
f"?cloud_integration_id={account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert get_response.status_code == HTTPStatus.OK
svc = get_response.json()["data"]["cloudIntegrationService"]
assert (
svc is not None
), "cloudIntegrationService should still be present after disable"
assert (
svc["config"]["aws"]["metrics"]["enabled"] is False
), "metrics should be disabled"
assert svc["config"]["aws"]["logs"]["enabled"] is False, "logs should be disabled"
def test_update_service_account_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""PUT with a non-existent account UUID returns 404."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{uuid.uuid4()}/services/{SERVICE_ID}"
),
headers={"Authorization": f"Bearer {admin_token}"},
json={"config": {"aws": {"metrics": {"enabled": True}}}},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_list_services_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""List services for an unsupported cloud provider returns 400."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/cloud_integrations/gcp/services"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400, got {response.status_code}"
def test_list_services_account_removed(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""List services with a cloud_integration_id for a deleted account returns 404."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
delete_response = requests.delete(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
delete_response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204 on delete, got {delete_response.status_code}"
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services?cloud_integration_id={account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_get_service_details_account_removed(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Get service details with a cloud_integration_id for a deleted account returns 404."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
delete_response = requests.delete(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
delete_response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204 on delete, got {delete_response.status_code}"
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/services/{SERVICE_ID}"
f"?cloud_integration_id={account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_update_service_account_removed(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""PUT service config for a deleted account returns 404."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
delete_response = requests.delete(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
delete_response.status_code == HTTPStatus.NO_CONTENT
), f"Expected 204 on delete, got {delete_response.status_code}"
response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}/services/{SERVICE_ID}"
),
headers={"Authorization": f"Bearer {admin_token}"},
json={"config": {"aws": {"metrics": {"enabled": True}}}},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"