mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-10 22:20:20 +01:00
Compare commits
109 Commits
debug-wal
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c6f6208f1 | ||
|
|
13f015142e | ||
|
|
7d405a4e8c | ||
|
|
7279c5f770 | ||
|
|
380d944205 | ||
|
|
e543776efc | ||
|
|
621127b7fb | ||
|
|
8924f96bb2 | ||
|
|
a52155886b | ||
|
|
4a953a4998 | ||
|
|
1bb909ae7d | ||
|
|
88a5d03a7f | ||
|
|
92218ccf68 | ||
|
|
45d4974c38 | ||
|
|
303aa282dc | ||
|
|
950830d193 | ||
|
|
b39b3970b6 | ||
|
|
a539983a4c | ||
|
|
861bbf1ec8 | ||
|
|
8158a85e86 | ||
|
|
b7d7a5422e | ||
|
|
74b7f4b4e8 | ||
|
|
985d66539a | ||
|
|
b15f817ba3 | ||
|
|
5e1cf14de9 | ||
|
|
99944b5f92 | ||
|
|
f8eda16533 | ||
|
|
a2eb8ab00a | ||
|
|
601007cba1 | ||
|
|
925a29d2df | ||
|
|
d54fc50236 | ||
|
|
a2ad5b1172 | ||
|
|
802a11ee2b | ||
|
|
a8124f6e73 | ||
|
|
8811aaefe8 | ||
|
|
66aaaea918 | ||
|
|
900c489d91 | ||
|
|
743fe56523 | ||
|
|
3a9e93ebdf | ||
|
|
cdbb78a93d | ||
|
|
c11186f7bf | ||
|
|
51dbb0b5b9 | ||
|
|
2545d7df61 | ||
|
|
3f91821825 | ||
|
|
ee5d182539 | ||
|
|
0bc12f02bc | ||
|
|
e5f00421fe | ||
|
|
539252e10c | ||
|
|
d65f426254 | ||
|
|
6e52f2c8f0 | ||
|
|
d9f8a4ae5a | ||
|
|
eefe3edffd | ||
|
|
2051861a03 | ||
|
|
4b01a40fb9 | ||
|
|
2d8a00bf18 | ||
|
|
f1b26b310f | ||
|
|
2c438b6c32 | ||
|
|
1814c2d13c | ||
|
|
e6cd771f11 | ||
|
|
6b94f87ca0 | ||
|
|
bf315253ae | ||
|
|
668ff7bc39 | ||
|
|
07f2aa52fd | ||
|
|
3416b3ad55 | ||
|
|
d6caa4f2c7 | ||
|
|
f86371566d | ||
|
|
9115803084 | ||
|
|
0c14d8f966 | ||
|
|
7afb461af8 | ||
|
|
a21fbb4ee0 | ||
|
|
0369842f3d | ||
|
|
59cd96562a | ||
|
|
cc4475cab7 | ||
|
|
ac8c648420 | ||
|
|
bede6be4b8 | ||
|
|
dd3d60e6df | ||
|
|
538ab686d2 | ||
|
|
936a325cb9 | ||
|
|
c6cdcd0143 | ||
|
|
cd9211d718 | ||
|
|
0601c28782 | ||
|
|
580610dbfa | ||
|
|
2d2aa02a81 | ||
|
|
dd9723ad13 | ||
|
|
3651469416 | ||
|
|
febce75734 | ||
|
|
e1616f3487 | ||
|
|
4b94287ac7 | ||
|
|
1575c7c54c | ||
|
|
8def3f835b | ||
|
|
11ed15f4c5 | ||
|
|
f47877cca9 | ||
|
|
bb2b9215ba | ||
|
|
3111904223 | ||
|
|
003e2c30d8 | ||
|
|
00fe516d10 | ||
|
|
0305f4f7db | ||
|
|
c60019a6dc | ||
|
|
acde2a37fa | ||
|
|
945241a52a | ||
|
|
e967f80c86 | ||
|
|
a09dc325de | ||
|
|
379b4f7fc4 | ||
|
|
5e536ae077 | ||
|
|
234585e642 | ||
|
|
2cc14f1ad4 | ||
|
|
dc4ed4d239 | ||
|
|
7281c36873 | ||
|
|
40288776e8 |
@@ -8,6 +8,7 @@ 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"
|
||||||
@@ -17,11 +18,15 @@ 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"
|
||||||
@@ -29,6 +34,7 @@ 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"
|
||||||
@@ -93,9 +99,15 @@ 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))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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"
|
||||||
@@ -16,6 +17,8 @@ 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"
|
||||||
@@ -24,15 +27,20 @@ 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"
|
||||||
@@ -40,6 +48,7 @@ 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"
|
||||||
)
|
)
|
||||||
@@ -125,7 +134,6 @@ 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)
|
||||||
@@ -133,12 +141,32 @@ 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
|
||||||
|
|||||||
@@ -364,3 +364,41 @@ 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
|
||||||
|
|||||||
@@ -3309,7 +3309,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/CloudintegrationtypesPostableAccount'
|
$ref: '#/components/schemas/CloudintegrationtypesPostableAccount'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"201":
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
@@ -3322,7 +3322,7 @@ paths:
|
|||||||
- status
|
- status
|
||||||
- data
|
- data
|
||||||
type: object
|
type: object
|
||||||
description: OK
|
description: Created
|
||||||
"401":
|
"401":
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -3683,6 +3683,11 @@ 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
|
||||||
@@ -3735,6 +3740,11 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
521
ee/modules/cloudintegration/implcloudintegration/module.go
Normal file
521
ee/modules/cloudintegration/implcloudintegration/module.go
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -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, nil).Wrap)
|
r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, s.signoz.Auditor).Wrap)
|
||||||
r.Use(middleware.NewComment().Wrap)
|
r.Use(middleware.NewComment().Wrap)
|
||||||
|
|
||||||
apiHandler.RegisterRoutes(r, am)
|
apiHandler.RegisterRoutes(r, am)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import type {
|
|||||||
CloudintegrationtypesPostableAgentCheckInDTO,
|
CloudintegrationtypesPostableAgentCheckInDTO,
|
||||||
CloudintegrationtypesUpdatableAccountDTO,
|
CloudintegrationtypesUpdatableAccountDTO,
|
||||||
CloudintegrationtypesUpdatableServiceDTO,
|
CloudintegrationtypesUpdatableServiceDTO,
|
||||||
CreateAccount200,
|
CreateAccount201,
|
||||||
CreateAccountPathParameters,
|
CreateAccountPathParameters,
|
||||||
DisconnectAccountPathParameters,
|
DisconnectAccountPathParameters,
|
||||||
GetAccount200,
|
GetAccount200,
|
||||||
@@ -36,10 +36,12 @@ import type {
|
|||||||
GetConnectionCredentials200,
|
GetConnectionCredentials200,
|
||||||
GetConnectionCredentialsPathParameters,
|
GetConnectionCredentialsPathParameters,
|
||||||
GetService200,
|
GetService200,
|
||||||
|
GetServiceParams,
|
||||||
GetServicePathParameters,
|
GetServicePathParameters,
|
||||||
ListAccounts200,
|
ListAccounts200,
|
||||||
ListAccountsPathParameters,
|
ListAccountsPathParameters,
|
||||||
ListServicesMetadata200,
|
ListServicesMetadata200,
|
||||||
|
ListServicesMetadataParams,
|
||||||
ListServicesMetadataPathParameters,
|
ListServicesMetadataPathParameters,
|
||||||
RenderErrorResponseDTO,
|
RenderErrorResponseDTO,
|
||||||
UpdateAccountPathParameters,
|
UpdateAccountPathParameters,
|
||||||
@@ -260,7 +262,7 @@ export const createAccount = (
|
|||||||
cloudintegrationtypesPostableAccountDTO: BodyType<CloudintegrationtypesPostableAccountDTO>,
|
cloudintegrationtypesPostableAccountDTO: BodyType<CloudintegrationtypesPostableAccountDTO>,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
) => {
|
) => {
|
||||||
return GeneratedAPIInstance<CreateAccount200>({
|
return GeneratedAPIInstance<CreateAccount201>({
|
||||||
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' },
|
||||||
@@ -940,19 +942,25 @@ 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,
|
{ cloudProvider }: ListServicesMetadataPathParameters,
|
||||||
}: ListServicesMetadataPathParameters) => {
|
params?: ListServicesMetadataParams,
|
||||||
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 = <
|
||||||
@@ -960,6 +968,7 @@ 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>>,
|
||||||
@@ -971,11 +980,12 @@ export const getListServicesMetadataQueryOptions = <
|
|||||||
const { query: queryOptions } = options ?? {};
|
const { query: queryOptions } = options ?? {};
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
queryOptions?.queryKey ?? getListServicesMetadataQueryKey({ cloudProvider });
|
queryOptions?.queryKey ??
|
||||||
|
getListServicesMetadataQueryKey({ cloudProvider }, params);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
Awaited<ReturnType<typeof listServicesMetadata>>
|
Awaited<ReturnType<typeof listServicesMetadata>>
|
||||||
> = ({ signal }) => listServicesMetadata({ cloudProvider }, signal);
|
> = ({ signal }) => listServicesMetadata({ cloudProvider }, params, signal);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey,
|
queryKey,
|
||||||
@@ -1003,6 +1013,7 @@ 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>>,
|
||||||
@@ -1013,6 +1024,7 @@ export function useListServicesMetadata<
|
|||||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||||
const queryOptions = getListServicesMetadataQueryOptions(
|
const queryOptions = getListServicesMetadataQueryOptions(
|
||||||
{ cloudProvider },
|
{ cloudProvider },
|
||||||
|
params,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1031,10 +1043,11 @@ 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 }) },
|
{ queryKey: getListServicesMetadataQueryKey({ cloudProvider }, params) },
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1047,21 +1060,24 @@ 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,
|
{ cloudProvider, serviceId }: GetServicePathParameters,
|
||||||
serviceId,
|
params?: GetServiceParams,
|
||||||
}: GetServicePathParameters) => {
|
) => {
|
||||||
return [
|
return [
|
||||||
`/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`,
|
`/api/v1/cloud_integrations/${cloudProvider}/services/${serviceId}`,
|
||||||
|
...(params ? [params] : []),
|
||||||
] as const;
|
] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1070,6 +1086,7 @@ 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>>,
|
||||||
@@ -1081,11 +1098,12 @@ export const getGetServiceQueryOptions = <
|
|||||||
const { query: queryOptions } = options ?? {};
|
const { query: queryOptions } = options ?? {};
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
queryOptions?.queryKey ?? getGetServiceQueryKey({ cloudProvider, serviceId });
|
queryOptions?.queryKey ??
|
||||||
|
getGetServiceQueryKey({ cloudProvider, serviceId }, params);
|
||||||
|
|
||||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getService>>> = ({
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof getService>>> = ({
|
||||||
signal,
|
signal,
|
||||||
}) => getService({ cloudProvider, serviceId }, signal);
|
}) => getService({ cloudProvider, serviceId }, params, signal);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey,
|
queryKey,
|
||||||
@@ -1111,6 +1129,7 @@ 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>>,
|
||||||
@@ -1121,6 +1140,7 @@ 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,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1139,10 +1159,11 @@ 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 }) },
|
{ queryKey: getGetServiceQueryKey({ cloudProvider, serviceId }, params) },
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3589,7 +3589,7 @@ export type ListAccounts200 = {
|
|||||||
export type CreateAccountPathParameters = {
|
export type CreateAccountPathParameters = {
|
||||||
cloudProvider: string;
|
cloudProvider: string;
|
||||||
};
|
};
|
||||||
export type CreateAccount200 = {
|
export type CreateAccount201 = {
|
||||||
data: CloudintegrationtypesGettableAccountWithConnectionArtifactDTO;
|
data: CloudintegrationtypesGettableAccountWithConnectionArtifactDTO;
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
@@ -3647,6 +3647,14 @@ 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;
|
||||||
/**
|
/**
|
||||||
@@ -3659,6 +3667,14 @@ 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;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -677,6 +677,18 @@ 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
|
||||||
|
|||||||
@@ -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.StatusOK,
|
SuccessStatusCode: http.StatusCreated,
|
||||||
ErrorStatusCodes: []int{},
|
ErrorStatusCodes: []int{},
|
||||||
Deprecated: false,
|
Deprecated: false,
|
||||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
@@ -138,6 +138,7 @@ 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",
|
||||||
@@ -158,6 +159,7 @@ 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",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ 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,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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"
|
||||||
@@ -32,11 +33,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, integrationID *valuer.UUID, serviceID citypes.ServiceID, provider citypes.CloudProviderType) (*citypes.Service, error)
|
GetService(ctx context.Context, orgID valuer.UUID, serviceID citypes.ServiceID, provider citypes.CloudProviderType, integrationID valuer.UUID) (*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
|
||||||
@@ -55,6 +56,8 @@ 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 {
|
||||||
|
|||||||
32
pkg/modules/cloudintegration/config.go
Normal file
32
pkg/modules/cloudintegration/config.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,21 +1,176 @@
|
|||||||
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{}
|
||||||
|
|
||||||
func NewDefinitionStore() citypes.ServiceDefinitionStore {
|
// NewServiceDefinitionStore creates a new ServiceDefinitionStore backed by the embedded filesystem.
|
||||||
|
func NewServiceDefinitionStore() citypes.ServiceDefinitionStore {
|
||||||
return &definitionStore{}
|
return &definitionStore{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *definitionStore) Get(ctx context.Context, provider citypes.CloudProviderType, serviceID citypes.ServiceID) (*citypes.ServiceDefinition, error) {
|
// Get reads and hydrates the service definition for the given provider and service ID.
|
||||||
panic("unimplemented")
|
func (s *definitionStore) Get(ctx context.Context, provider citypes.CloudProviderType, serviceID citypes.ServiceID) (*citypes.ServiceDefinition, error) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *definitionStore) List(ctx context.Context, provider citypes.CloudProviderType) ([]*citypes.ServiceDefinition, error) {
|
// List reads and hydrates all service definitions for the given provider, sorted by ID.
|
||||||
panic("unimplemented")
|
func (s *definitionStore) List(ctx context.Context, provider citypes.CloudProviderType) ([]*citypes.ServiceDefinition, error) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,457 @@
|
|||||||
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 (handler *handler) GetConnectionCredentials(http.ResponseWriter, *http.Request) {
|
func NewHandler(module cloudintegration.Module) cloudintegration.Handler {
|
||||||
panic("unimplemented")
|
return &handler{
|
||||||
|
module: module,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) CreateAccount(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) GetConnectionCredentials(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ListAccounts(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) CreateAccount(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) GetAccount(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) UpdateAccount(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) ListAccounts(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) DisconnectAccount(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) UpdateAccount(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ListServicesMetadata(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) DisconnectAccount(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) GetService(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) ListServicesMetadata(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) UpdateService(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) GetService(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) AgentCheckIn(writer http.ResponseWriter, request *http.Request) {
|
func (handler *handler) UpdateService(rw http.ResponseWriter, r *http.Request) {
|
||||||
// TODO implement me
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
panic("implement me")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|||||||
77
pkg/modules/cloudintegration/implcloudintegration/module.go
Normal file
77
pkg/modules/cloudintegration/implcloudintegration/module.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
@@ -73,6 +73,26 @@ 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.
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ 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"
|
||||||
@@ -1137,12 +1138,19 @@ func (aH *APIHandler) Get(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboard := new(dashboardtypes.Dashboard)
|
dashboard := new(dashboardtypes.Dashboard)
|
||||||
if aH.CloudIntegrationsController.IsCloudIntegrationDashboardUuid(id) {
|
|
||||||
cloudIntegrationDashboard, apiErr := aH.CloudIntegrationsController.GetDashboardById(ctx, orgID, id)
|
if _, _, _, err := cloudintegrationtypes.ParseCloudIntegrationDashboardID(id); err == nil {
|
||||||
if apiErr != nil {
|
cloudIntegrationDashboard, err := aH.Signoz.Modules.CloudIntegration.GetDashboardByID(ctx, orgID, id)
|
||||||
render.Error(rw, errorsV2.Wrapf(apiErr, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to get dashboard"))
|
if err != nil && !errorsV2.Ast(err, errorsV2.TypeLicenseUnavailable) {
|
||||||
|
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)
|
||||||
@@ -1207,9 +1215,11 @@ func (aH *APIHandler) List(rw http.ResponseWriter, r *http.Request) {
|
|||||||
dashboards = append(dashboards, installedIntegrationDashboards...)
|
dashboards = append(dashboards, installedIntegrationDashboards...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudIntegrationDashboards, apiErr := aH.CloudIntegrationsController.AvailableDashboards(ctx, orgID)
|
cloudIntegrationDashboards, err := aH.Signoz.Modules.CloudIntegration.ListDashboards(ctx, orgID)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
aH.logger.ErrorContext(ctx, "failed to get dashboards for cloud integrations", errors.Attr(apiErr))
|
if !errors.Ast(err, errorsV2.TypeLicenseUnavailable) {
|
||||||
|
aH.logger.ErrorContext(ctx, "failed to get dashboards for cloud integrations", errors.Attr(err))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dashboards = append(dashboards, cloudIntegrationDashboards...)
|
dashboards = append(dashboards, cloudIntegrationDashboards...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, nil).Wrap)
|
r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, s.signoz.Auditor).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)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ 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"
|
||||||
@@ -21,6 +22,7 @@ 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"
|
||||||
@@ -123,6 +125,12 @@ 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) {
|
||||||
@@ -153,6 +161,8 @@ 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)
|
||||||
@@ -295,7 +305,6 @@ 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) {
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ 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)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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"
|
||||||
@@ -30,7 +31,6 @@ 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,6 +71,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +94,8 @@ 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)
|
||||||
@@ -117,7 +120,8 @@ 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: implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), authz, cache, analytics, providerSettings, config.ServiceAccount),
|
ServiceAccount: serviceAccount,
|
||||||
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),
|
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),
|
||||||
|
CloudIntegration: cloudIntegrationModule,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ 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"
|
||||||
@@ -51,7 +54,9 @@ func TestNewModules(t *testing.T) {
|
|||||||
|
|
||||||
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)
|
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, 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++ {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ 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"
|
||||||
@@ -312,6 +314,12 @@ 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),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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"
|
||||||
@@ -17,12 +18,17 @@ 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"
|
||||||
@@ -42,6 +48,7 @@ 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"
|
||||||
@@ -75,6 +82,7 @@ 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(
|
||||||
@@ -94,7 +102,9 @@ 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")
|
||||||
@@ -371,6 +381,12 @@ 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)
|
||||||
@@ -417,11 +433,19 @@ 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)
|
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule)
|
||||||
|
|
||||||
// Initialize identN resolver
|
// Initialize identN resolver
|
||||||
identNFactories := NewIdentNProviderFactories(tokenizer, modules.ServiceAccount, orgGetter, userGetter, config.User)
|
identNFactories := NewIdentNProviderFactories(tokenizer, 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
|
||||||
@@ -443,7 +467,8 @@ func New(
|
|||||||
tokenizer,
|
tokenizer,
|
||||||
config,
|
config,
|
||||||
modules.AuthDomain,
|
modules.AuthDomain,
|
||||||
modules.ServiceAccount,
|
serviceAccount,
|
||||||
|
cloudIntegrationModule,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize stats reporter from the available stats reporter provider factories
|
// Initialize stats reporter from the available stats reporter provider factories
|
||||||
@@ -470,6 +495,7 @@ 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
|
||||||
@@ -516,5 +542,6 @@ func New(
|
|||||||
QueryParser: queryParser,
|
QueryParser: queryParser,
|
||||||
Flagger: flagger,
|
Flagger: flagger,
|
||||||
Gateway: gateway,
|
Gateway: gateway,
|
||||||
|
Auditor: auditor,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ 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"
|
||||||
@@ -25,7 +23,6 @@ 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] {
|
||||||
@@ -62,19 +59,13 @@ 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()),
|
||||||
done: done,
|
}, nil
|
||||||
}
|
|
||||||
go p.walDiagnosticLoop(config.Sqlite.Path)
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *provider) BunDB() *bun.DB {
|
func (provider *provider) BunDB() *bun.DB {
|
||||||
@@ -118,73 +109,3 @@ 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()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,10 +148,85 @@ func NewAccountsFromStorables(storableAccounts []*StorableCloudIntegration) ([]*
|
|||||||
return accounts, nil
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (account *Account) Update(config *AccountConfig) error {
|
func NewGettableAccountWithConnectionArtifact(account *Account, connectionArtifact *ConnectionArtifact) *GettableAccountWithConnectionArtifact {
|
||||||
if account.RemovedAt != nil {
|
return &GettableAccountWithConnectionArtifact{
|
||||||
return errors.New(errors.TypeUnsupported, ErrCodeCloudIntegrationRemoved, "this operation is not supported for a removed cloud integration account")
|
ID: account.ID,
|
||||||
|
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
|
||||||
@@ -159,47 +236,56 @@ func (account *Account) IsRemoved() bool {
|
|||||||
return account.RemovedAt != nil
|
return account.RemovedAt != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccountConfigFromPostable(provider CloudProviderType, config *PostableAccountConfig) (*AccountConfig, error) {
|
func (postableAccount *PostableAccount) UnmarshalJSON(data []byte) error {
|
||||||
switch provider {
|
type Alias PostableAccount
|
||||||
case CloudProviderTypeAWS:
|
|
||||||
if config.Aws == nil {
|
var temp Alias
|
||||||
return nil, errors.NewInternalf(errors.CodeInternal, "AWS config is nil")
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
}
|
return err
|
||||||
return &AccountConfig{
|
|
||||||
AWS: &AWSAccountConfig{
|
|
||||||
Regions: config.Aws.Regions,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
|
if temp.Config == nil || temp.Credentials == nil {
|
||||||
|
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 NewAccountFromPostableAccount(provider CloudProviderType, account *PostableAccount) (*Account, error) {
|
func (updatableAccount *UpdatableAccount) UnmarshalJSON(data []byte) error {
|
||||||
// req := &Account{
|
type Alias UpdatableAccount
|
||||||
// Credentials: account.Credentials,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// switch provider {
|
var temp Alias
|
||||||
// case CloudProviderTypeAWS:
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
// req.Config = &ConnectionArtifactRequestConfig{
|
return err
|
||||||
// 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
|
||||||
@@ -212,104 +298,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cloudintegrationtypes
|
package cloudintegrationtypes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
@@ -75,11 +76,17 @@ func NewGettableAgentCheckIn(provider CloudProviderType, resp *AgentCheckInRespo
|
|||||||
return gettable
|
return gettable
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks that the request uses either old fields (account_id, cloud_account_id) or
|
func (postable *PostableAgentCheckIn) UnmarshalJSON(data []byte) error {
|
||||||
// new fields (cloudIntegrationId, providerAccountId), never a mix of both.
|
type Alias PostableAgentCheckIn
|
||||||
func (req *PostableAgentCheckIn) Validate() error {
|
|
||||||
hasOldFields := req.ID != "" || req.AccountID != ""
|
var temp Alias
|
||||||
hasNewFields := !req.CloudIntegrationID.IsZero() || req.ProviderAccountID != ""
|
err := json.Unmarshal(data, &temp)
|
||||||
|
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,
|
||||||
@@ -89,5 +96,7 @@ func (req *PostableAgentCheckIn) Validate() 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,30 +153,42 @@ 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 {
|
func newStorableServiceConfig(provider CloudProviderType, serviceID ServiceID, serviceConfig *ServiceConfig, supportedSignals *SupportedSignals) (*StorableServiceConfig, error) {
|
||||||
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}
|
return &StorableServiceConfig{AWS: storableAWSServiceConfig}, nil
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,3 +98,11 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cloudintegrationtypes
|
package cloudintegrationtypes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -62,6 +63,10 @@ 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 {
|
||||||
@@ -69,6 +74,10 @@ 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"`
|
||||||
}
|
}
|
||||||
@@ -229,6 +238,12 @@ 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 {
|
||||||
@@ -259,9 +274,27 @@ func NewServiceConfigFromJSON(provider CloudProviderType, jsonString string) (*S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update sets the service config.
|
// Update sets the service config.
|
||||||
func (service *CloudIntegrationService) Update(config *ServiceConfig) {
|
func (service *CloudIntegrationService) Update(provider CloudProviderType, serviceID ServiceID, config *ServiceConfig) error {
|
||||||
|
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
|
||||||
@@ -299,29 +332,32 @@ 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 := newStorableServiceConfig(provider, serviceID, config, supportedSignals)
|
storableServiceConfig, err := newStorableServiceConfig(provider, serviceID, config, supportedSignals)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return storableServiceConfig.toJSON(provider)
|
return storableServiceConfig.toJSON(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (updatableService *UpdatableService) Validate(provider CloudProviderType, serviceID ServiceID) error {
|
func (updatableService *UpdatableService) UnmarshalJSON(data []byte) error {
|
||||||
switch provider {
|
type Alias UpdatableService
|
||||||
case CloudProviderTypeAWS:
|
|
||||||
if updatableService.Config.AWS == nil {
|
|
||||||
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS config is required for AWS service")
|
|
||||||
}
|
|
||||||
|
|
||||||
if serviceID == AWSServiceS3Sync {
|
var temp Alias
|
||||||
if updatableService.Config.AWS.Logs == nil || updatableService.Config.AWS.Logs.S3Buckets == nil {
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "AWS S3 Sync service requires S3 bucket configuration for logs")
|
return err
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,3 +59,53 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ logger = setup_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def create_cloud_integration_account(
|
def deprecated_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,3 +78,78 @@ def 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)
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
"""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
|
||||||
@@ -8,7 +17,7 @@ from fixtures.logger import setup_logger
|
|||||||
logger = setup_logger(__name__)
|
logger = setup_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def simulate_agent_checkin(
|
def deprecated_simulate_agent_checkin(
|
||||||
signoz: types.SigNoz,
|
signoz: types.SigNoz,
|
||||||
admin_token: str,
|
admin_token: str,
|
||||||
cloud_provider: str,
|
cloud_provider: str,
|
||||||
@@ -38,3 +47,108 @@ def 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
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ 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
|
||||||
|
|||||||
@@ -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 simulate_agent_checkin
|
from fixtures.cloudintegrationsutils import deprecated_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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account1 = deprecated_create_cloud_integration_account(admin_token, cloud_provider)
|
||||||
account1_id = account1["account_id"]
|
account1_id = account1["account_id"]
|
||||||
|
|
||||||
account2 = create_cloud_integration_account(admin_token, cloud_provider)
|
account2 = deprecated_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 = simulate_agent_checkin(
|
response = deprecated_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 = simulate_agent_checkin(
|
response = deprecated_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
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -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 simulate_agent_checkin
|
from fixtures.cloudintegrationsutils import deprecated_simulate_agent_checkin
|
||||||
from fixtures.logger import setup_logger
|
from fixtures.logger import setup_logger
|
||||||
|
|
||||||
logger = setup_logger(__name__)
|
logger = setup_logger(__name__)
|
||||||
@@ -45,19 +45,21 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -93,13 +95,15 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
admin_token, cloud_provider
|
||||||
|
)
|
||||||
account_id = account_data["account_id"]
|
account_id = account_data["account_id"]
|
||||||
|
|
||||||
# Get account status
|
# Get account status
|
||||||
@@ -152,19 +156,21 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -220,19 +226,21 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -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 simulate_agent_checkin
|
from fixtures.cloudintegrationsutils import deprecated_simulate_agent_checkin
|
||||||
from fixtures.logger import setup_logger
|
from fixtures.logger import setup_logger
|
||||||
|
|
||||||
logger = setup_logger(__name__)
|
logger = setup_logger(__name__)
|
||||||
@@ -50,18 +50,20 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -144,18 +146,20 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -248,18 +252,20 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -363,18 +369,20 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
@@ -410,18 +418,20 @@ 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],
|
||||||
create_cloud_integration_account: Callable,
|
deprecated_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 = create_cloud_integration_account(admin_token, cloud_provider)
|
account_data = deprecated_create_cloud_integration_account(
|
||||||
|
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 = simulate_agent_checkin(
|
response = deprecated_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 (
|
||||||
164
tests/integration/src/cloudintegrations/05_credentials.py
Normal file
164
tests/integration/src/cloudintegrations/05_credentials.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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"
|
||||||
92
tests/integration/src/cloudintegrations/06_create_account.py
Normal file
92
tests/integration/src/cloudintegrations/06_create_account.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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"
|
||||||
129
tests/integration/src/cloudintegrations/07_agent_check_in.py
Normal file
129
tests/integration/src/cloudintegrations/07_agent_check_in.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
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}"
|
||||||
341
tests/integration/src/cloudintegrations/08_accounts.py
Normal file
341
tests/integration/src/cloudintegrations/08_accounts.py
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
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}"
|
||||||
445
tests/integration/src/cloudintegrations/09_services.py
Normal file
445
tests/integration/src/cloudintegrations/09_services.py
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
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}"
|
||||||
Reference in New Issue
Block a user