mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-22 00:23:35 +00:00
Compare commits
39 Commits
fix/remove
...
test-gener
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d53ee5053 | ||
|
|
c62b4d9141 | ||
|
|
5720fcb654 | ||
|
|
d32911b0fd | ||
|
|
22fcb7e9fb | ||
|
|
e8d009d225 | ||
|
|
25b143d21a | ||
|
|
4487050375 | ||
|
|
f3732611ca | ||
|
|
989ca522f8 | ||
|
|
9a2e9d76b5 | ||
|
|
2be42deecd | ||
|
|
95cad880cc | ||
|
|
cfef1091b3 | ||
|
|
4504c364f2 | ||
|
|
1a006870e1 | ||
|
|
e7a27a1cfb | ||
|
|
1e7323ead2 | ||
|
|
af4c6c5b52 | ||
|
|
02262ba245 | ||
|
|
df7c9e1339 | ||
|
|
ac5e52479f | ||
|
|
de56477bbb | ||
|
|
fddd8a27fa | ||
|
|
2aa4f8e237 | ||
|
|
74006a214b | ||
|
|
ed2cbacadc | ||
|
|
3cbd529843 | ||
|
|
78b481e895 | ||
|
|
215098ec0d | ||
|
|
5a4ef2e4ce | ||
|
|
b1f33c4f7f | ||
|
|
713c84b1e4 | ||
|
|
c3daf9e428 | ||
|
|
70a908deb1 | ||
|
|
cc9cdded3c | ||
|
|
77067cd614 | ||
|
|
ab703d9a65 | ||
|
|
611e8fbf9e |
@@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,7 +14,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||||
querierAPI "github.com/SigNoz/signoz/pkg/querier"
|
querierAPI "github.com/SigNoz/signoz/pkg/querier"
|
||||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||||
@@ -30,13 +30,13 @@ type APIHandlerOptions struct {
|
|||||||
RulesManager *rules.Manager
|
RulesManager *rules.Manager
|
||||||
UsageManager *usage.Manager
|
UsageManager *usage.Manager
|
||||||
IntegrationsController *integrations.Controller
|
IntegrationsController *integrations.Controller
|
||||||
CloudIntegrationsController *cloudintegrations.Controller
|
|
||||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||||
Gateway *httputil.ReverseProxy
|
Gateway *httputil.ReverseProxy
|
||||||
GatewayUrl string
|
GatewayUrl string
|
||||||
// Querier Influx Interval
|
// Querier Influx Interval
|
||||||
FluxInterval time.Duration
|
FluxInterval time.Duration
|
||||||
GlobalConfig global.Config
|
GlobalConfig global.Config
|
||||||
|
Logger *slog.Logger // this is present in Signoz.Instrumentation but adding for quick access
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIHandler struct {
|
type APIHandler struct {
|
||||||
@@ -50,7 +50,6 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
|||||||
Reader: opts.DataConnector,
|
Reader: opts.DataConnector,
|
||||||
RuleManager: opts.RulesManager,
|
RuleManager: opts.RulesManager,
|
||||||
IntegrationsController: opts.IntegrationsController,
|
IntegrationsController: opts.IntegrationsController,
|
||||||
CloudIntegrationsController: opts.CloudIntegrationsController,
|
|
||||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||||
FluxInterval: opts.FluxInterval,
|
FluxInterval: opts.FluxInterval,
|
||||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||||
@@ -58,6 +57,7 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
|||||||
Signoz: signoz,
|
Signoz: signoz,
|
||||||
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
|
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
|
||||||
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
|
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
|
||||||
|
Logger: opts.Logger,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,14 +118,12 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||||
|
|
||||||
ah.APIHandler.RegisterCloudIntegrationsRoutes(router, am)
|
ah.APIHandler.RegisterCloudIntegrationsRoutes(router, am)
|
||||||
|
|
||||||
router.HandleFunc(
|
router.HandleFunc(
|
||||||
"/api/v1/cloud-integrations/{cloudProvider}/accounts/generate-connection-params",
|
"/api/v1/cloud-integrations/{cloudProvider}/accounts/generate-connection-params",
|
||||||
am.EditAccess(ah.CloudIntegrationsGenerateConnectionParams),
|
am.EditAccess(ah.CloudIntegrationsGenerateConnectionParams),
|
||||||
).Methods(http.MethodGet)
|
).Methods(http.MethodGet)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,20 +14,14 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"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/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CloudIntegrationConnectionParamsResponse struct {
|
// TODO: move this file with other cloud integration related code
|
||||||
IngestionUrl string `json:"ingestion_url,omitempty"`
|
|
||||||
IngestionKey string `json:"ingestion_key,omitempty"`
|
|
||||||
SigNozAPIUrl string `json:"signoz_api_url,omitempty"`
|
|
||||||
SigNozAPIKey string `json:"signoz_api_key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseWriter, r *http.Request) {
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
@@ -41,23 +36,21 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
if cloudProvider != "aws" {
|
|
||||||
RespondError(w, basemodel.BadRequest(fmt.Errorf(
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
"cloud provider not supported: %s", cloudProvider,
|
if err != nil {
|
||||||
)), nil)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), claims.OrgID, cloudProvider)
|
apiKey, err := ah.getOrCreateCloudIntegrationPAT(r.Context(), claims.OrgID, cloudProvider)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
RespondError(w, basemodel.WrapApiError(
|
render.Error(w, err)
|
||||||
apiErr, "couldn't provision PAT for cloud integration:",
|
|
||||||
), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result := CloudIntegrationConnectionParamsResponse{
|
result := integrationstypes.GettableCloudIntegrationConnectionParams{
|
||||||
SigNozAPIKey: apiKey,
|
SigNozAPIKey: apiKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,16 +64,17 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
// Return the API Key (PAT) even if the rest of the params can not be deduced.
|
// Return the API Key (PAT) even if the rest of the params can not be deduced.
|
||||||
// Params not returned from here will be requested from the user via form inputs.
|
// Params not returned from here will be requested from the user via form inputs.
|
||||||
// This enables gracefully degraded but working experience even for non-cloud deployments.
|
// This enables gracefully degraded but working experience even for non-cloud deployments.
|
||||||
zap.L().Info("ingestion params and signoz api url can not be deduced since no license was found")
|
ah.opts.Logger.InfoContext(
|
||||||
ah.Respond(w, result)
|
r.Context(),
|
||||||
|
"ingestion params and signoz api url can not be deduced since no license was found",
|
||||||
|
)
|
||||||
|
render.Success(w, http.StatusOK, result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
signozApiUrl, apiErr := ah.getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
|
signozApiUrl, err := ah.getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
RespondError(w, basemodel.WrapApiError(
|
render.Error(w, err)
|
||||||
apiErr, "couldn't deduce ingestion url and signoz api url",
|
|
||||||
), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,48 +83,41 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
|
|
||||||
gatewayUrl := ah.opts.GatewayUrl
|
gatewayUrl := ah.opts.GatewayUrl
|
||||||
if len(gatewayUrl) > 0 {
|
if len(gatewayUrl) > 0 {
|
||||||
|
ingestionKeyString, err := ah.getOrCreateCloudProviderIngestionKey(
|
||||||
ingestionKey, apiErr := getOrCreateCloudProviderIngestionKey(
|
|
||||||
r.Context(), gatewayUrl, license.Key, cloudProvider,
|
r.Context(), gatewayUrl, license.Key, cloudProvider,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
RespondError(w, basemodel.WrapApiError(
|
render.Error(w, err)
|
||||||
apiErr, "couldn't get or create ingestion key",
|
|
||||||
), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result.IngestionKey = ingestionKey
|
result.IngestionKey = ingestionKeyString
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
zap.L().Info("ingestion key can't be deduced since no gateway url has been configured")
|
ah.opts.Logger.InfoContext(
|
||||||
|
r.Context(),
|
||||||
|
"ingestion key can't be deduced since no gateway url has been configured",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ah.Respond(w, result)
|
render.Success(w, http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
|
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider valuer.String) (string, error) {
|
||||||
string, *basemodel.ApiError,
|
|
||||||
) {
|
|
||||||
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
|
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
|
||||||
|
|
||||||
integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
|
integrationUser, err := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
return "", apiErr
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
orgIdUUID, err := valuer.NewUUID(orgId)
|
orgIdUUID, err := valuer.NewUUID(orgId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", err
|
||||||
"couldn't parse orgId: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allPats, err := ah.Signoz.Modules.User.ListAPIKeys(ctx, orgIdUUID)
|
allPats, err := ah.Signoz.Modules.User.ListAPIKeys(ctx, orgIdUUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", err
|
||||||
"couldn't list PATs: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
for _, p := range allPats {
|
for _, p := range allPats {
|
||||||
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
|
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
|
||||||
@@ -138,9 +125,10 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.L().Info(
|
ah.opts.Logger.InfoContext(
|
||||||
|
ctx,
|
||||||
"no PAT found for cloud integration, creating a new one",
|
"no PAT found for cloud integration, creating a new one",
|
||||||
zap.String("cloudProvider", cloudProvider),
|
slog.String("cloudProvider", cloudProvider.String()),
|
||||||
)
|
)
|
||||||
|
|
||||||
newPAT, err := types.NewStorableAPIKey(
|
newPAT, err := types.NewStorableAPIKey(
|
||||||
@@ -150,68 +138,48 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
|||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", err
|
||||||
"couldn't create cloud integration PAT: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ah.Signoz.Modules.User.CreateAPIKey(ctx, newPAT)
|
err = ah.Signoz.Modules.User.CreateAPIKey(ctx, newPAT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", err
|
||||||
"couldn't create cloud integration PAT: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
return newPAT.Token, nil
|
return newPAT.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
// TODO: move this function out of handler and use proper module structure
|
||||||
ctx context.Context, orgId string, cloudProvider string,
|
func (ah *APIHandler) getOrCreateCloudIntegrationUser(ctx context.Context, orgId string, cloudProvider valuer.String) (*types.User, error) {
|
||||||
) (*types.User, *basemodel.ApiError) {
|
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider.String())
|
||||||
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
|
|
||||||
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
|
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
|
||||||
|
|
||||||
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId))
|
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
|
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
|
||||||
|
|
||||||
cloudIntegrationUser, err = ah.Signoz.Modules.User.GetOrCreateUser(ctx, cloudIntegrationUser, user.WithFactorPassword(password))
|
cloudIntegrationUser, err = ah.Signoz.Modules.User.GetOrCreateUser(ctx, cloudIntegrationUser, user.WithFactorPassword(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't look for integration user: %w", err))
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloudIntegrationUser, nil
|
return cloudIntegrationUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
|
// TODO: move this function out of handler and use proper module structure
|
||||||
string, *basemodel.ApiError,
|
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (string, error) {
|
||||||
) {
|
|
||||||
// TODO: remove this struct from here
|
|
||||||
type deploymentResponse struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
ClusterInfo struct {
|
|
||||||
Region struct {
|
|
||||||
DNS string `json:"dns"`
|
|
||||||
} `json:"region"`
|
|
||||||
} `json:"cluster"`
|
|
||||||
}
|
|
||||||
|
|
||||||
respBytes, err := ah.Signoz.Zeus.GetDeployment(ctx, licenseKey)
|
respBytes, err := ah.Signoz.Zeus.GetDeployment(ctx, licenseKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't query for deployment info: error")
|
||||||
"couldn't query for deployment info: error: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := new(deploymentResponse)
|
resp := new(integrationstypes.GettableDeployment)
|
||||||
|
|
||||||
err = json.Unmarshal(respBytes, resp)
|
err = json.Unmarshal(respBytes, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't unmarshal deployment info response")
|
||||||
"couldn't unmarshal deployment info response: error: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
regionDns := resp.ClusterInfo.Region.DNS
|
regionDns := resp.ClusterInfo.Region.DNS
|
||||||
@@ -219,9 +187,11 @@ func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licens
|
|||||||
|
|
||||||
if len(regionDns) < 1 || len(deploymentName) < 1 {
|
if len(regionDns) < 1 || len(deploymentName) < 1 {
|
||||||
// Fail early if actual response structure and expectation here ever diverge
|
// Fail early if actual response structure and expectation here ever diverge
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", errors.WrapInternalf(
|
||||||
|
err,
|
||||||
|
errors.CodeInternal,
|
||||||
"deployment info response not in expected shape. couldn't determine region dns and deployment name",
|
"deployment info response not in expected shape. couldn't determine region dns and deployment name",
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
signozApiUrl := fmt.Sprintf("https://%s.%s", deploymentName, regionDns)
|
signozApiUrl := fmt.Sprintf("https://%s.%s", deploymentName, regionDns)
|
||||||
@@ -229,102 +199,85 @@ func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licens
|
|||||||
return signozApiUrl, nil
|
return signozApiUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ingestionKey struct {
|
func (ah *APIHandler) getOrCreateCloudProviderIngestionKey(
|
||||||
Name string `json:"name"`
|
ctx context.Context, gatewayUrl string, licenseKey string, cloudProvider valuer.String,
|
||||||
Value string `json:"value"`
|
) (string, error) {
|
||||||
// other attributes from gateway response not included here since they are not being used.
|
|
||||||
}
|
|
||||||
|
|
||||||
type ingestionKeysSearchResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data []ingestionKey `json:"data"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type createIngestionKeyResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data ingestionKey `json:"data"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrCreateCloudProviderIngestionKey(
|
|
||||||
ctx context.Context, gatewayUrl string, licenseKey string, cloudProvider string,
|
|
||||||
) (string, *basemodel.ApiError) {
|
|
||||||
cloudProviderKeyName := fmt.Sprintf("%s-integration", cloudProvider)
|
cloudProviderKeyName := fmt.Sprintf("%s-integration", cloudProvider)
|
||||||
|
|
||||||
// see if the key already exists
|
// see if the key already exists
|
||||||
searchResult, apiErr := requestGateway[ingestionKeysSearchResponse](
|
searchResult, err := requestGateway[integrationstypes.GettableIngestionKeysSearch](
|
||||||
ctx,
|
ctx,
|
||||||
gatewayUrl,
|
gatewayUrl,
|
||||||
licenseKey,
|
licenseKey,
|
||||||
fmt.Sprintf("/v1/workspaces/me/keys/search?name=%s", cloudProviderKeyName),
|
fmt.Sprintf("/v1/workspaces/me/keys/search?name=%s", cloudProviderKeyName),
|
||||||
nil,
|
nil,
|
||||||
|
ah.opts.Logger,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
if apiErr != nil {
|
return "", err
|
||||||
return "", basemodel.WrapApiError(
|
|
||||||
apiErr, "couldn't search for cloudprovider ingestion key",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if searchResult.Status != "success" {
|
if searchResult.Status != "success" {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", errors.NewInternalf(
|
||||||
"couldn't search for cloudprovider ingestion key: status: %s, error: %s",
|
errors.CodeInternal,
|
||||||
|
"couldn't search for cloud provider ingestion key: status: %s, error: %s",
|
||||||
searchResult.Status, searchResult.Error,
|
searchResult.Status, searchResult.Error,
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range searchResult.Data {
|
|
||||||
if k.Name == cloudProviderKeyName {
|
|
||||||
if len(k.Value) < 1 {
|
|
||||||
// Fail early if actual response structure and expectation here ever diverge
|
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
|
||||||
"ingestion keys search response not as expected",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return k.Value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zap.L().Info(
|
|
||||||
"no existing ingestion key found for cloud integration, creating a new one",
|
|
||||||
zap.String("cloudProvider", cloudProvider),
|
|
||||||
)
|
|
||||||
createKeyResult, apiErr := requestGateway[createIngestionKeyResponse](
|
|
||||||
ctx, gatewayUrl, licenseKey, "/v1/workspaces/me/keys",
|
|
||||||
map[string]any{
|
|
||||||
"name": cloudProviderKeyName,
|
|
||||||
"tags": []string{"integration", cloudProvider},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return "", basemodel.WrapApiError(
|
|
||||||
apiErr, "couldn't create cloudprovider ingestion key",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, k := range searchResult.Data {
|
||||||
|
if k.Name != cloudProviderKeyName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k.Value) < 1 {
|
||||||
|
// Fail early if actual response structure and expectation here ever diverge
|
||||||
|
return "", errors.NewInternalf(errors.CodeInternal, "ingestion keys search response not as expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
return k.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.opts.Logger.InfoContext(
|
||||||
|
ctx,
|
||||||
|
"no existing ingestion key found for cloud integration, creating a new one",
|
||||||
|
slog.String("cloudProvider", cloudProvider.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
createKeyResult, err := requestGateway[integrationstypes.GettableCreateIngestionKey](
|
||||||
|
ctx, gatewayUrl, licenseKey, "/v1/workspaces/me/keys",
|
||||||
|
map[string]any{
|
||||||
|
"name": cloudProviderKeyName,
|
||||||
|
"tags": []string{"integration", cloudProvider.String()},
|
||||||
|
},
|
||||||
|
ah.opts.Logger,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
if createKeyResult.Status != "success" {
|
if createKeyResult.Status != "success" {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", errors.NewInternalf(
|
||||||
"couldn't create cloudprovider ingestion key: status: %s, error: %s",
|
errors.CodeInternal,
|
||||||
|
"couldn't create cloud provider ingestion key: status: %s, error: %s",
|
||||||
createKeyResult.Status, createKeyResult.Error,
|
createKeyResult.Status, createKeyResult.Error,
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ingestionKey := createKeyResult.Data.Value
|
ingestionKeyString := createKeyResult.Data.Value
|
||||||
if len(ingestionKey) < 1 {
|
if len(ingestionKeyString) < 1 {
|
||||||
// Fail early if actual response structure and expectation here ever diverge
|
// Fail early if actual response structure and expectation here ever diverge
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", errors.NewInternalf(errors.CodeInternal,
|
||||||
"ingestion key creation response not as expected",
|
"ingestion key creation response not as expected",
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ingestionKey, nil
|
return ingestionKeyString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestGateway[ResponseType any](
|
func requestGateway[ResponseType any](
|
||||||
ctx context.Context, gatewayUrl string, licenseKey string, path string, payload any,
|
ctx context.Context, gatewayUrl, licenseKey, path string, payload any, logger *slog.Logger,
|
||||||
) (*ResponseType, *basemodel.ApiError) {
|
) (*ResponseType, error) {
|
||||||
|
|
||||||
baseUrl := strings.TrimSuffix(gatewayUrl, "/")
|
baseUrl := strings.TrimSuffix(gatewayUrl, "/")
|
||||||
reqUrl := fmt.Sprintf("%s%s", baseUrl, path)
|
reqUrl := fmt.Sprintf("%s%s", baseUrl, path)
|
||||||
@@ -335,13 +288,12 @@ func requestGateway[ResponseType any](
|
|||||||
"X-Consumer-Groups": "ns:default",
|
"X-Consumer-Groups": "ns:default",
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestAndParseResponse[ResponseType](ctx, reqUrl, headers, payload)
|
return requestAndParseResponse[ResponseType](ctx, reqUrl, headers, payload, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestAndParseResponse[ResponseType any](
|
func requestAndParseResponse[ResponseType any](
|
||||||
ctx context.Context, url string, headers map[string]string, payload any,
|
ctx context.Context, url string, headers map[string]string, payload any, logger *slog.Logger,
|
||||||
) (*ResponseType, *basemodel.ApiError) {
|
) (*ResponseType, error) {
|
||||||
|
|
||||||
reqMethod := http.MethodGet
|
reqMethod := http.MethodGet
|
||||||
var reqBody io.Reader
|
var reqBody io.Reader
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
@@ -349,18 +301,14 @@ func requestAndParseResponse[ResponseType any](
|
|||||||
|
|
||||||
bodyJson, err := json.Marshal(payload)
|
bodyJson, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't marshal payload")
|
||||||
"couldn't serialize request payload to JSON: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
reqBody = bytes.NewBuffer([]byte(bodyJson))
|
reqBody = bytes.NewBuffer(bodyJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, reqMethod, url, reqBody)
|
req, err := http.NewRequestWithContext(ctx, reqMethod, url, reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't create req")
|
||||||
"couldn't prepare request: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
@@ -373,23 +321,26 @@ func requestAndParseResponse[ResponseType any](
|
|||||||
|
|
||||||
response, err := client.Do(req)
|
response, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't make request: %w", err))
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't make req")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer response.Body.Close()
|
defer func() {
|
||||||
|
err = response.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorContext(ctx, "couldn't close response body", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
respBody, err := io.ReadAll(response.Body)
|
respBody, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't read response: %w", err))
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read response body")
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp ResponseType
|
var resp ResponseType
|
||||||
|
|
||||||
err = json.Unmarshal(respBody, &resp)
|
err = json.Unmarshal(respBody, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't unmarshal response body")
|
||||||
"couldn't unmarshal gateway response into %T", resp,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
||||||
@@ -127,13 +126,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"couldn't create cloud provider integrations controller: %w", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ingestion pipelines manager
|
// ingestion pipelines manager
|
||||||
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
|
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
|
||||||
signoz.SQLStore,
|
signoz.SQLStore,
|
||||||
@@ -167,12 +159,12 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
|||||||
RulesManager: rm,
|
RulesManager: rm,
|
||||||
UsageManager: usageManager,
|
UsageManager: usageManager,
|
||||||
IntegrationsController: integrationsController,
|
IntegrationsController: integrationsController,
|
||||||
CloudIntegrationsController: cloudIntegrationsController,
|
|
||||||
LogsParsingPipelineController: logParsingPipelineController,
|
LogsParsingPipelineController: logParsingPipelineController,
|
||||||
FluxInterval: config.Querier.FluxInterval,
|
FluxInterval: config.Querier.FluxInterval,
|
||||||
Gateway: gatewayProxy,
|
Gateway: gatewayProxy,
|
||||||
GatewayUrl: config.Gateway.URL.String(),
|
GatewayUrl: config.Gateway.URL.String(),
|
||||||
GlobalConfig: config.Global,
|
GlobalConfig: config.Global,
|
||||||
|
Logger: signoz.Instrumentation.Logger(),
|
||||||
}
|
}
|
||||||
|
|
||||||
apiHandler, err := api.NewAPIHandler(apiOpts, signoz)
|
apiHandler, err := api.NewAPIHandler(apiOpts, signoz)
|
||||||
|
|||||||
44
pkg/http/middleware/recovery.go
Normal file
44
pkg/http/middleware/recovery.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recovery is a middleware that recovers from panics, logs the panic,
|
||||||
|
// and returns a 500 Internal Server Error.
|
||||||
|
type Recovery struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecovery creates a new Recovery middleware.
|
||||||
|
func NewRecovery(logger *slog.Logger) Wrapper {
|
||||||
|
return &Recovery{
|
||||||
|
logger: logger.With("pkg", "http-middleware-recovery"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap is the middleware handler.
|
||||||
|
func (m *Recovery) Wrap(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
m.logger.ErrorContext(
|
||||||
|
r.Context(),
|
||||||
|
"panic recovered",
|
||||||
|
"err", err, "stack", string(debug.Stack()),
|
||||||
|
)
|
||||||
|
|
||||||
|
render.Error(w, errors.NewInternalf(
|
||||||
|
errors.CodeInternal, "internal server error",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
root "github.com/SigNoz/signoz/pkg/modules/user"
|
root "github.com/SigNoz/signoz/pkg/modules/user"
|
||||||
"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/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@@ -462,7 +463,7 @@ func (h *handler) UpdateAPIKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(createdByUser.Email.String())) {
|
if slices.Contains(integrationstypes.IntegrationUserEmails, createdByUser.Email) {
|
||||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "API Keys for integration users cannot be revoked"))
|
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "API Keys for integration users cannot be revoked"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -507,7 +508,7 @@ func (h *handler) RevokeAPIKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(createdByUser.Email.String())) {
|
if slices.Contains(integrationstypes.IntegrationUserEmails, createdByUser.Email) {
|
||||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "API Keys for integration users cannot be revoked"))
|
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "API Keys for integration users cannot be revoked"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"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/emailtypes"
|
"github.com/SigNoz/signoz/pkg/types/emailtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
@@ -171,7 +172,7 @@ func (m *Module) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID)
|
|||||||
func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
||||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||||
|
|
||||||
// since assign is idempotant multiple calls to assign won't cause issues in case of retries.
|
// since assign is idempotent multiple calls to assign won't cause issues in case of retries.
|
||||||
err := module.authz.Grant(ctx, input.OrgID, roletypes.MustGetSigNozManagedRoleFromExistingRole(input.Role), authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.StringValue(), input.OrgID, nil))
|
err := module.authz.Grant(ctx, input.OrgID, roletypes.MustGetSigNozManagedRoleFromExistingRole(input.Role), authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.StringValue(), input.OrgID, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -286,7 +287,7 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(user.Email.String())) {
|
if slices.Contains(integrationstypes.IntegrationUserEmails, user.Email) {
|
||||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "integration user cannot be deleted")
|
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "integration user cannot be deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +301,7 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
|||||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
|
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
// since revoke is idempotant multiple calls to revoke won't cause issues in case of retries
|
// since revoke is idempotent multiple calls to revoke won't cause issues in case of retries
|
||||||
err = module.authz.Revoke(ctx, orgID, roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role), authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
|
err = module.authz.Revoke(ctx, orgID, roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role), authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,624 +0,0 @@
|
|||||||
package cloudintegrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
var SupportedCloudProviders = []string{
|
|
||||||
"aws",
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateCloudProviderName(name string) *model.ApiError {
|
|
||||||
if !slices.Contains(SupportedCloudProviders, name) {
|
|
||||||
return model.BadRequest(fmt.Errorf("invalid cloud provider: %s", name))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
accountsRepo cloudProviderAccountsRepository
|
|
||||||
serviceConfigRepo ServiceConfigDatabase
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController(sqlStore sqlstore.SQLStore) (*Controller, error) {
|
|
||||||
accountsRepo, err := newCloudProviderAccountsRepository(sqlStore)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceConfigRepo, err := newServiceConfigRepository(sqlStore)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't create cloud provider service config repo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Controller{
|
|
||||||
accountsRepo: accountsRepo,
|
|
||||||
serviceConfigRepo: serviceConfigRepo,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectedAccountsListResponse struct {
|
|
||||||
Accounts []types.Account `json:"accounts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) ListConnectedAccounts(ctx context.Context, orgId string, cloudProvider string) (
|
|
||||||
*ConnectedAccountsListResponse, *model.ApiError,
|
|
||||||
) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgId, cloudProvider)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't list cloud accounts")
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedAccounts := []types.Account{}
|
|
||||||
for _, a := range accountRecords {
|
|
||||||
connectedAccounts = append(connectedAccounts, a.Account())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ConnectedAccountsListResponse{
|
|
||||||
Accounts: connectedAccounts,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenerateConnectionUrlRequest struct {
|
|
||||||
// Optional. To be specified for updates.
|
|
||||||
AccountId *string `json:"account_id,omitempty"`
|
|
||||||
|
|
||||||
AccountConfig types.AccountConfig `json:"account_config"`
|
|
||||||
|
|
||||||
AgentConfig SigNozAgentConfig `json:"agent_config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SigNozAgentConfig struct {
|
|
||||||
// The region in which SigNoz agent should be installed.
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
IngestionUrl string `json:"ingestion_url"`
|
|
||||||
IngestionKey string `json:"ingestion_key"`
|
|
||||||
SigNozAPIUrl string `json:"signoz_api_url"`
|
|
||||||
SigNozAPIKey string `json:"signoz_api_key"`
|
|
||||||
|
|
||||||
Version string `json:"version,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenerateConnectionUrlResponse struct {
|
|
||||||
AccountId string `json:"account_id"`
|
|
||||||
ConnectionUrl string `json:"connection_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) GenerateConnectionUrl(ctx context.Context, orgId string, cloudProvider string, req GenerateConnectionUrlRequest) (*GenerateConnectionUrlResponse, *model.ApiError) {
|
|
||||||
// Account connection with a simple connection URL may not be available for all providers.
|
|
||||||
if cloudProvider != "aws" {
|
|
||||||
return nil, model.BadRequest(fmt.Errorf("unsupported cloud provider: %s", cloudProvider))
|
|
||||||
}
|
|
||||||
|
|
||||||
account, apiErr := c.accountsRepo.upsert(
|
|
||||||
ctx, orgId, cloudProvider, req.AccountId, &req.AccountConfig, nil, nil, nil,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
|
|
||||||
}
|
|
||||||
|
|
||||||
agentVersion := "v0.0.8"
|
|
||||||
if req.AgentConfig.Version != "" {
|
|
||||||
agentVersion = req.AgentConfig.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionUrl := fmt.Sprintf(
|
|
||||||
"https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks/quickcreate?",
|
|
||||||
req.AgentConfig.Region, req.AgentConfig.Region,
|
|
||||||
)
|
|
||||||
|
|
||||||
for qp, value := range map[string]string{
|
|
||||||
"param_SigNozIntegrationAgentVersion": agentVersion,
|
|
||||||
"param_SigNozApiUrl": req.AgentConfig.SigNozAPIUrl,
|
|
||||||
"param_SigNozApiKey": req.AgentConfig.SigNozAPIKey,
|
|
||||||
"param_SigNozAccountId": account.ID.StringValue(),
|
|
||||||
"param_IngestionUrl": req.AgentConfig.IngestionUrl,
|
|
||||||
"param_IngestionKey": req.AgentConfig.IngestionKey,
|
|
||||||
"stackName": "signoz-integration",
|
|
||||||
"templateURL": fmt.Sprintf(
|
|
||||||
"https://signoz-integrations.s3.us-east-1.amazonaws.com/aws-quickcreate-template-%s.json",
|
|
||||||
agentVersion,
|
|
||||||
),
|
|
||||||
} {
|
|
||||||
connectionUrl += fmt.Sprintf("&%s=%s", qp, url.QueryEscape(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GenerateConnectionUrlResponse{
|
|
||||||
AccountId: account.ID.StringValue(),
|
|
||||||
ConnectionUrl: connectionUrl,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountStatusResponse struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CloudAccountId *string `json:"cloud_account_id,omitempty"`
|
|
||||||
Status types.AccountStatus `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) GetAccountStatus(ctx context.Context, orgId string, cloudProvider string, accountId string) (
|
|
||||||
*AccountStatusResponse, *model.ApiError,
|
|
||||||
) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
account, apiErr := c.accountsRepo.get(ctx, orgId, cloudProvider, accountId)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := AccountStatusResponse{
|
|
||||||
Id: account.ID.StringValue(),
|
|
||||||
CloudAccountId: account.AccountID,
|
|
||||||
Status: account.Status(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentCheckInRequest struct {
|
|
||||||
ID string `json:"account_id"`
|
|
||||||
AccountID string `json:"cloud_account_id"`
|
|
||||||
// Arbitrary cloud specific Agent data
|
|
||||||
Data map[string]any `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentCheckInResponse struct {
|
|
||||||
AccountId string `json:"account_id"`
|
|
||||||
CloudAccountId string `json:"cloud_account_id"`
|
|
||||||
RemovedAt *time.Time `json:"removed_at"`
|
|
||||||
|
|
||||||
IntegrationConfig IntegrationConfigForAgent `json:"integration_config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntegrationConfigForAgent struct {
|
|
||||||
EnabledRegions []string `json:"enabled_regions"`
|
|
||||||
|
|
||||||
TelemetryCollectionStrategy *CompiledCollectionStrategy `json:"telemetry,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) CheckInAsAgent(ctx context.Context, orgId string, cloudProvider string, req AgentCheckInRequest) (*AgentCheckInResponse, error) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
existingAccount, apiErr := c.accountsRepo.get(ctx, orgId, cloudProvider, req.ID)
|
|
||||||
if existingAccount != nil && existingAccount.AccountID != nil && *existingAccount.AccountID != req.AccountID {
|
|
||||||
return nil, model.BadRequest(fmt.Errorf(
|
|
||||||
"can't check in with new %s account id %s for account %s with existing %s id %s",
|
|
||||||
cloudProvider, req.AccountID, existingAccount.ID.StringValue(), cloudProvider, *existingAccount.AccountID,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
existingAccount, apiErr = c.accountsRepo.getConnectedCloudAccount(ctx, orgId, cloudProvider, req.AccountID)
|
|
||||||
if existingAccount != nil && existingAccount.ID.StringValue() != req.ID {
|
|
||||||
return nil, model.BadRequest(fmt.Errorf(
|
|
||||||
"can't check in to %s account %s with id %s. already connected with id %s",
|
|
||||||
cloudProvider, req.AccountID, req.ID, existingAccount.ID.StringValue(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
agentReport := types.AgentReport{
|
|
||||||
TimestampMillis: time.Now().UnixMilli(),
|
|
||||||
Data: req.Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
account, apiErr := c.accountsRepo.upsert(
|
|
||||||
ctx, orgId, cloudProvider, &req.ID, nil, &req.AccountID, &agentReport, nil,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare and return integration config to be consumed by agent
|
|
||||||
compiledStrategy, err := NewCompiledCollectionStrategy(cloudProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
|
||||||
"couldn't init telemetry collection strategy: %w", err,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
agentConfig := IntegrationConfigForAgent{
|
|
||||||
EnabledRegions: []string{},
|
|
||||||
TelemetryCollectionStrategy: compiledStrategy,
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Config != nil && account.Config.EnabledRegions != nil {
|
|
||||||
agentConfig.EnabledRegions = account.Config.EnabledRegions
|
|
||||||
}
|
|
||||||
|
|
||||||
services, err := services.Map(cloudProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
svcConfigs, apiErr := c.serviceConfigRepo.getAllForAccount(
|
|
||||||
ctx, orgId, account.ID.StringValue(),
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(
|
|
||||||
apiErr, "couldn't get service configs for cloud account",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// accumulate config in a fixed order to ensure same config generated across runs
|
|
||||||
configuredServices := maps.Keys(svcConfigs)
|
|
||||||
slices.Sort(configuredServices)
|
|
||||||
|
|
||||||
for _, svcType := range configuredServices {
|
|
||||||
definition, ok := services[svcType]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
config := svcConfigs[svcType]
|
|
||||||
|
|
||||||
err := AddServiceStrategy(svcType, compiledStrategy, definition.Strategy, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AgentCheckInResponse{
|
|
||||||
AccountId: account.ID.StringValue(),
|
|
||||||
CloudAccountId: *account.AccountID,
|
|
||||||
RemovedAt: account.RemovedAt,
|
|
||||||
IntegrationConfig: agentConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateAccountConfigRequest struct {
|
|
||||||
Config types.AccountConfig `json:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) UpdateAccountConfig(ctx context.Context, orgId string, cloudProvider string, accountId string, req UpdateAccountConfigRequest) (*types.Account, *model.ApiError) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
accountRecord, apiErr := c.accountsRepo.upsert(
|
|
||||||
ctx, orgId, cloudProvider, &accountId, &req.Config, nil, nil, nil,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
|
|
||||||
}
|
|
||||||
|
|
||||||
account := accountRecord.Account()
|
|
||||||
|
|
||||||
return &account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) DisconnectAccount(ctx context.Context, orgId string, cloudProvider string, accountId string) (*types.CloudIntegration, *model.ApiError) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
account, apiErr := c.accountsRepo.get(ctx, orgId, cloudProvider, accountId)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't disconnect account")
|
|
||||||
}
|
|
||||||
|
|
||||||
tsNow := time.Now()
|
|
||||||
account, apiErr = c.accountsRepo.upsert(
|
|
||||||
ctx, orgId, cloudProvider, &accountId, nil, nil, nil, &tsNow,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't disconnect account")
|
|
||||||
}
|
|
||||||
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListServicesResponse struct {
|
|
||||||
Services []ServiceSummary `json:"services"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) ListServices(
|
|
||||||
ctx context.Context,
|
|
||||||
orgID string,
|
|
||||||
cloudProvider string,
|
|
||||||
cloudAccountId *string,
|
|
||||||
) (*ListServicesResponse, *model.ApiError) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
definitions, apiErr := services.List(cloudProvider)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't list cloud services")
|
|
||||||
}
|
|
||||||
|
|
||||||
svcConfigs := map[string]*types.CloudServiceConfig{}
|
|
||||||
if cloudAccountId != nil {
|
|
||||||
activeAccount, apiErr := c.accountsRepo.getConnectedCloudAccount(
|
|
||||||
ctx, orgID, cloudProvider, *cloudAccountId,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't get active account")
|
|
||||||
}
|
|
||||||
svcConfigs, apiErr = c.serviceConfigRepo.getAllForAccount(
|
|
||||||
ctx, orgID, activeAccount.ID.StringValue(),
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(
|
|
||||||
apiErr, "couldn't get service configs for cloud account",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
summaries := []ServiceSummary{}
|
|
||||||
for _, def := range definitions {
|
|
||||||
summary := ServiceSummary{
|
|
||||||
Metadata: def.Metadata,
|
|
||||||
}
|
|
||||||
summary.Config = svcConfigs[summary.Id]
|
|
||||||
|
|
||||||
summaries = append(summaries, summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ListServicesResponse{
|
|
||||||
Services: summaries,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) GetServiceDetails(
|
|
||||||
ctx context.Context,
|
|
||||||
orgID string,
|
|
||||||
cloudProvider string,
|
|
||||||
serviceId string,
|
|
||||||
cloudAccountId *string,
|
|
||||||
) (*ServiceDetails, error) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
definition, err := services.GetServiceDefinition(cloudProvider, serviceId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
details := ServiceDetails{
|
|
||||||
Definition: *definition,
|
|
||||||
}
|
|
||||||
|
|
||||||
if cloudAccountId != nil {
|
|
||||||
|
|
||||||
activeAccount, apiErr := c.accountsRepo.getConnectedCloudAccount(
|
|
||||||
ctx, orgID, cloudProvider, *cloudAccountId,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't get active account")
|
|
||||||
}
|
|
||||||
|
|
||||||
config, apiErr := c.serviceConfigRepo.get(
|
|
||||||
ctx, orgID, activeAccount.ID.StringValue(), serviceId,
|
|
||||||
)
|
|
||||||
if apiErr != nil && apiErr.Type() != model.ErrorNotFound {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't fetch service config")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config != nil {
|
|
||||||
details.Config = config
|
|
||||||
|
|
||||||
enabled := false
|
|
||||||
if config.Metrics != nil && config.Metrics.Enabled {
|
|
||||||
enabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// add links to service dashboards, making them clickable.
|
|
||||||
for i, d := range definition.Assets.Dashboards {
|
|
||||||
dashboardUuid := c.dashboardUuid(
|
|
||||||
cloudProvider, serviceId, d.Id,
|
|
||||||
)
|
|
||||||
if enabled {
|
|
||||||
definition.Assets.Dashboards[i].Url = fmt.Sprintf("/dashboard/%s", dashboardUuid)
|
|
||||||
} else {
|
|
||||||
definition.Assets.Dashboards[i].Url = "" // to unset the in-memory URL if enabled once and disabled afterwards
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &details, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateServiceConfigRequest struct {
|
|
||||||
CloudAccountId string `json:"cloud_account_id"`
|
|
||||||
Config types.CloudServiceConfig `json:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UpdateServiceConfigRequest) Validate(def *services.Definition) error {
|
|
||||||
if def.Id != services.S3Sync && u.Config.Logs != nil && u.Config.Logs.S3Buckets != nil {
|
|
||||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "s3 buckets can only be added to service-type[%s]", services.S3Sync)
|
|
||||||
} else if def.Id == services.S3Sync && u.Config.Logs != nil && u.Config.Logs.S3Buckets != nil {
|
|
||||||
for region := range u.Config.Logs.S3Buckets {
|
|
||||||
if _, found := ValidAWSRegions[region]; !found {
|
|
||||||
return errors.NewInvalidInputf(CodeInvalidCloudRegion, "invalid cloud region: %s", region)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateServiceConfigResponse struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Config types.CloudServiceConfig `json:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) UpdateServiceConfig(
|
|
||||||
ctx context.Context,
|
|
||||||
orgID string,
|
|
||||||
cloudProvider string,
|
|
||||||
serviceType string,
|
|
||||||
req *UpdateServiceConfigRequest,
|
|
||||||
) (*UpdateServiceConfigResponse, error) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// can only update config for a valid service.
|
|
||||||
definition, err := services.GetServiceDefinition(cloudProvider, serviceType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := req.Validate(definition); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// can only update config for a connected cloud account id
|
|
||||||
_, apiErr := c.accountsRepo.getConnectedCloudAccount(
|
|
||||||
ctx, orgID, cloudProvider, req.CloudAccountId,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't find connected cloud account")
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedConfig, apiErr := c.serviceConfigRepo.upsert(
|
|
||||||
ctx, orgID, cloudProvider, req.CloudAccountId, serviceType, req.Config,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't update service config")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UpdateServiceConfigResponse{
|
|
||||||
Id: serviceType,
|
|
||||||
Config: *updatedConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// All dashboards that are available based on cloud integrations configuration
|
|
||||||
// across all cloud providers
|
|
||||||
func (c *Controller) AvailableDashboards(ctx context.Context, orgId valuer.UUID) ([]*dashboardtypes.Dashboard, *model.ApiError) {
|
|
||||||
allDashboards := []*dashboardtypes.Dashboard{}
|
|
||||||
|
|
||||||
for _, provider := range []string{"aws"} {
|
|
||||||
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, provider)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(
|
|
||||||
apiErr, fmt.Sprintf("couldn't get available dashboards for %s", provider),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
allDashboards = append(allDashboards, providerDashboards...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return allDashboards, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) AvailableDashboardsForCloudProvider(ctx context.Context, orgID valuer.UUID, cloudProvider string) ([]*dashboardtypes.Dashboard, *model.ApiError) {
|
|
||||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID.StringValue(), cloudProvider)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't list connected cloud accounts")
|
|
||||||
}
|
|
||||||
|
|
||||||
// for v0, service dashboards are only available when metrics are enabled.
|
|
||||||
servicesWithAvailableMetrics := map[string]*time.Time{}
|
|
||||||
|
|
||||||
for _, ar := range accountRecords {
|
|
||||||
if ar.AccountID != nil {
|
|
||||||
configsBySvcId, apiErr := c.serviceConfigRepo.getAllForAccount(
|
|
||||||
ctx, orgID.StringValue(), ar.ID.StringValue(),
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
for svcId, config := range configsBySvcId {
|
|
||||||
if config.Metrics != nil && config.Metrics.Enabled {
|
|
||||||
servicesWithAvailableMetrics[svcId] = &ar.CreatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allServices, apiErr := services.List(cloudProvider)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
svcDashboards := []*dashboardtypes.Dashboard{}
|
|
||||||
for _, svc := range allServices {
|
|
||||||
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
|
||||||
if serviceDashboardsCreatedAt != nil {
|
|
||||||
for _, d := range svc.Assets.Dashboards {
|
|
||||||
author := fmt.Sprintf("%s-integration", cloudProvider)
|
|
||||||
svcDashboards = append(svcDashboards, &dashboardtypes.Dashboard{
|
|
||||||
ID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
|
||||||
Locked: true,
|
|
||||||
Data: *d.Definition,
|
|
||||||
TimeAuditable: types.TimeAuditable{
|
|
||||||
CreatedAt: *serviceDashboardsCreatedAt,
|
|
||||||
UpdatedAt: *serviceDashboardsCreatedAt,
|
|
||||||
},
|
|
||||||
UserAuditable: types.UserAuditable{
|
|
||||||
CreatedBy: author,
|
|
||||||
UpdatedBy: author,
|
|
||||||
},
|
|
||||||
OrgID: orgID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
servicesWithAvailableMetrics[svc.Id] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return svcDashboards, nil
|
|
||||||
}
|
|
||||||
func (c *Controller) GetDashboardById(ctx context.Context, orgId valuer.UUID, dashboardUuid string) (*dashboardtypes.Dashboard, *model.ApiError) {
|
|
||||||
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
allDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, cloudProvider)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't list available dashboards")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range allDashboards {
|
|
||||||
if d.ID == dashboardUuid {
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, model.NotFoundError(fmt.Errorf("couldn't find dashboard with uuid: %s", dashboardUuid))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) dashboardUuid(
|
|
||||||
cloudProvider string, svcId string, dashboardId string,
|
|
||||||
) string {
|
|
||||||
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) parseDashboardUuid(dashboardUuid string) (cloudProvider string, svcId string, dashboardId string, apiErr *model.ApiError) {
|
|
||||||
parts := strings.SplitN(dashboardUuid, "--", 4)
|
|
||||||
if len(parts) != 4 || parts[0] != "cloud-integration" {
|
|
||||||
return "", "", "", model.BadRequest(fmt.Errorf("invalid cloud integration dashboard id"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[1], parts[2], parts[3], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) IsCloudIntegrationDashboardUuid(dashboardUuid string) bool {
|
|
||||||
_, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
|
||||||
return apiErr == nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,797 @@
|
|||||||
|
package implawsprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/querier"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
||||||
|
integrationstore "github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/store"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||||
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CodeInvalidAWSRegion = errors.MustNewCode("invalid_aws_region")
|
||||||
|
CodeDashboardNotFound = errors.MustNewCode("dashboard_not_found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type awsProvider struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
querier querier.Querier
|
||||||
|
accountsRepo integrationstore.CloudProviderAccountsRepository
|
||||||
|
serviceConfigRepo integrationstore.ServiceConfigDatabase
|
||||||
|
serviceDefinitions *services.ServicesProvider[*integrationstypes.AWSDefinition]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAWSCloudProvider(
|
||||||
|
logger *slog.Logger,
|
||||||
|
accountsRepo integrationstore.CloudProviderAccountsRepository,
|
||||||
|
serviceConfigRepo integrationstore.ServiceConfigDatabase,
|
||||||
|
querier querier.Querier,
|
||||||
|
) integrationstypes.CloudProvider {
|
||||||
|
serviceDefinitions, err := services.NewAWSCloudProviderServices()
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to initialize AWS service definitions: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &awsProvider{
|
||||||
|
logger: logger,
|
||||||
|
querier: querier,
|
||||||
|
accountsRepo: accountsRepo,
|
||||||
|
serviceConfigRepo: serviceConfigRepo,
|
||||||
|
serviceDefinitions: serviceDefinitions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) GetAccountStatus(ctx context.Context, orgID, accountID string) (*integrationstypes.GettableAccountStatus, error) {
|
||||||
|
accountRecord, err := a.accountsRepo.Get(ctx, orgID, a.GetName().String(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAccountStatus{
|
||||||
|
Id: accountRecord.ID.String(),
|
||||||
|
CloudAccountId: accountRecord.AccountID,
|
||||||
|
Status: accountRecord.Status(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) ListConnectedAccounts(ctx context.Context, orgID string) (*integrationstypes.GettableConnectedAccountsList, error) {
|
||||||
|
accountRecords, err := a.accountsRepo.ListConnected(ctx, orgID, a.GetName().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedAccounts := make([]*integrationstypes.Account, 0, len(accountRecords))
|
||||||
|
for _, r := range accountRecords {
|
||||||
|
connectedAccounts = append(connectedAccounts, r.Account(a.GetName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableConnectedAccountsList{
|
||||||
|
Accounts: connectedAccounts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) AgentCheckIn(ctx context.Context, req *integrationstypes.PostableAgentCheckInPayload) (any, error) {
|
||||||
|
// agent can't check in unless the account is already created
|
||||||
|
existingAccount, err := a.accountsRepo.Get(ctx, req.OrgID, a.GetName().String(), req.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingAccount != nil && existingAccount.AccountID != nil && *existingAccount.AccountID != req.AccountID {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
|
"can't check in with new %s account id %s for account %s with existing %s id %s",
|
||||||
|
a.GetName().String(), req.AccountID, existingAccount.ID.StringValue(), a.GetName().String(),
|
||||||
|
*existingAccount.AccountID,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAccount, err = a.accountsRepo.GetConnectedCloudAccount(ctx, req.OrgID, a.GetName().String(), req.AccountID)
|
||||||
|
if existingAccount != nil && existingAccount.ID.StringValue() != req.ID {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
|
"can't check in to %s account %s with id %s. already connected with id %s",
|
||||||
|
a.GetName().String(), req.AccountID, req.ID, existingAccount.ID.StringValue(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
agentReport := integrationstypes.AgentReport{
|
||||||
|
TimestampMillis: time.Now().UnixMilli(),
|
||||||
|
Data: req.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := a.accountsRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), &req.ID, nil, &req.AccountID, &agentReport, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agentConfig, err := a.getAWSAgentConfig(ctx, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAWSAgentCheckIn{
|
||||||
|
AccountId: account.ID.StringValue(),
|
||||||
|
CloudAccountId: *account.AccountID,
|
||||||
|
RemovedAt: account.RemovedAt,
|
||||||
|
IntegrationConfig: *agentConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) getAWSAgentConfig(ctx context.Context, account *integrationstypes.CloudIntegration) (*integrationstypes.AWSAgentIntegrationConfig, error) {
|
||||||
|
// prepare and return integration config to be consumed by agent
|
||||||
|
agentConfig := &integrationstypes.AWSAgentIntegrationConfig{
|
||||||
|
EnabledRegions: []string{},
|
||||||
|
TelemetryCollectionStrategy: &integrationstypes.AWSCollectionStrategy{
|
||||||
|
Metrics: &integrationstypes.AWSMetricsStrategy{},
|
||||||
|
Logs: &integrationstypes.AWSLogsStrategy{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
accountConfig := new(integrationstypes.AWSAccountConfig)
|
||||||
|
err := integrationstypes.UnmarshalJSON([]byte(account.Config), accountConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountConfig.EnabledRegions != nil {
|
||||||
|
agentConfig.EnabledRegions = accountConfig.EnabledRegions
|
||||||
|
}
|
||||||
|
|
||||||
|
svcConfigs, err := a.serviceConfigRepo.GetAllForAccount(
|
||||||
|
ctx, account.OrgID, account.ID.StringValue(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate config in a fixed order to ensure same config generated across runs
|
||||||
|
configuredServices := maps.Keys(svcConfigs)
|
||||||
|
slices.Sort(configuredServices)
|
||||||
|
|
||||||
|
for _, svcType := range configuredServices {
|
||||||
|
definition, err := a.serviceDefinitions.GetServiceDefinition(ctx, svcType)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config := svcConfigs[svcType]
|
||||||
|
|
||||||
|
serviceConfig := new(integrationstypes.AWSCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceConfig.Logs != nil && serviceConfig.Logs.Enabled {
|
||||||
|
if svcType == integrationstypes.S3Sync {
|
||||||
|
// S3 bucket sync; No cloudwatch logs are appended for this service type;
|
||||||
|
// Though definition is populated with a custom cloudwatch group that helps in calculating logs connection status
|
||||||
|
agentConfig.TelemetryCollectionStrategy.S3Buckets = serviceConfig.Logs.S3Buckets
|
||||||
|
} else if definition.Strategy != nil && definition.Strategy.Logs != nil { // services that includes a logs subscription
|
||||||
|
agentConfig.TelemetryCollectionStrategy.Logs.Subscriptions = append(
|
||||||
|
agentConfig.TelemetryCollectionStrategy.Logs.Subscriptions,
|
||||||
|
definition.Strategy.Logs.Subscriptions...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceConfig.Metrics != nil && serviceConfig.Metrics.Enabled && definition.Strategy != nil && definition.Strategy.Metrics != nil {
|
||||||
|
agentConfig.TelemetryCollectionStrategy.Metrics.StreamFilters = append(
|
||||||
|
agentConfig.TelemetryCollectionStrategy.Metrics.StreamFilters,
|
||||||
|
definition.Strategy.Metrics.StreamFilters...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return agentConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) GetName() integrationstypes.CloudProviderType {
|
||||||
|
return integrationstypes.CloudProviderAWS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) ListServices(ctx context.Context, orgID string, cloudAccountID *string) (any, error) {
|
||||||
|
svcConfigs := make(map[string]*integrationstypes.AWSCloudServiceConfig)
|
||||||
|
if cloudAccountID != nil {
|
||||||
|
activeAccount, err := a.accountsRepo.GetConnectedCloudAccount(ctx, orgID, a.GetName().String(), *cloudAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfigs, err := a.serviceConfigRepo.GetAllForAccount(ctx, orgID, activeAccount.ID.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcType, config := range serviceConfigs {
|
||||||
|
serviceConfig := new(integrationstypes.AWSCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
svcConfigs[svcType] = serviceConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries := make([]integrationstypes.AWSServiceSummary, 0)
|
||||||
|
|
||||||
|
definitions, err := a.serviceDefinitions.ListServiceDefinitions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf("couldn't list aws service definitions: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, def := range definitions {
|
||||||
|
summary := integrationstypes.AWSServiceSummary{
|
||||||
|
DefinitionMetadata: def.DefinitionMetadata,
|
||||||
|
Config: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.Config = svcConfigs[summary.Id]
|
||||||
|
|
||||||
|
summaries = append(summaries, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(summaries, func(i, j int) bool {
|
||||||
|
return summaries[i].DefinitionMetadata.Title < summaries[j].DefinitionMetadata.Title
|
||||||
|
})
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAWSServices{
|
||||||
|
Services: summaries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) GetServiceDetails(ctx context.Context, req *integrationstypes.GetServiceDetailsReq) (any, error) {
|
||||||
|
details := new(integrationstypes.GettableAWSServiceDetails)
|
||||||
|
|
||||||
|
awsDefinition, err := a.serviceDefinitions.GetServiceDefinition(ctx, req.ServiceId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf("couldn't get aws service definition: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
details.AWSDefinition = *awsDefinition
|
||||||
|
if req.CloudAccountID == nil {
|
||||||
|
return details, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := a.getServiceConfig(ctx, &details.AWSDefinition, req.OrgID, a.GetName().String(), req.ServiceId, *req.CloudAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
return details, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Config = config
|
||||||
|
|
||||||
|
connectionStatus, err := a.getServiceConnectionStatus(
|
||||||
|
ctx,
|
||||||
|
*req.CloudAccountID,
|
||||||
|
req.OrgID,
|
||||||
|
&details.AWSDefinition,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
details.ConnectionStatus = connectionStatus
|
||||||
|
|
||||||
|
return details, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) getServiceConnectionStatus(
|
||||||
|
ctx context.Context,
|
||||||
|
cloudAccountID string,
|
||||||
|
orgID valuer.UUID,
|
||||||
|
def *integrationstypes.AWSDefinition,
|
||||||
|
serviceConfig *integrationstypes.AWSCloudServiceConfig,
|
||||||
|
) (*integrationstypes.ServiceConnectionStatus, error) {
|
||||||
|
if def.Strategy == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := new(integrationstypes.ServiceConnectionStatus)
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
if def.Strategy.Metrics != nil && serviceConfig.Metrics != nil && serviceConfig.Metrics.Enabled {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
a.logger.ErrorContext(
|
||||||
|
ctx, "panic while getting service metrics connection status",
|
||||||
|
"error", r,
|
||||||
|
"service", def.DefinitionMetadata.Id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer wg.Done()
|
||||||
|
status, _ := a.getServiceMetricsConnectionStatus(ctx, cloudAccountID, orgID, def)
|
||||||
|
resp.Metrics = status
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if def.Strategy.Logs != nil && serviceConfig.Logs != nil && serviceConfig.Logs.Enabled {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
a.logger.ErrorContext(
|
||||||
|
ctx, "panic while getting service logs connection status",
|
||||||
|
"error", r,
|
||||||
|
"service", def.DefinitionMetadata.Id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer wg.Done()
|
||||||
|
status, _ := a.getServiceLogsConnectionStatus(ctx, cloudAccountID, orgID, def)
|
||||||
|
resp.Logs = status
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) getServiceMetricsConnectionStatus(
|
||||||
|
ctx context.Context,
|
||||||
|
cloudAccountID string,
|
||||||
|
orgID valuer.UUID,
|
||||||
|
def *integrationstypes.AWSDefinition,
|
||||||
|
) ([]*integrationstypes.SignalConnectionStatus, error) {
|
||||||
|
if def.Strategy == nil ||
|
||||||
|
def.Strategy.Metrics == nil ||
|
||||||
|
len(def.Strategy.Metrics.StreamFilters) < 1 ||
|
||||||
|
len(def.DataCollected.Metrics) < 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
statusResp := make([]*integrationstypes.SignalConnectionStatus, 0)
|
||||||
|
|
||||||
|
for _, category := range def.IngestionStatusCheck.Metrics {
|
||||||
|
queries := make([]qbtypes.QueryEnvelope, 0)
|
||||||
|
|
||||||
|
for _, check := range category.Checks {
|
||||||
|
filterExpression := fmt.Sprintf(`cloud.provider="aws" AND cloud.account.id="%s"`, cloudAccountID)
|
||||||
|
f := ""
|
||||||
|
for _, attribute := range check.Attributes {
|
||||||
|
f = fmt.Sprintf("%s %s", attribute.Name, attribute.Operator)
|
||||||
|
if attribute.Value != "" {
|
||||||
|
f = fmt.Sprintf("%s '%s'", f, attribute.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
filterExpression = fmt.Sprintf("%s AND %s", filterExpression, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = append(queries, qbtypes.QueryEnvelope{
|
||||||
|
Type: qbtypes.QueryTypeBuilder,
|
||||||
|
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
|
||||||
|
Name: valuer.GenerateUUID().String(),
|
||||||
|
Signal: telemetrytypes.SignalMetrics,
|
||||||
|
Aggregations: []qbtypes.MetricAggregation{{
|
||||||
|
MetricName: check.Key,
|
||||||
|
TimeAggregation: metrictypes.TimeAggregationAvg,
|
||||||
|
SpaceAggregation: metrictypes.SpaceAggregationAvg,
|
||||||
|
}},
|
||||||
|
Filter: &qbtypes.Filter{
|
||||||
|
Expression: filterExpression,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.querier.QueryRange(ctx, orgID, &qbtypes.QueryRangeRequest{
|
||||||
|
SchemaVersion: "v5",
|
||||||
|
Start: uint64(time.Now().Add(-time.Hour).UnixMilli()),
|
||||||
|
End: uint64(time.Now().UnixMilli()),
|
||||||
|
RequestType: qbtypes.RequestTypeScalar,
|
||||||
|
CompositeQuery: qbtypes.CompositeQuery{
|
||||||
|
Queries: queries,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
a.logger.DebugContext(ctx,
|
||||||
|
"error querying for service metrics connection status",
|
||||||
|
"error", err,
|
||||||
|
"service", def.DefinitionMetadata.Id,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil && len(resp.Data.Results) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
queryResponse, ok := resp.Data.Results[0].(*qbtypes.TimeSeriesData)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryResponse == nil ||
|
||||||
|
len(queryResponse.Aggregations) < 1 ||
|
||||||
|
len(queryResponse.Aggregations[0].Series) < 1 ||
|
||||||
|
len(queryResponse.Aggregations[0].Series[0].Values) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
statusResp = append(statusResp, &integrationstypes.SignalConnectionStatus{
|
||||||
|
CategoryID: category.Category,
|
||||||
|
CategoryDisplayName: category.DisplayName,
|
||||||
|
LastReceivedTsMillis: queryResponse.Aggregations[0].Series[0].Values[0].Timestamp,
|
||||||
|
LastReceivedFrom: "signoz-aws-integration",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) getServiceLogsConnectionStatus(
|
||||||
|
ctx context.Context,
|
||||||
|
cloudAccountID string,
|
||||||
|
orgID valuer.UUID,
|
||||||
|
def *integrationstypes.AWSDefinition,
|
||||||
|
) ([]*integrationstypes.SignalConnectionStatus, error) {
|
||||||
|
if def.Strategy == nil ||
|
||||||
|
def.Strategy.Logs == nil ||
|
||||||
|
len(def.Strategy.Logs.Subscriptions) < 1 ||
|
||||||
|
len(def.DataCollected.Logs) < 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
statusResp := make([]*integrationstypes.SignalConnectionStatus, 0)
|
||||||
|
|
||||||
|
for _, category := range def.IngestionStatusCheck.Logs {
|
||||||
|
queries := make([]qbtypes.QueryEnvelope, 0)
|
||||||
|
|
||||||
|
for _, check := range category.Checks {
|
||||||
|
filterExpression := fmt.Sprintf(`cloud.account.id="%s"`, cloudAccountID)
|
||||||
|
f := ""
|
||||||
|
for _, attribute := range check.Attributes {
|
||||||
|
f = fmt.Sprintf("%s %s", attribute.Name, attribute.Operator)
|
||||||
|
if attribute.Value != "" {
|
||||||
|
f = fmt.Sprintf("%s '%s'", f, attribute.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
filterExpression = fmt.Sprintf("%s AND %s", filterExpression, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = append(queries, qbtypes.QueryEnvelope{
|
||||||
|
Type: qbtypes.QueryTypeBuilder,
|
||||||
|
Spec: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||||
|
Name: valuer.GenerateUUID().String(),
|
||||||
|
Signal: telemetrytypes.SignalLogs,
|
||||||
|
Aggregations: []qbtypes.LogAggregation{{
|
||||||
|
Expression: "count()",
|
||||||
|
}},
|
||||||
|
Filter: &qbtypes.Filter{
|
||||||
|
Expression: filterExpression,
|
||||||
|
},
|
||||||
|
Limit: 10,
|
||||||
|
Offset: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.querier.QueryRange(ctx, orgID, &qbtypes.QueryRangeRequest{
|
||||||
|
SchemaVersion: "v1",
|
||||||
|
Start: uint64(time.Now().Add(-time.Hour * 1).UnixMilli()),
|
||||||
|
End: uint64(time.Now().UnixMilli()),
|
||||||
|
RequestType: qbtypes.RequestTypeTimeSeries,
|
||||||
|
CompositeQuery: qbtypes.CompositeQuery{
|
||||||
|
Queries: queries,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
a.logger.DebugContext(ctx,
|
||||||
|
"error querying for service logs connection status",
|
||||||
|
"error", err,
|
||||||
|
"service", def.DefinitionMetadata.Id,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil && len(resp.Data.Results) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
queryResponse, ok := resp.Data.Results[0].(*qbtypes.TimeSeriesData)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryResponse == nil ||
|
||||||
|
len(queryResponse.Aggregations) < 1 ||
|
||||||
|
len(queryResponse.Aggregations[0].Series) < 1 ||
|
||||||
|
len(queryResponse.Aggregations[0].Series[0].Values) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
statusResp = append(statusResp, &integrationstypes.SignalConnectionStatus{
|
||||||
|
CategoryID: category.Category,
|
||||||
|
CategoryDisplayName: category.DisplayName,
|
||||||
|
LastReceivedTsMillis: queryResponse.Aggregations[0].Series[0].Values[0].Timestamp,
|
||||||
|
LastReceivedFrom: "signoz-aws-integration",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) getServiceConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
def *integrationstypes.AWSDefinition,
|
||||||
|
orgID valuer.UUID, cloudProvider, serviceId, cloudAccountId string,
|
||||||
|
) (*integrationstypes.AWSCloudServiceConfig, error) {
|
||||||
|
activeAccount, err := a.accountsRepo.GetConnectedCloudAccount(ctx, orgID.String(), cloudProvider, cloudAccountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := a.serviceConfigRepo.Get(ctx, orgID.String(), activeAccount.ID.StringValue(), serviceId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Ast(err, errors.TypeNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfig := new(integrationstypes.AWSCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config != nil && serviceConfig.Metrics != nil && serviceConfig.Metrics.Enabled {
|
||||||
|
def.PopulateDashboardURLs(a.GetName(), serviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) GetAvailableDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||||
|
accountRecords, err := a.accountsRepo.ListConnected(ctx, orgID.StringValue(), a.GetName().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for now service dashboards are only available when metrics are enabled.
|
||||||
|
servicesWithAvailableMetrics := map[string]*time.Time{}
|
||||||
|
|
||||||
|
for _, ar := range accountRecords {
|
||||||
|
if ar.AccountID != nil {
|
||||||
|
configsBySvcId, err := a.serviceConfigRepo.GetAllForAccount(ctx, orgID.StringValue(), ar.ID.StringValue())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcId, config := range configsBySvcId {
|
||||||
|
serviceConfig := new(integrationstypes.AWSCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceConfig.Metrics != nil && serviceConfig.Metrics.Enabled {
|
||||||
|
servicesWithAvailableMetrics[svcId] = &ar.CreatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svcDashboards := make([]*dashboardtypes.Dashboard, 0)
|
||||||
|
|
||||||
|
allServices, err := a.serviceDefinitions.ListServiceDefinitions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to list aws service definitions")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, svc := range allServices {
|
||||||
|
serviceDashboardsCreatedAt, ok := servicesWithAvailableMetrics[svc.Id]
|
||||||
|
if ok {
|
||||||
|
svcDashboards = integrationstypes.GetDashboardsFromAssets(svc.Id, a.GetName(), serviceDashboardsCreatedAt, svc.Assets)
|
||||||
|
servicesWithAvailableMetrics[svc.Id] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return svcDashboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) GetDashboard(ctx context.Context, req *integrationstypes.GettableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||||
|
allDashboards, err := a.GetAvailableDashboards(ctx, req.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range allDashboards {
|
||||||
|
if d.ID == req.ID {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.NewNotFoundf(CodeDashboardNotFound, "dashboard with id %s not found", req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) GenerateConnectionArtifact(ctx context.Context, req *integrationstypes.PostableConnectionArtifact) (any, error) {
|
||||||
|
connection := new(integrationstypes.PostableAWSConnectionUrl)
|
||||||
|
|
||||||
|
err := integrationstypes.UnmarshalJSON(req.Data, connection)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if connection.AccountConfig != nil {
|
||||||
|
for _, region := range connection.AccountConfig.EnabledRegions {
|
||||||
|
if integrationstypes.ValidAWSRegions[region] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.NewInvalidInputf(CodeInvalidAWSRegion, "invalid aws region: %s", region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := integrationstypes.MarshalJSON(connection.AccountConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := a.accountsRepo.Upsert(
|
||||||
|
ctx, req.OrgID, integrationstypes.CloudProviderAWS.String(), nil, config,
|
||||||
|
nil, nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agentVersion := "v0.0.8"
|
||||||
|
if connection.AgentConfig.Version != "" {
|
||||||
|
agentVersion = connection.AgentConfig.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL := fmt.Sprintf("https://%s.console.aws.amazon.com/cloudformation/home",
|
||||||
|
connection.AgentConfig.Region)
|
||||||
|
u, _ := url.Parse(baseURL)
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
q.Set("region", connection.AgentConfig.Region)
|
||||||
|
u.Fragment = "/stacks/quickcreate"
|
||||||
|
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
q = u.Query()
|
||||||
|
q.Set("stackName", "signoz-integration")
|
||||||
|
q.Set("templateURL", fmt.Sprintf("https://signoz-integrations.s3.us-east-1.amazonaws.com/aws-quickcreate-template-%s.json", agentVersion))
|
||||||
|
q.Set("param_SigNozIntegrationAgentVersion", agentVersion)
|
||||||
|
q.Set("param_SigNozApiUrl", connection.AgentConfig.SigNozAPIUrl)
|
||||||
|
q.Set("param_SigNozApiKey", connection.AgentConfig.SigNozAPIKey)
|
||||||
|
q.Set("param_SigNozAccountId", account.ID.StringValue())
|
||||||
|
q.Set("param_IngestionUrl", connection.AgentConfig.IngestionUrl)
|
||||||
|
q.Set("param_IngestionKey", connection.AgentConfig.IngestionKey)
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAWSConnectionUrl{
|
||||||
|
AccountId: account.ID.StringValue(),
|
||||||
|
ConnectionUrl: u.String() + "?&" + q.Encode(), // this format is required by AWS
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) UpdateServiceConfig(ctx context.Context, req *integrationstypes.PatchableServiceConfig) (any, error) {
|
||||||
|
definition, err := a.serviceDefinitions.GetServiceDefinition(ctx, req.ServiceId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf("couldn't get aws service definition: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfig := new(integrationstypes.PatchableAWSCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(req.Config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = serviceConfig.Config.Validate(definition); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// can only update config for a connected cloud account id
|
||||||
|
_, err = a.accountsRepo.GetConnectedCloudAccount(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), serviceConfig.CloudAccountId,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfigBytes, err := integrationstypes.MarshalJSON(serviceConfig.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedConfig, err := a.serviceConfigRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), serviceConfig.CloudAccountId, req.ServiceId, serviceConfigBytes,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = integrationstypes.UnmarshalJSON(updatedConfig, serviceConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.PatchServiceConfigResponse{
|
||||||
|
ServiceId: req.ServiceId,
|
||||||
|
Config: serviceConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) UpdateAccountConfig(ctx context.Context, req *integrationstypes.PatchableAccountConfig) (any, error) {
|
||||||
|
config := new(integrationstypes.PatchableAWSAccountConfig)
|
||||||
|
|
||||||
|
err := integrationstypes.UnmarshalJSON(req.Data, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Config == nil {
|
||||||
|
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "account config can't be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, region := range config.Config.EnabledRegions {
|
||||||
|
if integrationstypes.ValidAWSRegions[region] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.NewInvalidInputf(CodeInvalidAWSRegion, "invalid aws region: %s", region)
|
||||||
|
}
|
||||||
|
|
||||||
|
configBytes, err := integrationstypes.MarshalJSON(config.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// account must exist to update config, but it doesn't need to be connected
|
||||||
|
_, err = a.accountsRepo.Get(ctx, req.OrgID, a.GetName().String(), req.AccountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountRecord, err := a.accountsRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), &req.AccountId, configBytes, nil, nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountRecord.Account(a.GetName()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awsProvider) DisconnectAccount(ctx context.Context, orgID, accountID string) (*integrationstypes.CloudIntegration, error) {
|
||||||
|
account, err := a.accountsRepo.Get(ctx, orgID, a.GetName().String(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tsNow := time.Now()
|
||||||
|
account, err = a.accountsRepo.Upsert(
|
||||||
|
ctx, orgID, a.GetName().String(), &accountID, nil, nil, nil, &tsNow,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,592 @@
|
|||||||
|
package implazureprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/querier"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/store"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CodeInvalidAzureRegion = errors.MustNewCode("invalid_azure_region")
|
||||||
|
CodeDashboardNotFound = errors.MustNewCode("dashboard_not_found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type azureProvider struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
accountsRepo store.CloudProviderAccountsRepository
|
||||||
|
serviceConfigRepo store.ServiceConfigDatabase
|
||||||
|
azureServiceDefinitions *services.ServicesProvider[*integrationstypes.AzureDefinition]
|
||||||
|
querier querier.Querier
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAzureCloudProvider(
|
||||||
|
logger *slog.Logger,
|
||||||
|
accountsRepo store.CloudProviderAccountsRepository,
|
||||||
|
serviceConfigRepo store.ServiceConfigDatabase,
|
||||||
|
querier querier.Querier,
|
||||||
|
) integrationstypes.CloudProvider {
|
||||||
|
azureServiceDefinitions, err := services.NewAzureCloudProviderServices()
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to initialize Azure service definitions: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &azureProvider{
|
||||||
|
logger: logger,
|
||||||
|
accountsRepo: accountsRepo,
|
||||||
|
serviceConfigRepo: serviceConfigRepo,
|
||||||
|
azureServiceDefinitions: azureServiceDefinitions,
|
||||||
|
querier: querier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) GetAccountStatus(ctx context.Context, orgID, accountID string) (*integrationstypes.GettableAccountStatus, error) {
|
||||||
|
account, err := a.accountsRepo.Get(ctx, orgID, a.GetName().String(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAccountStatus{
|
||||||
|
Id: account.ID.String(),
|
||||||
|
CloudAccountId: account.AccountID,
|
||||||
|
Status: account.Status(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) ListConnectedAccounts(ctx context.Context, orgID string) (*integrationstypes.GettableConnectedAccountsList, error) {
|
||||||
|
accountRecords, err := a.accountsRepo.ListConnected(ctx, orgID, a.GetName().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedAccounts := make([]*integrationstypes.Account, 0, len(accountRecords))
|
||||||
|
for _, r := range accountRecords {
|
||||||
|
connectedAccounts = append(connectedAccounts, r.Account(a.GetName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableConnectedAccountsList{
|
||||||
|
Accounts: connectedAccounts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) AgentCheckIn(ctx context.Context, req *integrationstypes.PostableAgentCheckInPayload) (any, error) {
|
||||||
|
existingAccount, err := a.accountsRepo.Get(ctx, req.OrgID, a.GetName().String(), req.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingAccount != nil && existingAccount.AccountID != nil && *existingAccount.AccountID != req.AccountID {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
|
"can't check in with new %s account id %s for account %s with existing %s id %s",
|
||||||
|
a.GetName().String(), req.AccountID, existingAccount.ID.StringValue(), a.GetName().String(),
|
||||||
|
*existingAccount.AccountID,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAccount, err = a.accountsRepo.GetConnectedCloudAccount(ctx, req.OrgID, a.GetName().String(), req.AccountID)
|
||||||
|
if existingAccount != nil && existingAccount.ID.StringValue() != req.ID {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
|
"can't check in to %s account %s with id %s. already connected with id %s",
|
||||||
|
a.GetName().String(), req.AccountID, req.ID, existingAccount.ID.StringValue(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
agentReport := integrationstypes.AgentReport{
|
||||||
|
TimestampMillis: time.Now().UnixMilli(),
|
||||||
|
Data: req.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := a.accountsRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), &req.ID, nil, &req.AccountID, &agentReport, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agentConfig, err := a.getAzureAgentConfig(ctx, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAzureAgentCheckIn{
|
||||||
|
AccountId: account.ID.StringValue(),
|
||||||
|
CloudAccountId: *account.AccountID,
|
||||||
|
RemovedAt: account.RemovedAt,
|
||||||
|
IntegrationConfig: *agentConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) getAzureAgentConfig(ctx context.Context, account *integrationstypes.CloudIntegration) (*integrationstypes.AzureAgentIntegrationConfig, error) {
|
||||||
|
// prepare and return integration config to be consumed by agent
|
||||||
|
agentConfig := &integrationstypes.AzureAgentIntegrationConfig{
|
||||||
|
TelemetryCollectionStrategy: make(map[string]*integrationstypes.AzureCollectionStrategy),
|
||||||
|
}
|
||||||
|
|
||||||
|
accountConfig := new(integrationstypes.AzureAccountConfig)
|
||||||
|
err := integrationstypes.UnmarshalJSON([]byte(account.Config), accountConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Config != "" {
|
||||||
|
agentConfig.DeploymentRegion = accountConfig.DeploymentRegion
|
||||||
|
agentConfig.EnabledResourceGroups = accountConfig.EnabledResourceGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
svcConfigs, err := a.serviceConfigRepo.GetAllForAccount(
|
||||||
|
ctx, account.OrgID, account.ID.StringValue(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate config in a fixed order to ensure same config generated across runs
|
||||||
|
configuredServices := maps.Keys(svcConfigs)
|
||||||
|
slices.Sort(configuredServices)
|
||||||
|
|
||||||
|
metrics := make([]*integrationstypes.AzureMetricsStrategy, 0)
|
||||||
|
logs := make([]*integrationstypes.AzureLogsStrategy, 0)
|
||||||
|
|
||||||
|
for _, svcType := range configuredServices {
|
||||||
|
definition, err := a.azureServiceDefinitions.GetServiceDefinition(ctx, svcType)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config := svcConfigs[svcType]
|
||||||
|
|
||||||
|
serviceConfig := new(integrationstypes.AzureCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
metricsStrategyMap := make(map[string]*integrationstypes.AzureMetricsStrategy)
|
||||||
|
logsStrategyMap := make(map[string]*integrationstypes.AzureLogsStrategy)
|
||||||
|
|
||||||
|
if definition.Strategy != nil && definition.Strategy.Metrics != nil {
|
||||||
|
for _, metric := range definition.Strategy.Metrics {
|
||||||
|
metricsStrategyMap[metric.Name] = metric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if definition.Strategy != nil && definition.Strategy.Logs != nil {
|
||||||
|
for _, log := range definition.Strategy.Logs {
|
||||||
|
logsStrategyMap[log.Name] = log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceConfig.Metrics != nil {
|
||||||
|
for _, metric := range serviceConfig.Metrics {
|
||||||
|
if metric.Enabled {
|
||||||
|
metrics = append(metrics, &integrationstypes.AzureMetricsStrategy{
|
||||||
|
CategoryType: metricsStrategyMap[metric.Name].CategoryType,
|
||||||
|
Name: metric.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceConfig.Logs != nil {
|
||||||
|
for _, log := range serviceConfig.Logs {
|
||||||
|
if log.Enabled {
|
||||||
|
logs = append(logs, &integrationstypes.AzureLogsStrategy{
|
||||||
|
CategoryType: logsStrategyMap[log.Name].CategoryType,
|
||||||
|
Name: log.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strategy := &integrationstypes.AzureCollectionStrategy{
|
||||||
|
Metrics: metrics,
|
||||||
|
Logs: logs,
|
||||||
|
}
|
||||||
|
|
||||||
|
agentConfig.TelemetryCollectionStrategy[svcType] = strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
return agentConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) GetName() valuer.String {
|
||||||
|
return integrationstypes.CloudProviderAzure
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) ListServices(ctx context.Context, orgID string, cloudAccountID *string) (any, error) {
|
||||||
|
svcConfigs := make(map[string]*integrationstypes.AzureCloudServiceConfig)
|
||||||
|
if cloudAccountID != nil {
|
||||||
|
activeAccount, err := a.accountsRepo.GetConnectedCloudAccount(ctx, orgID, a.GetName().String(), *cloudAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfigs, err := a.serviceConfigRepo.GetAllForAccount(ctx, orgID, activeAccount.ID.StringValue())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcType, config := range serviceConfigs {
|
||||||
|
serviceConfig := new(integrationstypes.AzureCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
svcConfigs[svcType] = serviceConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries := make([]integrationstypes.AzureServiceSummary, 0)
|
||||||
|
|
||||||
|
definitions, err := a.azureServiceDefinitions.ListServiceDefinitions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf("couldn't list aws service definitions: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, def := range definitions {
|
||||||
|
summary := integrationstypes.AzureServiceSummary{
|
||||||
|
DefinitionMetadata: def.DefinitionMetadata,
|
||||||
|
Config: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.Config = svcConfigs[summary.Id]
|
||||||
|
|
||||||
|
summaries = append(summaries, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(summaries, func(i, j int) bool {
|
||||||
|
return summaries[i].DefinitionMetadata.Title < summaries[j].DefinitionMetadata.Title
|
||||||
|
})
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAzureServices{
|
||||||
|
Services: summaries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) GetServiceDetails(ctx context.Context, req *integrationstypes.GetServiceDetailsReq) (any, error) {
|
||||||
|
details := new(integrationstypes.GettableAzureServiceDetails)
|
||||||
|
|
||||||
|
azureDefinition, err := a.azureServiceDefinitions.GetServiceDefinition(ctx, req.ServiceId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf("couldn't get aws service definition: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
details.AzureDefinition = *azureDefinition
|
||||||
|
if req.CloudAccountID == nil {
|
||||||
|
return details, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := a.getServiceConfig(ctx, azureDefinition, req.OrgID.String(), req.ServiceId, *req.CloudAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Config = config
|
||||||
|
|
||||||
|
// fill default values for config
|
||||||
|
if details.Config == nil {
|
||||||
|
cfg := new(integrationstypes.AzureCloudServiceConfig)
|
||||||
|
|
||||||
|
logs := make([]*integrationstypes.AzureCloudServiceLogsConfig, 0)
|
||||||
|
if azureDefinition.Strategy != nil && azureDefinition.Strategy.Logs != nil {
|
||||||
|
for _, log := range azureDefinition.Strategy.Logs {
|
||||||
|
logs = append(logs, &integrationstypes.AzureCloudServiceLogsConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Name: log.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics := make([]*integrationstypes.AzureCloudServiceMetricsConfig, 0)
|
||||||
|
if azureDefinition.Strategy != nil && azureDefinition.Strategy.Metrics != nil {
|
||||||
|
for _, metric := range azureDefinition.Strategy.Metrics {
|
||||||
|
metrics = append(metrics, &integrationstypes.AzureCloudServiceMetricsConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Name: metric.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Logs = logs
|
||||||
|
cfg.Metrics = metrics
|
||||||
|
|
||||||
|
details.Config = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: write logic for getting connection status
|
||||||
|
|
||||||
|
return details, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) getServiceConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
definition *integrationstypes.AzureDefinition,
|
||||||
|
orgID string,
|
||||||
|
serviceId string,
|
||||||
|
cloudAccountId string,
|
||||||
|
) (*integrationstypes.AzureCloudServiceConfig, error) {
|
||||||
|
activeAccount, err := a.accountsRepo.GetConnectedCloudAccount(ctx, orgID, a.GetName().String(), cloudAccountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
configBytes, err := a.serviceConfigRepo.Get(ctx, orgID, activeAccount.ID.String(), serviceId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Ast(err, errors.TypeNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := new(integrationstypes.AzureCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(configBytes, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metric := range config.Metrics {
|
||||||
|
if metric.Enabled {
|
||||||
|
definition.PopulateDashboardURLs(a.GetName(), serviceId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) GenerateConnectionArtifact(ctx context.Context, req *integrationstypes.PostableConnectionArtifact) (any, error) {
|
||||||
|
connection := new(integrationstypes.PostableAzureConnectionCommand)
|
||||||
|
|
||||||
|
err := integrationstypes.UnmarshalJSON(req.Data, connection)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed unmarshal request data into AWS connection config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate connection config
|
||||||
|
if connection.AccountConfig != nil {
|
||||||
|
if !integrationstypes.ValidAzureRegions[connection.AccountConfig.DeploymentRegion] {
|
||||||
|
return nil, errors.NewInvalidInputf(CodeInvalidAzureRegion, "invalid azure region: %s",
|
||||||
|
connection.AccountConfig.DeploymentRegion,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := integrationstypes.MarshalJSON(connection.AccountConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := a.accountsRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), nil, config,
|
||||||
|
nil, nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agentVersion := "v0.0.1"
|
||||||
|
|
||||||
|
if connection.AgentConfig.Version != "" {
|
||||||
|
agentVersion = connection.AgentConfig.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: improve the command and set url
|
||||||
|
cliCommand := []string{"az", "stack", "sub", "create", "--name", "SigNozIntegration", "--location",
|
||||||
|
connection.AccountConfig.DeploymentRegion, "--template-uri", fmt.Sprintf("<url>%s", agentVersion),
|
||||||
|
"--action-on-unmanage", "deleteAll", "--deny-settings-mode", "denyDelete", "--parameters", fmt.Sprintf("rgName=%s", "signoz-integration-rg"),
|
||||||
|
fmt.Sprintf("rgLocation=%s", connection.AccountConfig.DeploymentRegion)}
|
||||||
|
|
||||||
|
return &integrationstypes.GettableAzureConnectionCommand{
|
||||||
|
AccountId: account.ID.String(),
|
||||||
|
AzureShellConnectionCommand: "az create",
|
||||||
|
AzureCliConnectionCommand: strings.Join(cliCommand, " "),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) UpdateServiceConfig(ctx context.Context, req *integrationstypes.PatchableServiceConfig) (any, error) {
|
||||||
|
definition, err := a.azureServiceDefinitions.GetServiceDefinition(ctx, req.ServiceId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfig := new(integrationstypes.PatchableAzureCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(req.Config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = serviceConfig.Config.Validate(definition); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// can only update config for a connected cloud account id
|
||||||
|
_, err = a.accountsRepo.GetConnectedCloudAccount(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), serviceConfig.CloudAccountId,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceConfigBytes, err := integrationstypes.MarshalJSON(serviceConfig.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedConfig, err := a.serviceConfigRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), serviceConfig.CloudAccountId, req.ServiceId, serviceConfigBytes,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = integrationstypes.UnmarshalJSON(updatedConfig, serviceConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &integrationstypes.PatchServiceConfigResponse{
|
||||||
|
ServiceId: req.ServiceId,
|
||||||
|
Config: serviceConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) GetAvailableDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||||
|
accountRecords, err := a.accountsRepo.ListConnected(ctx, orgID.StringValue(), a.GetName().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for now service dashboards are only available when metrics are enabled.
|
||||||
|
servicesWithAvailableMetrics := map[string]*time.Time{}
|
||||||
|
|
||||||
|
for _, ar := range accountRecords {
|
||||||
|
if ar.AccountID == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configsBySvcId, err := a.serviceConfigRepo.GetAllForAccount(ctx, orgID.StringValue(), ar.ID.StringValue())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcId, config := range configsBySvcId {
|
||||||
|
serviceConfig := new(integrationstypes.AzureCloudServiceConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON(config, serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceConfig.Metrics != nil {
|
||||||
|
for _, metric := range serviceConfig.Metrics {
|
||||||
|
if metric.Enabled {
|
||||||
|
servicesWithAvailableMetrics[svcId] = &ar.CreatedAt
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svcDashboards := make([]*dashboardtypes.Dashboard, 0)
|
||||||
|
|
||||||
|
allServices, err := a.azureServiceDefinitions.ListServiceDefinitions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to list azure service definitions")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, svc := range allServices {
|
||||||
|
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
||||||
|
if serviceDashboardsCreatedAt != nil {
|
||||||
|
svcDashboards = integrationstypes.GetDashboardsFromAssets(svc.Id, a.GetName(), serviceDashboardsCreatedAt, svc.Assets)
|
||||||
|
servicesWithAvailableMetrics[svc.Id] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return svcDashboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) GetDashboard(ctx context.Context, req *integrationstypes.GettableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||||
|
allDashboards, err := a.GetAvailableDashboards(ctx, req.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dashboard := range allDashboards {
|
||||||
|
if dashboard.ID == req.ID {
|
||||||
|
return dashboard, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.NewNotFoundf(CodeDashboardNotFound, "dashboard with id %s not found", req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) UpdateAccountConfig(ctx context.Context, req *integrationstypes.PatchableAccountConfig) (any, error) {
|
||||||
|
config := new(integrationstypes.PatchableAzureAccountConfig)
|
||||||
|
|
||||||
|
err := integrationstypes.UnmarshalJSON(req.Data, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Config == nil && len(config.Config.EnabledResourceGroups) < 1 {
|
||||||
|
return nil, errors.NewInvalidInputf(CodeInvalidAzureRegion, "azure region and resource groups must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
//for azure, preserve deployment region if already set
|
||||||
|
account, err := a.accountsRepo.Get(ctx, req.OrgID, a.GetName().String(), req.AccountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
storedConfig := new(integrationstypes.AzureAccountConfig)
|
||||||
|
err = integrationstypes.UnmarshalJSON([]byte(account.Config), storedConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Config != "" {
|
||||||
|
config.Config.DeploymentRegion = storedConfig.DeploymentRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
configBytes, err := integrationstypes.MarshalJSON(config.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountRecord, err := a.accountsRepo.Upsert(
|
||||||
|
ctx, req.OrgID, a.GetName().String(), &req.AccountId, configBytes, nil, nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountRecord.Account(a.GetName()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *azureProvider) DisconnectAccount(ctx context.Context, orgID, accountID string) (*integrationstypes.CloudIntegration, error) {
|
||||||
|
account, err := a.accountsRepo.Get(ctx, orgID, a.GetName().String(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tsNow := time.Now()
|
||||||
|
account, err = a.accountsRepo.Upsert(
|
||||||
|
ctx, orgID, a.GetName().String(), &accountID, nil, nil, nil, &tsNow,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
@@ -1,94 +1 @@
|
|||||||
package cloudintegrations
|
package cloudintegrations
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServiceSummary struct {
|
|
||||||
services.Metadata
|
|
||||||
|
|
||||||
Config *types.CloudServiceConfig `json:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceDetails struct {
|
|
||||||
services.Definition
|
|
||||||
|
|
||||||
Config *types.CloudServiceConfig `json:"config"`
|
|
||||||
ConnectionStatus *ServiceConnectionStatus `json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountStatus struct {
|
|
||||||
Integration AccountIntegrationStatus `json:"integration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountIntegrationStatus struct {
|
|
||||||
LastHeartbeatTsMillis *int64 `json:"last_heartbeat_ts_ms"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogsConfig struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
S3Buckets map[string][]string `json:"s3_buckets,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetricsConfig struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceConnectionStatus struct {
|
|
||||||
Logs *SignalConnectionStatus `json:"logs"`
|
|
||||||
Metrics *SignalConnectionStatus `json:"metrics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignalConnectionStatus struct {
|
|
||||||
LastReceivedTsMillis int64 `json:"last_received_ts_ms"` // epoch milliseconds
|
|
||||||
LastReceivedFrom string `json:"last_received_from"` // resource identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompiledCollectionStrategy = services.CollectionStrategy
|
|
||||||
|
|
||||||
func NewCompiledCollectionStrategy(provider string) (*CompiledCollectionStrategy, error) {
|
|
||||||
if provider == "aws" {
|
|
||||||
return &CompiledCollectionStrategy{
|
|
||||||
Provider: "aws",
|
|
||||||
AWSMetrics: &services.AWSMetricsStrategy{},
|
|
||||||
AWSLogs: &services.AWSLogsStrategy{},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, errors.NewNotFoundf(services.CodeUnsupportedCloudProvider, "unsupported cloud provider: %s", provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for accumulating strategies for enabled services.
|
|
||||||
func AddServiceStrategy(serviceType string, cs *CompiledCollectionStrategy,
|
|
||||||
definitionStrat *services.CollectionStrategy, config *types.CloudServiceConfig) error {
|
|
||||||
if definitionStrat.Provider != cs.Provider {
|
|
||||||
return errors.NewInternalf(CodeMismatchCloudProvider, "can't add %s service strategy to compiled strategy for %s",
|
|
||||||
definitionStrat.Provider, cs.Provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cs.Provider == "aws" {
|
|
||||||
if config.Logs != nil && config.Logs.Enabled {
|
|
||||||
if serviceType == services.S3Sync {
|
|
||||||
// S3 bucket sync; No cloudwatch logs are appended for this service type;
|
|
||||||
// Though definition is populated with a custom cloudwatch group that helps in calculating logs connection status
|
|
||||||
cs.S3Buckets = config.Logs.S3Buckets
|
|
||||||
} else if definitionStrat.AWSLogs != nil { // services that includes a logs subscription
|
|
||||||
cs.AWSLogs.Subscriptions = append(
|
|
||||||
cs.AWSLogs.Subscriptions,
|
|
||||||
definitionStrat.AWSLogs.Subscriptions...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.Metrics != nil && config.Metrics.Enabled && definitionStrat.AWSMetrics != nil {
|
|
||||||
cs.AWSMetrics.StreamFilters = append(
|
|
||||||
cs.AWSMetrics.StreamFilters,
|
|
||||||
definitionStrat.AWSMetrics.StreamFilters...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.NewNotFoundf(services.CodeUnsupportedCloudProvider, "unsupported cloud provider: %s", cs.Provider)
|
|
||||||
}
|
|
||||||
|
|||||||
30
pkg/query-service/app/cloudintegrations/providerregistry.go
Normal file
30
pkg/query-service/app/cloudintegrations/providerregistry.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package cloudintegrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/querier"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/implawsprovider"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/implazureprovider"
|
||||||
|
integrationstore "github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/store"
|
||||||
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCloudProviderRegistry(
|
||||||
|
logger *slog.Logger,
|
||||||
|
store sqlstore.SQLStore,
|
||||||
|
querier querier.Querier,
|
||||||
|
) map[integrationstypes.CloudProviderType]integrationstypes.CloudProvider {
|
||||||
|
registry := make(map[integrationstypes.CloudProviderType]integrationstypes.CloudProvider)
|
||||||
|
|
||||||
|
accountsRepo := integrationstore.NewCloudProviderAccountsRepository(store)
|
||||||
|
serviceConfigRepo := integrationstore.NewServiceConfigRepository(store)
|
||||||
|
|
||||||
|
awsProviderImpl := implawsprovider.NewAWSCloudProvider(logger, accountsRepo, serviceConfigRepo, querier)
|
||||||
|
registry[integrationstypes.CloudProviderAWS] = awsProviderImpl
|
||||||
|
azureProviderImpl := implazureprovider.NewAzureCloudProvider(logger, accountsRepo, serviceConfigRepo, querier)
|
||||||
|
registry[integrationstypes.CloudProviderAzure] = azureProviderImpl
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
@@ -148,6 +148,146 @@
|
|||||||
"name": "aws_ApiGateway_Latency_sum",
|
"name": "aws_ApiGateway_Latency_sum",
|
||||||
"unit": "Milliseconds",
|
"unit": "Milliseconds",
|
||||||
"type": "Gauge"
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_4xx_sum",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_4xx_max",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_4xx_min",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_4xx_count",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_5xx_sum",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_5xx_max",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_5xx_min",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_5xx_count",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_DataProcessed_sum",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_DataProcessed_max",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_DataProcessed_min",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_DataProcessed_count",
|
||||||
|
"unit": "Bytes",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ExecutionError_sum",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ExecutionError_max",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ExecutionError_min",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ExecutionError_count",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ClientError_sum",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ClientError_max",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ClientError_min",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ClientError_count",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_IntegrationError_sum",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_IntegrationError_max",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_IntegrationError_min",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_IntegrationError_count",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ConnectCount_sum",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ConnectCount_max",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ConnectCount_min",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws_ApiGateway_ConnectCount_count",
|
||||||
|
"unit": "Count",
|
||||||
|
"type": "Gauge"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"logs": [
|
"logs": [
|
||||||
|
|||||||
@@ -391,4 +391,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -515,4 +515,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1928,7 +1928,7 @@
|
|||||||
"unit": "Percent",
|
"unit": "Percent",
|
||||||
"type": "Gauge",
|
"type": "Gauge",
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"telemetry_collection_strategy": {
|
"telemetry_collection_strategy": {
|
||||||
@@ -1951,4 +1951,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1088,4 +1088,3 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,4 +800,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,4 +127,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,4 +247,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
<svg id="f2f04349-8aee-4413-84c9-a9053611b319" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><defs><linearGradient id="ad4c4f96-09aa-4f91-ba10-5cb8ad530f74" x1="9" y1="15.83" x2="9" y2="5.79" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b3b3b3" /><stop offset="0.26" stop-color="#c1c1c1" /><stop offset="1" stop-color="#e6e6e6" /></linearGradient></defs><title>Icon-storage-86</title><path d="M.5,5.79h17a0,0,0,0,1,0,0v9.48a.57.57,0,0,1-.57.57H1.07a.57.57,0,0,1-.57-.57V5.79A0,0,0,0,1,.5,5.79Z" fill="url(#ad4c4f96-09aa-4f91-ba10-5cb8ad530f74)" /><path d="M1.07,2.17H16.93a.57.57,0,0,1,.57.57V5.79a0,0,0,0,1,0,0H.5a0,0,0,0,1,0,0V2.73A.57.57,0,0,1,1.07,2.17Z" fill="#37c2b1" /><path d="M2.81,6.89H15.18a.27.27,0,0,1,.26.27v1.4a.27.27,0,0,1-.26.27H2.81a.27.27,0,0,1-.26-.27V7.16A.27.27,0,0,1,2.81,6.89Z" fill="#fff" /><path d="M2.82,9.68H15.19a.27.27,0,0,1,.26.27v1.41a.27.27,0,0,1-.26.27H2.82a.27.27,0,0,1-.26-.27V10A.27.27,0,0,1,2.82,9.68Z" fill="#37c2b1" /><path d="M2.82,12.5H15.19a.27.27,0,0,1,.26.27v1.41a.27.27,0,0,1-.26.27H2.82a.27.27,0,0,1-.26-.27V12.77A.27.27,0,0,1,2.82,12.5Z" fill="#258277" /></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,252 @@
|
|||||||
|
{
|
||||||
|
"id": "blobstorage",
|
||||||
|
"title": "Blob Storage",
|
||||||
|
"icon": "file://icon.svg",
|
||||||
|
"overview": "file://overview.md",
|
||||||
|
"supported_signals": {
|
||||||
|
"metrics": true,
|
||||||
|
"logs": true
|
||||||
|
},
|
||||||
|
"data_collected": {
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"telemetry_collection_strategy": {
|
||||||
|
"azure_metrics": [
|
||||||
|
{
|
||||||
|
"category_type": "metrics",
|
||||||
|
"name": "Capacity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_type": "metrics",
|
||||||
|
"name": "Transaction"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"azure_logs": [
|
||||||
|
{
|
||||||
|
"category_type": "logs",
|
||||||
|
"name": "StorageRead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_type": "logs",
|
||||||
|
"name": "StorageWrite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_type": "logs",
|
||||||
|
"name": "StorageDelete"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"dashboards": [
|
||||||
|
{
|
||||||
|
"id": "overview",
|
||||||
|
"title": "Blob Storage Overview",
|
||||||
|
"description": "Overview of Blob Storage",
|
||||||
|
"definition": "file://assets/dashboards/overview.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Monitor Azure Blob Storage with SigNoz
|
||||||
|
Collect key Blob Storage metrics and view them with an out of the box dashboard.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
<svg id="f2f04349-8aee-4413-84c9-a9053611b319" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><defs><linearGradient id="ad4c4f96-09aa-4f91-ba10-5cb8ad530f74" x1="9" y1="15.83" x2="9" y2="5.79" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b3b3b3" /><stop offset="0.26" stop-color="#c1c1c1" /><stop offset="1" stop-color="#e6e6e6" /></linearGradient></defs><title>Icon-storage-86</title><path d="M.5,5.79h17a0,0,0,0,1,0,0v9.48a.57.57,0,0,1-.57.57H1.07a.57.57,0,0,1-.57-.57V5.79A0,0,0,0,1,.5,5.79Z" fill="url(#ad4c4f96-09aa-4f91-ba10-5cb8ad530f74)" /><path d="M1.07,2.17H16.93a.57.57,0,0,1,.57.57V5.79a0,0,0,0,1,0,0H.5a0,0,0,0,1,0,0V2.73A.57.57,0,0,1,1.07,2.17Z" fill="#37c2b1" /><path d="M2.81,6.89H15.18a.27.27,0,0,1,.26.27v1.4a.27.27,0,0,1-.26.27H2.81a.27.27,0,0,1-.26-.27V7.16A.27.27,0,0,1,2.81,6.89Z" fill="#fff" /><path d="M2.82,9.68H15.19a.27.27,0,0,1,.26.27v1.41a.27.27,0,0,1-.26.27H2.82a.27.27,0,0,1-.26-.27V10A.27.27,0,0,1,2.82,9.68Z" fill="#37c2b1" /><path d="M2.82,12.5H15.19a.27.27,0,0,1,.26.27v1.41a.27.27,0,0,1-.26.27H2.82a.27.27,0,0,1-.26-.27V12.77A.27.27,0,0,1,2.82,12.5Z" fill="#258277" /></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,247 @@
|
|||||||
|
{
|
||||||
|
"id": "frontdoor",
|
||||||
|
"title": "Front Door",
|
||||||
|
"icon": "file://icon.svg",
|
||||||
|
"overview": "file://overview.md",
|
||||||
|
"supported_signals": {
|
||||||
|
"metrics": true,
|
||||||
|
"logs": true
|
||||||
|
},
|
||||||
|
"data_collected": {
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_metric_1",
|
||||||
|
"unit": "Percent",
|
||||||
|
"type": "Gauge",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "placeholder_log_1",
|
||||||
|
"path": "placeholder.path.value",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"telemetry_collection_strategy": {
|
||||||
|
"azure_metrics": [
|
||||||
|
{
|
||||||
|
"category_type": "metrics",
|
||||||
|
"name": "Capacity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_type": "metrics",
|
||||||
|
"name": "Transaction"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"azure_logs": [
|
||||||
|
{
|
||||||
|
"category_type": "logs",
|
||||||
|
"name": "StorageRead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_type": "logs",
|
||||||
|
"name": "StorageWrite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_type": "logs",
|
||||||
|
"name": "StorageDelete"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"dashboards": [
|
||||||
|
{
|
||||||
|
"id": "overview",
|
||||||
|
"title": "Front Door Overview",
|
||||||
|
"description": "Overview of Blob Storage",
|
||||||
|
"definition": "file://assets/dashboards/overview.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Monitor Azure Front Door with SigNoz
|
||||||
|
Collect key Front Door metrics and view them with an out of the box dashboard.
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Metadata struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Definition struct {
|
|
||||||
Metadata
|
|
||||||
|
|
||||||
Overview string `json:"overview"` // markdown
|
|
||||||
|
|
||||||
Assets Assets `json:"assets"`
|
|
||||||
|
|
||||||
SupportedSignals SupportedSignals `json:"supported_signals"`
|
|
||||||
|
|
||||||
DataCollected DataCollected `json:"data_collected"`
|
|
||||||
|
|
||||||
Strategy *CollectionStrategy `json:"telemetry_collection_strategy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Assets struct {
|
|
||||||
Dashboards []Dashboard `json:"dashboards"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SupportedSignals struct {
|
|
||||||
Logs bool `json:"logs"`
|
|
||||||
Metrics bool `json:"metrics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataCollected struct {
|
|
||||||
Logs []CollectedLogAttribute `json:"logs"`
|
|
||||||
Metrics []CollectedMetric `json:"metrics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CollectedLogAttribute struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CollectedMetric struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Unit string `json:"unit"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CollectionStrategy struct {
|
|
||||||
Provider string `json:"provider"`
|
|
||||||
|
|
||||||
AWSMetrics *AWSMetricsStrategy `json:"aws_metrics,omitempty"`
|
|
||||||
AWSLogs *AWSLogsStrategy `json:"aws_logs,omitempty"`
|
|
||||||
S3Buckets map[string][]string `json:"s3_buckets,omitempty"` // Only available in S3 Sync Service Type
|
|
||||||
}
|
|
||||||
|
|
||||||
type AWSMetricsStrategy struct {
|
|
||||||
// to be used as https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-metricstream.html#cfn-cloudwatch-metricstream-includefilters
|
|
||||||
StreamFilters []struct {
|
|
||||||
// json tags here are in the shape expected by AWS API as detailed at
|
|
||||||
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-metricstream-metricstreamfilter.html
|
|
||||||
Namespace string `json:"Namespace"`
|
|
||||||
MetricNames []string `json:"MetricNames,omitempty"`
|
|
||||||
} `json:"cloudwatch_metric_stream_filters"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AWSLogsStrategy struct {
|
|
||||||
Subscriptions []struct {
|
|
||||||
// subscribe to all logs groups with specified prefix.
|
|
||||||
// eg: `/aws/rds/`
|
|
||||||
LogGroupNamePrefix string `json:"log_group_name_prefix"`
|
|
||||||
|
|
||||||
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html
|
|
||||||
// "" implies no filtering is required.
|
|
||||||
FilterPattern string `json:"filter_pattern"`
|
|
||||||
} `json:"cloudwatch_logs_subscriptions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dashboard struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Image string `json:"image"`
|
|
||||||
Definition *dashboardtypes.StorableDashboardData `json:"definition,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -2,128 +2,110 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
koanfJson "github.com/knadh/koanf/parsers/json"
|
koanfJson "github.com/knadh/koanf/parsers/json"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
S3Sync = "s3sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CodeUnsupportedCloudProvider = errors.MustNewCode("unsupported_cloud_provider")
|
CodeServiceDefinitionNotFound = errors.MustNewCode("service_definition_not_dound")
|
||||||
CodeUnsupportedServiceType = errors.MustNewCode("unsupported_service_type")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func List(cloudProvider string) ([]Definition, *model.ApiError) {
|
type ServicesProvider[T integrationstypes.Definition] struct {
|
||||||
cloudServices, found := supportedServices[cloudProvider]
|
definitions map[string]T
|
||||||
if !found || cloudServices == nil {
|
|
||||||
return nil, model.NotFoundError(fmt.Errorf(
|
|
||||||
"unsupported cloud provider: %s", cloudProvider,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
services := maps.Values(cloudServices)
|
|
||||||
sort.Slice(services, func(i, j int) bool {
|
|
||||||
return services[i].Id < services[j].Id
|
|
||||||
})
|
|
||||||
|
|
||||||
return services, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Map(cloudProvider string) (map[string]Definition, error) {
|
func (a *ServicesProvider[T]) ListServiceDefinitions(ctx context.Context) (map[string]T, error) {
|
||||||
cloudServices, found := supportedServices[cloudProvider]
|
return a.definitions, nil
|
||||||
if !found || cloudServices == nil {
|
}
|
||||||
return nil, errors.Newf(errors.TypeNotFound, CodeUnsupportedCloudProvider, "unsupported cloud provider: %s", cloudProvider)
|
|
||||||
|
func (a *ServicesProvider[T]) GetServiceDefinition(ctx context.Context, serviceName string) (T, error) {
|
||||||
|
def, ok := a.definitions[serviceName]
|
||||||
|
if !ok {
|
||||||
|
return *new(T), errors.NewNotFoundf(CodeServiceDefinitionNotFound, "azure service definition not found: %s", serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAWSCloudProviderServices() (*ServicesProvider[*integrationstypes.AWSDefinition], error) {
|
||||||
|
definitions, err := readAllServiceDefinitions(integrationstypes.CloudProviderAWS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceDefinitions := make(map[string]*integrationstypes.AWSDefinition)
|
||||||
|
for id, def := range definitions {
|
||||||
|
typedDef, ok := def.(*integrationstypes.AWSDefinition)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid type for AWS service definition %s", id)
|
||||||
|
}
|
||||||
|
serviceDefinitions[id] = typedDef
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ServicesProvider[*integrationstypes.AWSDefinition]{
|
||||||
|
definitions: serviceDefinitions,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAzureCloudProviderServices() (*ServicesProvider[*integrationstypes.AzureDefinition], error) {
|
||||||
|
definitions, err := readAllServiceDefinitions(integrationstypes.CloudProviderAzure)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceDefinitions := make(map[string]*integrationstypes.AzureDefinition)
|
||||||
|
for id, def := range definitions {
|
||||||
|
typedDef, ok := def.(*integrationstypes.AzureDefinition)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid type for Azure service definition %s", id)
|
||||||
|
}
|
||||||
|
serviceDefinitions[id] = typedDef
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ServicesProvider[*integrationstypes.AzureDefinition]{
|
||||||
|
definitions: serviceDefinitions,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of API. Logic for reading service definition files follows
|
||||||
|
|
||||||
|
//go:embed definitions/*
|
||||||
|
var definitionFiles embed.FS
|
||||||
|
|
||||||
|
func readAllServiceDefinitions(cloudProvider valuer.String) (map[string]any, error) {
|
||||||
|
rootDirName := "definitions"
|
||||||
|
|
||||||
|
cloudProviderDirPath := path.Join(rootDirName, cloudProvider.String())
|
||||||
|
|
||||||
|
cloudServices, err := readServiceDefinitionsFromDir(cloudProvider, cloudProviderDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cloudServices) < 1 {
|
||||||
|
return nil, errors.NewInternalf(errors.CodeInternal, "no service definitions found in %s", cloudProviderDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloudServices, nil
|
return cloudServices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetServiceDefinition(cloudProvider, serviceType string) (*Definition, error) {
|
func readServiceDefinitionsFromDir(cloudProvider valuer.String, cloudProviderDirPath string) (map[string]any, error) {
|
||||||
cloudServices := supportedServices[cloudProvider]
|
|
||||||
if cloudServices == nil {
|
|
||||||
return nil, errors.Newf(errors.TypeNotFound, CodeUnsupportedCloudProvider, "unsupported cloud provider: %s", cloudProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc, exists := cloudServices[serviceType]
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.Newf(errors.TypeNotFound, CodeUnsupportedServiceType, "%s service not found: %s", cloudProvider, serviceType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &svc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of API. Logic for reading service definition files follows
|
|
||||||
|
|
||||||
// Service details read from ./serviceDefinitions
|
|
||||||
// { "providerName": { "service_id": {...}} }
|
|
||||||
var supportedServices map[string]map[string]Definition
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := readAllServiceDefinitions()
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf(
|
|
||||||
"couldn't read cloud service definitions: %w", err,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed definitions/*
|
|
||||||
var definitionFiles embed.FS
|
|
||||||
|
|
||||||
func readAllServiceDefinitions() error {
|
|
||||||
supportedServices = map[string]map[string]Definition{}
|
|
||||||
|
|
||||||
rootDirName := "definitions"
|
|
||||||
|
|
||||||
cloudProviderDirs, err := fs.ReadDir(definitionFiles, rootDirName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't read dirs in %s: %w", rootDirName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range cloudProviderDirs {
|
|
||||||
if !d.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudProvider := d.Name()
|
|
||||||
|
|
||||||
cloudProviderDirPath := path.Join(rootDirName, cloudProvider)
|
|
||||||
cloudServices, err := readServiceDefinitionsFromDir(cloudProvider, cloudProviderDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't read %s service definitions: %w", cloudProvider, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cloudServices) < 1 {
|
|
||||||
return fmt.Errorf("no %s services could be read", cloudProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
supportedServices[cloudProvider] = cloudServices
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readServiceDefinitionsFromDir(cloudProvider string, cloudProviderDirPath string) (
|
|
||||||
map[string]Definition, error,
|
|
||||||
) {
|
|
||||||
svcDefDirs, err := fs.ReadDir(definitionFiles, cloudProviderDirPath)
|
svcDefDirs, err := fs.ReadDir(definitionFiles, cloudProviderDirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't list integrations dirs: %w", err)
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't list integrations dirs")
|
||||||
}
|
}
|
||||||
|
|
||||||
svcDefs := map[string]Definition{}
|
svcDefs := make(map[string]any)
|
||||||
|
|
||||||
for _, d := range svcDefDirs {
|
for _, d := range svcDefDirs {
|
||||||
if !d.IsDir() {
|
if !d.IsDir() {
|
||||||
@@ -133,103 +115,73 @@ func readServiceDefinitionsFromDir(cloudProvider string, cloudProviderDirPath st
|
|||||||
svcDirPath := path.Join(cloudProviderDirPath, d.Name())
|
svcDirPath := path.Join(cloudProviderDirPath, d.Name())
|
||||||
s, err := readServiceDefinition(cloudProvider, svcDirPath)
|
s, err := readServiceDefinition(cloudProvider, svcDirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't read svc definition for %s: %w", d.Name(), err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := svcDefs[s.Id]
|
_, exists := svcDefs[s.GetId()]
|
||||||
if exists {
|
if exists {
|
||||||
return nil, fmt.Errorf(
|
return nil, errors.NewInternalf(errors.CodeInternal, "duplicate service definition for id %s at %s", s.GetId(), d.Name())
|
||||||
"duplicate service definition for id %s at %s", s.Id, d.Name(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
svcDefs[s.Id] = *s
|
svcDefs[s.GetId()] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
return svcDefs, nil
|
return svcDefs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readServiceDefinition(cloudProvider string, svcDirpath string) (*Definition, error) {
|
func readServiceDefinition(cloudProvider valuer.String, svcDirpath string) (integrationstypes.Definition, error) {
|
||||||
integrationJsonPath := path.Join(svcDirpath, "integration.json")
|
integrationJsonPath := path.Join(svcDirpath, "integration.json")
|
||||||
|
|
||||||
serializedSpec, err := definitionFiles.ReadFile(integrationJsonPath)
|
serializedSpec, err := definitionFiles.ReadFile(integrationJsonPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read integration definition in %s", svcDirpath)
|
||||||
"couldn't find integration.json in %s: %w",
|
|
||||||
svcDirpath, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
integrationSpec, err := koanfJson.Parser().Unmarshal(serializedSpec)
|
integrationSpec, err := koanfJson.Parser().Unmarshal(serializedSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't parse integration definition in %s", svcDirpath)
|
||||||
"couldn't parse integration.json from %s: %w",
|
|
||||||
integrationJsonPath, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrated, err := integrations.HydrateFileUris(
|
hydrated, err := integrations.HydrateFileUris(integrationSpec, definitionFiles, svcDirpath)
|
||||||
integrationSpec, definitionFiles, svcDirpath,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't hydrate integration definition in %s", svcDirpath)
|
||||||
"couldn't hydrate files referenced in service definition %s: %w",
|
|
||||||
integrationJsonPath, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
hydratedSpec := hydrated.(map[string]any)
|
hydratedSpec := hydrated.(map[string]any)
|
||||||
|
|
||||||
serviceDef, err := ParseStructWithJsonTagsFromMap[Definition](hydratedSpec)
|
var serviceDef integrationstypes.Definition
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
switch cloudProvider {
|
||||||
"couldn't parse hydrated JSON spec read from %s: %w",
|
case integrationstypes.CloudProviderAWS:
|
||||||
integrationJsonPath, err,
|
serviceDef = &integrationstypes.AWSDefinition{}
|
||||||
)
|
case integrationstypes.CloudProviderAzure:
|
||||||
|
serviceDef = &integrationstypes.AzureDefinition{}
|
||||||
|
default:
|
||||||
|
// ideally this shouldn't happen hence throwing internal error
|
||||||
|
return nil, errors.NewInternalf(errors.CodeInternal, "unsupported cloud provider: %s", cloudProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateServiceDefinition(serviceDef)
|
err = parseStructWithJsonTagsFromMap(hydratedSpec, serviceDef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid service definition %s: %w", serviceDef.Id, err)
|
return nil, err
|
||||||
|
}
|
||||||
|
err = serviceDef.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceDef.Strategy.Provider = cloudProvider
|
|
||||||
|
|
||||||
return serviceDef, nil
|
return serviceDef, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateServiceDefinition(s *Definition) error {
|
func parseStructWithJsonTagsFromMap(data map[string]any, target interface{}) error {
|
||||||
// Validate dashboard data
|
|
||||||
seenDashboardIds := map[string]interface{}{}
|
|
||||||
for _, dd := range s.Assets.Dashboards {
|
|
||||||
if _, seen := seenDashboardIds[dd.Id]; seen {
|
|
||||||
return fmt.Errorf("multiple dashboards found with id %s", dd.Id)
|
|
||||||
}
|
|
||||||
seenDashboardIds[dd.Id] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Strategy == nil {
|
|
||||||
return fmt.Errorf("telemetry_collection_strategy is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// potentially more to follow
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseStructWithJsonTagsFromMap[StructType any](data map[string]any) (
|
|
||||||
*StructType, error,
|
|
||||||
) {
|
|
||||||
mapJson, err := json.Marshal(data)
|
mapJson, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't marshal map to json: %w", err)
|
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't marshal service definition json data")
|
||||||
}
|
}
|
||||||
|
|
||||||
var res StructType
|
|
||||||
decoder := json.NewDecoder(bytes.NewReader(mapJson))
|
decoder := json.NewDecoder(bytes.NewReader(mapJson))
|
||||||
decoder.DisallowUnknownFields()
|
decoder.DisallowUnknownFields()
|
||||||
err = decoder.Decode(&res)
|
err = decoder.Decode(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't unmarshal json back to struct: %w", err)
|
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't unmarshal service definition json data")
|
||||||
}
|
}
|
||||||
return &res, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,3 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
// TODO: add more tests for services package
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAvailableServices(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
// should be able to list available services.
|
|
||||||
_, apiErr := List("bad-cloud-provider")
|
|
||||||
require.NotNil(apiErr)
|
|
||||||
require.Equal(model.ErrorNotFound, apiErr.Type())
|
|
||||||
|
|
||||||
awsSvcs, apiErr := List("aws")
|
|
||||||
require.Nil(apiErr)
|
|
||||||
require.Greater(len(awsSvcs), 0)
|
|
||||||
|
|
||||||
// should be able to get details of a service
|
|
||||||
_, err := GetServiceDefinition(
|
|
||||||
"aws", "bad-service-id",
|
|
||||||
)
|
|
||||||
require.NotNil(err)
|
|
||||||
require.True(errors.Ast(err, errors.TypeNotFound))
|
|
||||||
|
|
||||||
svc, err := GetServiceDefinition(
|
|
||||||
"aws", awsSvcs[0].Id,
|
|
||||||
)
|
|
||||||
require.Nil(err)
|
|
||||||
require.Equal(*svc, awsSvcs[0])
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,55 +1,57 @@
|
|||||||
package cloudintegrations
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cloudProviderAccountsRepository interface {
|
var (
|
||||||
listConnected(ctx context.Context, orgId string, provider string) ([]types.CloudIntegration, *model.ApiError)
|
CodeCloudIntegrationAccountNotFound errors.Code = errors.MustNewCode("cloud_integration_account_not_found")
|
||||||
|
)
|
||||||
|
|
||||||
get(ctx context.Context, orgId string, provider string, id string) (*types.CloudIntegration, *model.ApiError)
|
type CloudProviderAccountsRepository interface {
|
||||||
|
ListConnected(ctx context.Context, orgId string, provider string) ([]integrationstypes.CloudIntegration, error)
|
||||||
|
|
||||||
getConnectedCloudAccount(ctx context.Context, orgId string, provider string, accountID string) (*types.CloudIntegration, *model.ApiError)
|
Get(ctx context.Context, orgId string, provider string, id string) (*integrationstypes.CloudIntegration, error)
|
||||||
|
|
||||||
|
GetConnectedCloudAccount(ctx context.Context, orgId, provider string, accountID string) (*integrationstypes.CloudIntegration, error)
|
||||||
|
|
||||||
// Insert an account or update it by (cloudProvider, id)
|
// Insert an account or update it by (cloudProvider, id)
|
||||||
// for specified non-empty fields
|
// for specified non-empty fields
|
||||||
upsert(
|
Upsert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
provider string,
|
provider string,
|
||||||
id *string,
|
id *string,
|
||||||
config *types.AccountConfig,
|
config []byte,
|
||||||
accountId *string,
|
accountId *string,
|
||||||
agentReport *types.AgentReport,
|
agentReport *integrationstypes.AgentReport,
|
||||||
removedAt *time.Time,
|
removedAt *time.Time,
|
||||||
) (*types.CloudIntegration, *model.ApiError)
|
) (*integrationstypes.CloudIntegration, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCloudProviderAccountsRepository(store sqlstore.SQLStore) (
|
func NewCloudProviderAccountsRepository(store sqlstore.SQLStore) CloudProviderAccountsRepository {
|
||||||
*cloudProviderAccountsSQLRepository, error,
|
return &cloudProviderAccountsSQLRepository{store: store}
|
||||||
) {
|
|
||||||
return &cloudProviderAccountsSQLRepository{
|
|
||||||
store: store,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type cloudProviderAccountsSQLRepository struct {
|
type cloudProviderAccountsSQLRepository struct {
|
||||||
store sqlstore.SQLStore
|
store sqlstore.SQLStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cloudProviderAccountsSQLRepository) listConnected(
|
func (r *cloudProviderAccountsSQLRepository) ListConnected(
|
||||||
ctx context.Context, orgId string, cloudProvider string,
|
ctx context.Context, orgId string, cloudProvider string,
|
||||||
) ([]types.CloudIntegration, *model.ApiError) {
|
) ([]integrationstypes.CloudIntegration, error) {
|
||||||
accounts := []types.CloudIntegration{}
|
accounts := []integrationstypes.CloudIntegration{}
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model(&accounts).
|
Model(&accounts).
|
||||||
@@ -62,18 +64,17 @@ func (r *cloudProviderAccountsSQLRepository) listConnected(
|
|||||||
Scan(ctx)
|
Scan(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
slog.ErrorContext(ctx, "error querying connected cloud accounts", "error", err)
|
||||||
"could not query connected cloud accounts: %w", err,
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "could not query connected cloud accounts")
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts, nil
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cloudProviderAccountsSQLRepository) get(
|
func (r *cloudProviderAccountsSQLRepository) Get(
|
||||||
ctx context.Context, orgId string, provider string, id string,
|
ctx context.Context, orgId string, provider string, id string,
|
||||||
) (*types.CloudIntegration, *model.ApiError) {
|
) (*integrationstypes.CloudIntegration, error) {
|
||||||
var result types.CloudIntegration
|
var result integrationstypes.CloudIntegration
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model(&result).
|
Model(&result).
|
||||||
@@ -82,23 +83,25 @@ func (r *cloudProviderAccountsSQLRepository) get(
|
|||||||
Where("id = ?", id).
|
Where("id = ?", id).
|
||||||
Scan(ctx)
|
Scan(ctx)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err != nil {
|
||||||
return nil, model.NotFoundError(fmt.Errorf(
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
"couldn't find account with Id %s", id,
|
return nil, errors.WrapNotFoundf(
|
||||||
))
|
err,
|
||||||
} else if err != nil {
|
CodeCloudIntegrationAccountNotFound,
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
"couldn't find account with Id %s", id,
|
||||||
"couldn't query cloud provider accounts: %w", err,
|
)
|
||||||
))
|
}
|
||||||
|
|
||||||
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't query cloud provider account")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cloudProviderAccountsSQLRepository) getConnectedCloudAccount(
|
func (r *cloudProviderAccountsSQLRepository) GetConnectedCloudAccount(
|
||||||
ctx context.Context, orgId string, provider string, accountId string,
|
ctx context.Context, orgId string, provider string, accountId string,
|
||||||
) (*types.CloudIntegration, *model.ApiError) {
|
) (*integrationstypes.CloudIntegration, error) {
|
||||||
var result types.CloudIntegration
|
var result integrationstypes.CloudIntegration
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model(&result).
|
Model(&result).
|
||||||
@@ -109,29 +112,25 @@ func (r *cloudProviderAccountsSQLRepository) getConnectedCloudAccount(
|
|||||||
Where("removed_at is NULL").
|
Where("removed_at is NULL").
|
||||||
Scan(ctx)
|
Scan(ctx)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, model.NotFoundError(fmt.Errorf(
|
return nil, errors.WrapNotFoundf(err, CodeCloudIntegrationAccountNotFound, "couldn't find connected cloud account %s", accountId)
|
||||||
"couldn't find connected cloud account %s", accountId,
|
|
||||||
))
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't query cloud provider account")
|
||||||
"couldn't query cloud provider accounts: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cloudProviderAccountsSQLRepository) upsert(
|
func (r *cloudProviderAccountsSQLRepository) Upsert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
provider string,
|
provider string,
|
||||||
id *string,
|
id *string,
|
||||||
config *types.AccountConfig,
|
config []byte,
|
||||||
accountId *string,
|
accountId *string,
|
||||||
agentReport *types.AgentReport,
|
agentReport *integrationstypes.AgentReport,
|
||||||
removedAt *time.Time,
|
removedAt *time.Time,
|
||||||
) (*types.CloudIntegration, *model.ApiError) {
|
) (*integrationstypes.CloudIntegration, error) {
|
||||||
// Insert
|
// Insert
|
||||||
if id == nil {
|
if id == nil {
|
||||||
temp := valuer.GenerateUUID().StringValue()
|
temp := valuer.GenerateUUID().StringValue()
|
||||||
@@ -181,7 +180,7 @@ func (r *cloudProviderAccountsSQLRepository) upsert(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
integration := types.CloudIntegration{
|
integration := integrationstypes.CloudIntegration{
|
||||||
OrgID: orgId,
|
OrgID: orgId,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Identifiable: types.Identifiable{ID: valuer.MustNewUUID(*id)},
|
Identifiable: types.Identifiable{ID: valuer.MustNewUUID(*id)},
|
||||||
@@ -189,28 +188,25 @@ func (r *cloudProviderAccountsSQLRepository) upsert(
|
|||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
},
|
},
|
||||||
Config: config,
|
Config: string(config),
|
||||||
AccountID: accountId,
|
AccountID: accountId,
|
||||||
LastAgentReport: agentReport,
|
LastAgentReport: agentReport,
|
||||||
RemovedAt: removedAt,
|
RemovedAt: removedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, dbErr := r.store.BunDB().NewInsert().
|
_, err := r.store.BunDB().NewInsert().
|
||||||
Model(&integration).
|
Model(&integration).
|
||||||
On(onConflictClause).
|
On(onConflictClause).
|
||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
|
|
||||||
if dbErr != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't upsert cloud integration account")
|
||||||
"could not upsert cloud account record: %w", dbErr,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertedAccount, apiErr := r.get(ctx, orgId, provider, *id)
|
upsertedAccount, err := r.Get(ctx, orgId, provider, *id)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
slog.ErrorContext(ctx, "error upserting cloud integration account", "error", err)
|
||||||
"couldn't fetch upserted account by id: %w", apiErr.ToError(),
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't get upserted cloud integration account")
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return upsertedAccount, nil
|
return upsertedAccount, nil
|
||||||
@@ -1,64 +1,63 @@
|
|||||||
package cloudintegrations
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CodeServiceConfigNotFound = errors.MustNewCode("service_config_not_found")
|
||||||
|
)
|
||||||
|
|
||||||
type ServiceConfigDatabase interface {
|
type ServiceConfigDatabase interface {
|
||||||
get(
|
Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgID string,
|
orgID string,
|
||||||
cloudAccountId string,
|
cloudAccountId string,
|
||||||
serviceType string,
|
serviceType string,
|
||||||
) (*types.CloudServiceConfig, *model.ApiError)
|
) ([]byte, error)
|
||||||
|
|
||||||
upsert(
|
Upsert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgID string,
|
orgID string,
|
||||||
cloudProvider string,
|
cloudProvider string,
|
||||||
cloudAccountId string,
|
cloudAccountId string,
|
||||||
serviceId string,
|
serviceId string,
|
||||||
config types.CloudServiceConfig,
|
config []byte,
|
||||||
) (*types.CloudServiceConfig, *model.ApiError)
|
) ([]byte, error)
|
||||||
|
|
||||||
getAllForAccount(
|
GetAllForAccount(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgID string,
|
orgID string,
|
||||||
cloudAccountId string,
|
cloudAccountId string,
|
||||||
) (
|
) (
|
||||||
configsBySvcId map[string]*types.CloudServiceConfig,
|
map[string][]byte,
|
||||||
apiErr *model.ApiError,
|
error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServiceConfigRepository(store sqlstore.SQLStore) (
|
func NewServiceConfigRepository(store sqlstore.SQLStore) ServiceConfigDatabase {
|
||||||
*serviceConfigSQLRepository, error,
|
return &serviceConfigSQLRepository{store: store}
|
||||||
) {
|
|
||||||
return &serviceConfigSQLRepository{
|
|
||||||
store: store,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type serviceConfigSQLRepository struct {
|
type serviceConfigSQLRepository struct {
|
||||||
store sqlstore.SQLStore
|
store sqlstore.SQLStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *serviceConfigSQLRepository) get(
|
func (r *serviceConfigSQLRepository) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgID string,
|
orgID string,
|
||||||
cloudAccountId string,
|
cloudAccountId string,
|
||||||
serviceType string,
|
serviceType string,
|
||||||
) (*types.CloudServiceConfig, *model.ApiError) {
|
) ([]byte, error) {
|
||||||
|
var result integrationstypes.CloudIntegrationService
|
||||||
var result types.CloudIntegrationService
|
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model(&result).
|
Model(&result).
|
||||||
@@ -67,36 +66,30 @@ func (r *serviceConfigSQLRepository) get(
|
|||||||
Where("ci.id = ?", cloudAccountId).
|
Where("ci.id = ?", cloudAccountId).
|
||||||
Where("cis.type = ?", serviceType).
|
Where("cis.type = ?", serviceType).
|
||||||
Scan(ctx)
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errors.WrapNotFoundf(err, CodeServiceConfigNotFound, "couldn't find config for cloud account %s", cloudAccountId)
|
||||||
|
}
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't query cloud service config")
|
||||||
return nil, model.NotFoundError(fmt.Errorf(
|
|
||||||
"couldn't find config for cloud account %s",
|
|
||||||
cloudAccountId,
|
|
||||||
))
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
|
||||||
"couldn't query cloud service config: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result.Config, nil
|
return []byte(result.Config), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *serviceConfigSQLRepository) upsert(
|
func (r *serviceConfigSQLRepository) Upsert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgID string,
|
orgID string,
|
||||||
cloudProvider string,
|
cloudProvider string,
|
||||||
cloudAccountId string,
|
cloudAccountId string,
|
||||||
serviceId string,
|
serviceId string,
|
||||||
config types.CloudServiceConfig,
|
config []byte,
|
||||||
) (*types.CloudServiceConfig, *model.ApiError) {
|
) ([]byte, error) {
|
||||||
|
|
||||||
// get cloud integration id from account id
|
// get cloud integration id from account id
|
||||||
// if the account is not connected, we don't need to upsert the config
|
// if the account is not connected, we don't need to upsert the config
|
||||||
var cloudIntegrationId string
|
var cloudIntegrationId string
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model((*types.CloudIntegration)(nil)).
|
Model((*integrationstypes.CloudIntegration)(nil)).
|
||||||
Column("id").
|
Column("id").
|
||||||
Where("provider = ?", cloudProvider).
|
Where("provider = ?", cloudProvider).
|
||||||
Where("account_id = ?", cloudAccountId).
|
Where("account_id = ?", cloudAccountId).
|
||||||
@@ -104,20 +97,24 @@ func (r *serviceConfigSQLRepository) upsert(
|
|||||||
Where("removed_at is NULL").
|
Where("removed_at is NULL").
|
||||||
Where("last_agent_report is not NULL").
|
Where("last_agent_report is not NULL").
|
||||||
Scan(ctx, &cloudIntegrationId)
|
Scan(ctx, &cloudIntegrationId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
"couldn't query cloud integration id: %w", err,
|
return nil, errors.WrapNotFoundf(
|
||||||
))
|
err,
|
||||||
|
CodeCloudIntegrationAccountNotFound,
|
||||||
|
"couldn't find active cloud integration account",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't query cloud integration id")
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceConfig := types.CloudIntegrationService{
|
serviceConfig := integrationstypes.CloudIntegrationService{
|
||||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||||
TimeAuditable: types.TimeAuditable{
|
TimeAuditable: types.TimeAuditable{
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
},
|
},
|
||||||
Config: config,
|
Config: string(config),
|
||||||
Type: serviceId,
|
Type: serviceId,
|
||||||
CloudIntegrationID: cloudIntegrationId,
|
CloudIntegrationID: cloudIntegrationId,
|
||||||
}
|
}
|
||||||
@@ -126,21 +123,18 @@ func (r *serviceConfigSQLRepository) upsert(
|
|||||||
On("conflict(cloud_integration_id, type) do update set config=excluded.config, updated_at=excluded.updated_at").
|
On("conflict(cloud_integration_id, type) do update set config=excluded.config, updated_at=excluded.updated_at").
|
||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't upsert cloud service config")
|
||||||
"could not upsert cloud service config: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &serviceConfig.Config, nil
|
return config, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *serviceConfigSQLRepository) getAllForAccount(
|
func (r *serviceConfigSQLRepository) GetAllForAccount(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgID string,
|
orgID string,
|
||||||
cloudAccountId string,
|
cloudAccountId string,
|
||||||
) (map[string]*types.CloudServiceConfig, *model.ApiError) {
|
) (map[string][]byte, error) {
|
||||||
serviceConfigs := []types.CloudIntegrationService{}
|
var serviceConfigs []integrationstypes.CloudIntegrationService
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model(&serviceConfigs).
|
Model(&serviceConfigs).
|
||||||
@@ -149,15 +143,13 @@ func (r *serviceConfigSQLRepository) getAllForAccount(
|
|||||||
Where("ci.org_id = ?", orgID).
|
Where("ci.org_id = ?", orgID).
|
||||||
Scan(ctx)
|
Scan(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't query service configs from db")
|
||||||
"could not query service configs from db: %w", err,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := map[string]*types.CloudServiceConfig{}
|
result := make(map[string][]byte)
|
||||||
|
|
||||||
for _, r := range serviceConfigs {
|
for _, r := range serviceConfigs {
|
||||||
result[r.Type] = &r.Config
|
result[r.Type] = []byte(r.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -6,11 +6,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
|
||||||
"github.com/SigNoz/signoz/pkg/flagger"
|
|
||||||
"github.com/SigNoz/signoz/pkg/modules/thirdpartyapi"
|
|
||||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@@ -25,14 +21,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
|
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/flagger"
|
||||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/licensing"
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
"github.com/SigNoz/signoz/pkg/modules/thirdpartyapi"
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer"
|
"github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer"
|
||||||
|
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||||
"github.com/SigNoz/signoz/pkg/signoz"
|
"github.com/SigNoz/signoz/pkg/signoz"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
|
|
||||||
@@ -44,7 +45,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/contextlinks"
|
"github.com/SigNoz/signoz/pkg/contextlinks"
|
||||||
traceFunnelsModule "github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
traceFunnelsModule "github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/inframetrics"
|
"github.com/SigNoz/signoz/pkg/query-service/app/inframetrics"
|
||||||
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/logs"
|
"github.com/SigNoz/signoz/pkg/query-service/app/logs"
|
||||||
@@ -113,7 +113,7 @@ type APIHandler struct {
|
|||||||
|
|
||||||
IntegrationsController *integrations.Controller
|
IntegrationsController *integrations.Controller
|
||||||
|
|
||||||
CloudIntegrationsController *cloudintegrations.Controller
|
cloudIntegrationsRegistry map[integrationstypes.CloudProviderType]integrationstypes.CloudProvider
|
||||||
|
|
||||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||||
|
|
||||||
@@ -162,9 +162,6 @@ type APIHandlerOpts struct {
|
|||||||
// Integrations
|
// Integrations
|
||||||
IntegrationsController *integrations.Controller
|
IntegrationsController *integrations.Controller
|
||||||
|
|
||||||
// Cloud Provider Integrations
|
|
||||||
CloudIntegrationsController *cloudintegrations.Controller
|
|
||||||
|
|
||||||
// Log parsing pipelines
|
// Log parsing pipelines
|
||||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||||
|
|
||||||
@@ -180,6 +177,8 @@ type APIHandlerOpts struct {
|
|||||||
QueryParserAPI *queryparser.API
|
QueryParserAPI *queryparser.API
|
||||||
|
|
||||||
Signoz *signoz.SigNoz
|
Signoz *signoz.SigNoz
|
||||||
|
|
||||||
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIHandler returns an APIHandler
|
// NewAPIHandler returns an APIHandler
|
||||||
@@ -215,12 +214,18 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
|||||||
summaryService := metricsexplorer.NewSummaryService(opts.Reader, opts.RuleManager, opts.Signoz.Modules.Dashboard)
|
summaryService := metricsexplorer.NewSummaryService(opts.Reader, opts.RuleManager, opts.Signoz.Modules.Dashboard)
|
||||||
//quickFilterModule := quickfilter.NewAPI(opts.QuickFilterModule)
|
//quickFilterModule := quickfilter.NewAPI(opts.QuickFilterModule)
|
||||||
|
|
||||||
|
cloudIntegrationsRegistry := cloudintegrations.NewCloudProviderRegistry(
|
||||||
|
opts.Logger,
|
||||||
|
opts.Signoz.SQLStore,
|
||||||
|
opts.Signoz.Querier,
|
||||||
|
)
|
||||||
|
|
||||||
aH := &APIHandler{
|
aH := &APIHandler{
|
||||||
reader: opts.Reader,
|
reader: opts.Reader,
|
||||||
temporalityMap: make(map[string]map[v3.Temporality]bool),
|
temporalityMap: make(map[string]map[v3.Temporality]bool),
|
||||||
ruleManager: opts.RuleManager,
|
ruleManager: opts.RuleManager,
|
||||||
IntegrationsController: opts.IntegrationsController,
|
IntegrationsController: opts.IntegrationsController,
|
||||||
CloudIntegrationsController: opts.CloudIntegrationsController,
|
cloudIntegrationsRegistry: cloudIntegrationsRegistry,
|
||||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||||
querier: querier,
|
querier: querier,
|
||||||
querierV2: querierv2,
|
querierV2: querierv2,
|
||||||
@@ -1217,13 +1222,22 @@ func (aH *APIHandler) Get(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboard := new(dashboardtypes.Dashboard)
|
dashboard := new(dashboardtypes.Dashboard)
|
||||||
if aH.CloudIntegrationsController.IsCloudIntegrationDashboardUuid(id) {
|
if integrationstypes.IsCloudIntegrationDashboardUuid(id) {
|
||||||
cloudIntegrationDashboard, apiErr := aH.CloudIntegrationsController.GetDashboardById(ctx, orgID, id)
|
cloudProvider, err := integrationstypes.GetCloudProviderFromDashboardID(id)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
render.Error(rw, errorsV2.Wrapf(apiErr, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to get dashboard"))
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dashboard = cloudIntegrationDashboard
|
|
||||||
|
integrationDashboard, err := aH.cloudIntegrationsRegistry[cloudProvider].GetDashboard(ctx, &integrationstypes.GettableDashboard{
|
||||||
|
ID: id,
|
||||||
|
OrgID: orgID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard = integrationDashboard
|
||||||
} 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)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@@ -1287,11 +1301,13 @@ 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)
|
for _, provider := range aH.cloudIntegrationsRegistry {
|
||||||
if apiErr != nil {
|
cloudIntegrationDashboards, err := provider.GetAvailableDashboards(ctx, orgID)
|
||||||
zap.L().Error("failed to get dashboards for cloud integrations", zap.Error(apiErr))
|
if err != nil {
|
||||||
} else {
|
zap.L().Error("failed to get dashboards for cloud integrations", zap.Error(apiErr))
|
||||||
dashboards = append(dashboards, cloudIntegrationDashboards...)
|
} else {
|
||||||
|
dashboards = append(dashboards, cloudIntegrationDashboards...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gettableDashboards, err := dashboardtypes.NewGettableDashboardsFromDashboards(dashboards)
|
gettableDashboards, err := dashboardtypes.NewGettableDashboardsFromDashboards(dashboards)
|
||||||
@@ -3267,15 +3283,15 @@ func (aH *APIHandler) GetIntegrationConnectionStatus(w http.ResponseWriter, r *h
|
|||||||
lookbackSeconds = 15 * 60
|
lookbackSeconds = 15 * 60
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionStatus, apiErr := aH.calculateConnectionStatus(
|
connectionStatus, err := aH.calculateConnectionStatus(
|
||||||
r.Context(), orgID, connectionTests, lookbackSeconds,
|
r.Context(), orgID, connectionTests, lookbackSeconds,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
RespondError(w, apiErr, "Failed to calculate integration connection status")
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aH.Respond(w, connectionStatus)
|
render.Success(w, http.StatusOK, connectionStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) calculateConnectionStatus(
|
func (aH *APIHandler) calculateConnectionStatus(
|
||||||
@@ -3283,10 +3299,11 @@ func (aH *APIHandler) calculateConnectionStatus(
|
|||||||
orgID valuer.UUID,
|
orgID valuer.UUID,
|
||||||
connectionTests *integrations.IntegrationConnectionTests,
|
connectionTests *integrations.IntegrationConnectionTests,
|
||||||
lookbackSeconds int64,
|
lookbackSeconds int64,
|
||||||
) (*integrations.IntegrationConnectionStatus, *model.ApiError) {
|
) (*integrations.IntegrationConnectionStatus, error) {
|
||||||
// Calculate connection status for signals in parallel
|
// Calculate connection status for signals in parallel
|
||||||
|
|
||||||
result := &integrations.IntegrationConnectionStatus{}
|
result := &integrations.IntegrationConnectionStatus{}
|
||||||
|
// TODO: migrate to errors package
|
||||||
errors := []*model.ApiError{}
|
errors := []*model.ApiError{}
|
||||||
var resultLock sync.Mutex
|
var resultLock sync.Mutex
|
||||||
|
|
||||||
@@ -3484,12 +3501,14 @@ func (aH *APIHandler) UninstallIntegration(w http.ResponseWriter, r *http.Reques
|
|||||||
aH.Respond(w, map[string]interface{}{})
|
aH.Respond(w, map[string]interface{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// cloud provider integrations
|
// RegisterCloudIntegrationsRoutes register routes for cloud provider integrations
|
||||||
func (aH *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
func (aH *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||||
subRouter := router.PathPrefix("/api/v1/cloud-integrations").Subrouter()
|
subRouter := router.PathPrefix("/api/v1/cloud-integrations").Subrouter()
|
||||||
|
|
||||||
|
subRouter.Use(middleware.NewRecovery(aH.Signoz.Instrumentation.Logger()).Wrap)
|
||||||
|
|
||||||
subRouter.HandleFunc(
|
subRouter.HandleFunc(
|
||||||
"/{cloudProvider}/accounts/generate-connection-url", am.EditAccess(aH.CloudIntegrationsGenerateConnectionUrl),
|
"/{cloudProvider}/accounts/generate-connection-url", am.EditAccess(aH.CloudIntegrationsGenerateConnectionArtifact),
|
||||||
).Methods(http.MethodPost)
|
).Methods(http.MethodPost)
|
||||||
|
|
||||||
subRouter.HandleFunc(
|
subRouter.HandleFunc(
|
||||||
@@ -3523,170 +3542,199 @@ func (aH *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *mi
|
|||||||
subRouter.HandleFunc(
|
subRouter.HandleFunc(
|
||||||
"/{cloudProvider}/services/{serviceId}/config", am.EditAccess(aH.CloudIntegrationsUpdateServiceConfig),
|
"/{cloudProvider}/services/{serviceId}/config", am.EditAccess(aH.CloudIntegrationsUpdateServiceConfig),
|
||||||
).Methods(http.MethodPost)
|
).Methods(http.MethodPost)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsListConnectedAccounts(
|
func (aH *APIHandler) CloudIntegrationsGenerateConnectionArtifact(w http.ResponseWriter, r *http.Request) {
|
||||||
w http.ResponseWriter, r *http.Request,
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, apiErr := aH.CloudIntegrationsController.ListConnectedAccounts(
|
|
||||||
r.Context(), claims.OrgID, cloudProvider,
|
|
||||||
)
|
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
RespondError(w, apiErr, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aH.Respond(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsGenerateConnectionUrl(
|
|
||||||
w http.ResponseWriter, r *http.Request,
|
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
|
||||||
|
|
||||||
req := cloudintegrations.GenerateConnectionUrlRequest{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, apiErr := aH.CloudIntegrationsController.GenerateConnectionUrl(
|
|
||||||
r.Context(), claims.OrgID, cloudProvider, req,
|
|
||||||
)
|
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
RespondError(w, apiErr, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsGetAccountStatus(
|
|
||||||
w http.ResponseWriter, r *http.Request,
|
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
|
||||||
accountId := mux.Vars(r)["accountId"]
|
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, apiErr := aH.CloudIntegrationsController.GetAccountStatus(
|
|
||||||
r.Context(), claims.OrgID, cloudProvider, accountId,
|
|
||||||
)
|
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
RespondError(w, apiErr, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aH.Respond(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsAgentCheckIn(
|
|
||||||
w http.ResponseWriter, r *http.Request,
|
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
|
||||||
|
|
||||||
req := cloudintegrations.AgentCheckInRequest{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := aH.CloudIntegrationsController.CheckInAsAgent(
|
|
||||||
r.Context(), claims.OrgID, cloudProvider, req,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aH.Respond(w, result)
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, errors.WrapInternalf(err, errors.CodeInternal, "failed to read request body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].GenerateConnectionArtifact(r.Context(), &integrationstypes.PostableConnectionArtifact{
|
||||||
|
OrgID: claims.OrgID,
|
||||||
|
Data: reqBody,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
aH.Signoz.Instrumentation.Logger().ErrorContext(r.Context(),
|
||||||
|
"failed to generate connection artifact for cloud integration",
|
||||||
|
slog.String("cloudProvider", cloudProviderString),
|
||||||
|
slog.String("orgID", claims.OrgID),
|
||||||
|
)
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(w, http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsUpdateAccountConfig(
|
func (aH *APIHandler) CloudIntegrationsListConnectedAccounts(w http.ResponseWriter, r *http.Request) {
|
||||||
w http.ResponseWriter, r *http.Request,
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].ListConnectedAccounts(r.Context(), claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) CloudIntegrationsGetAccountStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
|
|
||||||
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
accountId := mux.Vars(r)["accountId"]
|
accountId := mux.Vars(r)["accountId"]
|
||||||
|
|
||||||
req := cloudintegrations.UpdateAccountConfigRequest{}
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err != nil {
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].GetAccountStatus(r.Context(), claims.OrgID, accountId)
|
||||||
if errv2 != nil {
|
if err != nil {
|
||||||
render.Error(w, errv2)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, apiErr := aH.CloudIntegrationsController.UpdateAccountConfig(
|
render.Success(w, http.StatusOK, resp)
|
||||||
r.Context(), claims.OrgID, cloudProvider, accountId, req,
|
|
||||||
)
|
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
RespondError(w, apiErr, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsDisconnectAccount(
|
func (aH *APIHandler) CloudIntegrationsAgentCheckIn(w http.ResponseWriter, r *http.Request) {
|
||||||
w http.ResponseWriter, r *http.Request,
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(integrationstypes.PostableAgentCheckInPayload)
|
||||||
|
if err = json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||||
|
render.Error(w, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "invalid request body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.OrgID = claims.OrgID
|
||||||
|
|
||||||
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].AgentCheckIn(r.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) CloudIntegrationsUpdateAccountConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
|
|
||||||
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
accountId := mux.Vars(r)["accountId"]
|
accountId := mux.Vars(r)["accountId"]
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
reqBody, err := io.ReadAll(r.Body)
|
||||||
if errv2 != nil {
|
if err != nil {
|
||||||
render.Error(w, errv2)
|
render.Error(w, errors.WrapInternalf(err, errors.CodeInternal, "failed to read request body"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, apiErr := aH.CloudIntegrationsController.DisconnectAccount(
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].UpdateAccountConfig(r.Context(), &integrationstypes.PatchableAccountConfig{
|
||||||
r.Context(), claims.OrgID, cloudProvider, accountId,
|
OrgID: claims.OrgID,
|
||||||
)
|
AccountId: accountId,
|
||||||
|
Data: reqBody,
|
||||||
if apiErr != nil {
|
})
|
||||||
RespondError(w, apiErr, nil)
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aH.Respond(w, result)
|
render.Success(w, http.StatusOK, resp)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsListServices(
|
func (aH *APIHandler) CloudIntegrationsDisconnectAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
w http.ResponseWriter, r *http.Request,
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accountId := mux.Vars(r)["accountId"]
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := aH.cloudIntegrationsRegistry[cloudProvider].DisconnectAccount(r.Context(), claims.OrgID, accountId)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(w, http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) CloudIntegrationsListServices(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
|
|
||||||
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var cloudAccountId *string
|
var cloudAccountId *string
|
||||||
|
|
||||||
@@ -3695,26 +3743,22 @@ func (aH *APIHandler) CloudIntegrationsListServices(
|
|||||||
cloudAccountId = &cloudAccountIdQP
|
cloudAccountId = &cloudAccountIdQP
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
if errv2 != nil {
|
if err != nil {
|
||||||
render.Error(w, errv2)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, apiErr := aH.CloudIntegrationsController.ListServices(
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].ListServices(r.Context(), claims.OrgID, cloudAccountId)
|
||||||
r.Context(), claims.OrgID, cloudProvider, cloudAccountId,
|
if err != nil {
|
||||||
)
|
render.Error(w, err)
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
RespondError(w, apiErr, nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aH.Respond(w, resp)
|
|
||||||
|
render.Success(w, http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsGetServiceDetails(
|
func (aH *APIHandler) CloudIntegrationsGetServiceDetails(w http.ResponseWriter, r *http.Request) {
|
||||||
w http.ResponseWriter, r *http.Request,
|
|
||||||
) {
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(w, err)
|
render.Error(w, err)
|
||||||
@@ -3726,7 +3770,14 @@ func (aH *APIHandler) CloudIntegrationsGetServiceDetails(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
|
|
||||||
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
serviceId := mux.Vars(r)["serviceId"]
|
serviceId := mux.Vars(r)["serviceId"]
|
||||||
|
|
||||||
var cloudAccountId *string
|
var cloudAccountId *string
|
||||||
@@ -3736,270 +3787,59 @@ func (aH *APIHandler) CloudIntegrationsGetServiceDetails(
|
|||||||
cloudAccountId = &cloudAccountIdQP
|
cloudAccountId = &cloudAccountIdQP
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
resp, err := aH.cloudIntegrationsRegistry[cloudProvider].GetServiceDetails(r.Context(), &integrationstypes.GetServiceDetailsReq{
|
||||||
if errv2 != nil {
|
OrgID: orgID,
|
||||||
render.Error(w, errv2)
|
ServiceId: serviceId,
|
||||||
return
|
CloudAccountID: cloudAccountId,
|
||||||
}
|
})
|
||||||
|
|
||||||
resp, err := aH.CloudIntegrationsController.GetServiceDetails(
|
|
||||||
r.Context(), claims.OrgID, cloudProvider, serviceId, cloudAccountId,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add connection status for the 2 signals.
|
render.Success(w, http.StatusOK, resp)
|
||||||
if cloudAccountId != nil {
|
return
|
||||||
connStatus, apiErr := aH.calculateCloudIntegrationServiceConnectionStatus(
|
|
||||||
r.Context(), orgID, cloudProvider, *cloudAccountId, resp,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
RespondError(w, apiErr, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.ConnectionStatus = connStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) calculateCloudIntegrationServiceConnectionStatus(
|
func (aH *APIHandler) CloudIntegrationsUpdateServiceConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx context.Context,
|
cloudProviderString := mux.Vars(r)["cloudProvider"]
|
||||||
orgID valuer.UUID,
|
|
||||||
cloudProvider string,
|
|
||||||
cloudAccountId string,
|
|
||||||
svcDetails *cloudintegrations.ServiceDetails,
|
|
||||||
) (*cloudintegrations.ServiceConnectionStatus, *model.ApiError) {
|
|
||||||
if cloudProvider != "aws" {
|
|
||||||
// TODO(Raj): Make connection check generic for all providers in a follow up change
|
|
||||||
return nil, model.BadRequest(
|
|
||||||
fmt.Errorf("unsupported cloud provider: %s", cloudProvider),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
telemetryCollectionStrategy := svcDetails.Strategy
|
cloudProvider, err := integrationstypes.NewCloudProvider(cloudProviderString)
|
||||||
if telemetryCollectionStrategy == nil {
|
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
|
||||||
"service doesn't have telemetry collection strategy: %s", svcDetails.Id,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &cloudintegrations.ServiceConnectionStatus{}
|
|
||||||
errors := []*model.ApiError{}
|
|
||||||
var resultLock sync.Mutex
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Calculate metrics connection status
|
|
||||||
if telemetryCollectionStrategy.AWSMetrics != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
metricsConnStatus, apiErr := aH.calculateAWSIntegrationSvcMetricsConnectionStatus(
|
|
||||||
ctx, cloudAccountId, telemetryCollectionStrategy.AWSMetrics, svcDetails.DataCollected.Metrics,
|
|
||||||
)
|
|
||||||
|
|
||||||
resultLock.Lock()
|
|
||||||
defer resultLock.Unlock()
|
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
errors = append(errors, apiErr)
|
|
||||||
} else {
|
|
||||||
result.Metrics = metricsConnStatus
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate logs connection status
|
|
||||||
if telemetryCollectionStrategy.AWSLogs != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
logsConnStatus, apiErr := aH.calculateAWSIntegrationSvcLogsConnectionStatus(
|
|
||||||
ctx, orgID, cloudAccountId, telemetryCollectionStrategy.AWSLogs,
|
|
||||||
)
|
|
||||||
|
|
||||||
resultLock.Lock()
|
|
||||||
defer resultLock.Unlock()
|
|
||||||
|
|
||||||
if apiErr != nil {
|
|
||||||
errors = append(errors, apiErr)
|
|
||||||
} else {
|
|
||||||
result.Logs = logsConnStatus
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return nil, errors[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (aH *APIHandler) calculateAWSIntegrationSvcMetricsConnectionStatus(
|
|
||||||
ctx context.Context,
|
|
||||||
cloudAccountId string,
|
|
||||||
strategy *services.AWSMetricsStrategy,
|
|
||||||
metricsCollectedBySvc []services.CollectedMetric,
|
|
||||||
) (*cloudintegrations.SignalConnectionStatus, *model.ApiError) {
|
|
||||||
if strategy == nil || len(strategy.StreamFilters) < 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedLabelValues := map[string]string{
|
|
||||||
"cloud_provider": "aws",
|
|
||||||
"cloud_account_id": cloudAccountId,
|
|
||||||
}
|
|
||||||
|
|
||||||
metricsNamespace := strategy.StreamFilters[0].Namespace
|
|
||||||
metricsNamespaceParts := strings.Split(metricsNamespace, "/")
|
|
||||||
|
|
||||||
if len(metricsNamespaceParts) >= 2 {
|
|
||||||
expectedLabelValues["service_namespace"] = metricsNamespaceParts[0]
|
|
||||||
expectedLabelValues["service_name"] = metricsNamespaceParts[1]
|
|
||||||
} else {
|
|
||||||
// metrics for single word namespaces like "CWAgent" do not
|
|
||||||
// have the service_namespace label populated
|
|
||||||
expectedLabelValues["service_name"] = metricsNamespaceParts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
metricNamesCollectedBySvc := []string{}
|
|
||||||
for _, cm := range metricsCollectedBySvc {
|
|
||||||
metricNamesCollectedBySvc = append(metricNamesCollectedBySvc, cm.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
statusForLastReceivedMetric, apiErr := aH.reader.GetLatestReceivedMetric(
|
|
||||||
ctx, metricNamesCollectedBySvc, expectedLabelValues,
|
|
||||||
)
|
|
||||||
if apiErr != nil {
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusForLastReceivedMetric != nil {
|
|
||||||
return &cloudintegrations.SignalConnectionStatus{
|
|
||||||
LastReceivedTsMillis: statusForLastReceivedMetric.LastReceivedTsMillis,
|
|
||||||
LastReceivedFrom: "signoz-aws-integration",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) calculateAWSIntegrationSvcLogsConnectionStatus(
|
|
||||||
ctx context.Context,
|
|
||||||
orgID valuer.UUID,
|
|
||||||
cloudAccountId string,
|
|
||||||
strategy *services.AWSLogsStrategy,
|
|
||||||
) (*cloudintegrations.SignalConnectionStatus, *model.ApiError) {
|
|
||||||
if strategy == nil || len(strategy.Subscriptions) < 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logGroupNamePrefix := strategy.Subscriptions[0].LogGroupNamePrefix
|
|
||||||
if len(logGroupNamePrefix) < 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logsConnTestFilter := &v3.FilterSet{
|
|
||||||
Operator: "AND",
|
|
||||||
Items: []v3.FilterItem{
|
|
||||||
{
|
|
||||||
Key: v3.AttributeKey{
|
|
||||||
Key: "cloud.account.id",
|
|
||||||
DataType: v3.AttributeKeyDataTypeString,
|
|
||||||
Type: v3.AttributeKeyTypeResource,
|
|
||||||
},
|
|
||||||
Operator: "=",
|
|
||||||
Value: cloudAccountId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: v3.AttributeKey{
|
|
||||||
Key: "aws.cloudwatch.log_group_name",
|
|
||||||
DataType: v3.AttributeKeyDataTypeString,
|
|
||||||
Type: v3.AttributeKeyTypeResource,
|
|
||||||
},
|
|
||||||
Operator: "like",
|
|
||||||
Value: logGroupNamePrefix + "%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Raj): Receive this as a param from UI in the future.
|
|
||||||
lookbackSeconds := int64(30 * 60)
|
|
||||||
|
|
||||||
qrParams := &v3.QueryRangeParamsV3{
|
|
||||||
Start: time.Now().UnixMilli() - (lookbackSeconds * 1000),
|
|
||||||
End: time.Now().UnixMilli(),
|
|
||||||
CompositeQuery: &v3.CompositeQuery{
|
|
||||||
PanelType: v3.PanelTypeList,
|
|
||||||
QueryType: v3.QueryTypeBuilder,
|
|
||||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
|
||||||
"A": {
|
|
||||||
PageSize: 1,
|
|
||||||
Filters: logsConnTestFilter,
|
|
||||||
QueryName: "A",
|
|
||||||
DataSource: v3.DataSourceLogs,
|
|
||||||
Expression: "A",
|
|
||||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
queryRes, _, err := aH.querier.QueryRange(
|
|
||||||
ctx, orgID, qrParams,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.InternalError(fmt.Errorf(
|
render.Error(w, err)
|
||||||
"could not query for integration connection status: %w", err,
|
return
|
||||||
))
|
|
||||||
}
|
|
||||||
if len(queryRes) > 0 && queryRes[0].List != nil && len(queryRes[0].List) > 0 {
|
|
||||||
lastLog := queryRes[0].List[0]
|
|
||||||
|
|
||||||
return &cloudintegrations.SignalConnectionStatus{
|
|
||||||
LastReceivedTsMillis: lastLog.Timestamp.UnixMilli(),
|
|
||||||
LastReceivedFrom: "signoz-aws-integration",
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) CloudIntegrationsUpdateServiceConfig(
|
|
||||||
w http.ResponseWriter, r *http.Request,
|
|
||||||
) {
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
|
||||||
serviceId := mux.Vars(r)["serviceId"]
|
serviceId := mux.Vars(r)["serviceId"]
|
||||||
|
|
||||||
req := cloudintegrations.UpdateServiceConfigRequest{}
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := aH.CloudIntegrationsController.UpdateServiceConfig(
|
|
||||||
r.Context(), claims.OrgID, cloudProvider, serviceId, &req,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aH.Respond(w, result)
|
reqBody, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, errors.WrapInternalf(err,
|
||||||
|
errors.CodeInternal,
|
||||||
|
"failed to read update service config request body",
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := aH.cloudIntegrationsRegistry[cloudProvider].UpdateServiceConfig(
|
||||||
|
r.Context(), &integrationstypes.PatchableServiceConfig{
|
||||||
|
OrgID: claims.OrgID,
|
||||||
|
ServiceId: serviceId,
|
||||||
|
Config: reqBody,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(w, http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logs
|
// logs
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
@@ -107,7 +108,7 @@ type IntegrationsListItem struct {
|
|||||||
|
|
||||||
type Integration struct {
|
type Integration struct {
|
||||||
IntegrationDetails
|
IntegrationDetails
|
||||||
Installation *types.InstalledIntegration `json:"installation"`
|
Installation *integrationstypes.InstalledIntegration `json:"installation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -223,7 +224,7 @@ func (m *Manager) InstallIntegration(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
integrationId string,
|
integrationId string,
|
||||||
config types.InstalledIntegrationConfig,
|
config integrationstypes.InstalledIntegrationConfig,
|
||||||
) (*IntegrationsListItem, *model.ApiError) {
|
) (*IntegrationsListItem, *model.ApiError) {
|
||||||
integrationDetails, apiErr := m.getIntegrationDetails(ctx, integrationId)
|
integrationDetails, apiErr := m.getIntegrationDetails(ctx, integrationId)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@@ -429,7 +430,7 @@ func (m *Manager) getInstalledIntegration(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
integrationId string,
|
integrationId string,
|
||||||
) (*types.InstalledIntegration, *model.ApiError) {
|
) (*integrationstypes.InstalledIntegration, *model.ApiError) {
|
||||||
iis, apiErr := m.installedIntegrationsRepo.get(
|
iis, apiErr := m.installedIntegrationsRepo.get(
|
||||||
ctx, orgId, []string{integrationId},
|
ctx, orgId, []string{integrationId},
|
||||||
)
|
)
|
||||||
@@ -457,7 +458,7 @@ func (m *Manager) getInstalledIntegrations(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
installedTypes := utils.MapSlice(installations, func(i types.InstalledIntegration) string {
|
installedTypes := utils.MapSlice(installations, func(i integrationstypes.InstalledIntegration) string {
|
||||||
return i.Type
|
return i.Type
|
||||||
})
|
})
|
||||||
integrationDetails, apiErr := m.availableIntegrationsRepo.get(ctx, installedTypes)
|
integrationDetails, apiErr := m.availableIntegrationsRepo.get(ctx, installedTypes)
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InstalledIntegrationsRepo interface {
|
type InstalledIntegrationsRepo interface {
|
||||||
list(ctx context.Context, orgId string) ([]types.InstalledIntegration, *model.ApiError)
|
list(ctx context.Context, orgId string) ([]integrationstypes.InstalledIntegration, *model.ApiError)
|
||||||
|
|
||||||
get(
|
get(
|
||||||
ctx context.Context, orgId string, integrationTypes []string,
|
ctx context.Context, orgId string, integrationTypes []string,
|
||||||
) (map[string]types.InstalledIntegration, *model.ApiError)
|
) (map[string]integrationstypes.InstalledIntegration, *model.ApiError)
|
||||||
|
|
||||||
upsert(
|
upsert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
integrationType string,
|
integrationType string,
|
||||||
config types.InstalledIntegrationConfig,
|
config integrationstypes.InstalledIntegrationConfig,
|
||||||
) (*types.InstalledIntegration, *model.ApiError)
|
) (*integrationstypes.InstalledIntegration, *model.ApiError)
|
||||||
|
|
||||||
delete(ctx context.Context, orgId string, integrationType string) *model.ApiError
|
delete(ctx context.Context, orgId string, integrationType string) *model.ApiError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/integrationstypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
)
|
)
|
||||||
@@ -26,8 +27,8 @@ func NewInstalledIntegrationsSqliteRepo(store sqlstore.SQLStore) (
|
|||||||
func (r *InstalledIntegrationsSqliteRepo) list(
|
func (r *InstalledIntegrationsSqliteRepo) list(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
) ([]types.InstalledIntegration, *model.ApiError) {
|
) ([]integrationstypes.InstalledIntegration, *model.ApiError) {
|
||||||
integrations := []types.InstalledIntegration{}
|
integrations := []integrationstypes.InstalledIntegration{}
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
err := r.store.BunDB().NewSelect().
|
||||||
Model(&integrations).
|
Model(&integrations).
|
||||||
@@ -44,8 +45,8 @@ func (r *InstalledIntegrationsSqliteRepo) list(
|
|||||||
|
|
||||||
func (r *InstalledIntegrationsSqliteRepo) get(
|
func (r *InstalledIntegrationsSqliteRepo) get(
|
||||||
ctx context.Context, orgId string, integrationTypes []string,
|
ctx context.Context, orgId string, integrationTypes []string,
|
||||||
) (map[string]types.InstalledIntegration, *model.ApiError) {
|
) (map[string]integrationstypes.InstalledIntegration, *model.ApiError) {
|
||||||
integrations := []types.InstalledIntegration{}
|
integrations := []integrationstypes.InstalledIntegration{}
|
||||||
|
|
||||||
typeValues := []interface{}{}
|
typeValues := []interface{}{}
|
||||||
for _, integrationType := range integrationTypes {
|
for _, integrationType := range integrationTypes {
|
||||||
@@ -62,7 +63,7 @@ func (r *InstalledIntegrationsSqliteRepo) get(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := map[string]types.InstalledIntegration{}
|
result := map[string]integrationstypes.InstalledIntegration{}
|
||||||
for _, ii := range integrations {
|
for _, ii := range integrations {
|
||||||
result[ii.Type] = ii
|
result[ii.Type] = ii
|
||||||
}
|
}
|
||||||
@@ -74,10 +75,10 @@ func (r *InstalledIntegrationsSqliteRepo) upsert(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
orgId string,
|
orgId string,
|
||||||
integrationType string,
|
integrationType string,
|
||||||
config types.InstalledIntegrationConfig,
|
config integrationstypes.InstalledIntegrationConfig,
|
||||||
) (*types.InstalledIntegration, *model.ApiError) {
|
) (*integrationstypes.InstalledIntegration, *model.ApiError) {
|
||||||
|
|
||||||
integration := types.InstalledIntegration{
|
integration := integrationstypes.InstalledIntegration{
|
||||||
Identifiable: types.Identifiable{
|
Identifiable: types.Identifiable{
|
||||||
ID: valuer.GenerateUUID(),
|
ID: valuer.GenerateUUID(),
|
||||||
},
|
},
|
||||||
@@ -114,7 +115,7 @@ func (r *InstalledIntegrationsSqliteRepo) delete(
|
|||||||
ctx context.Context, orgId string, integrationType string,
|
ctx context.Context, orgId string, integrationType string,
|
||||||
) *model.ApiError {
|
) *model.ApiError {
|
||||||
_, dbErr := r.store.BunDB().NewDelete().
|
_, dbErr := r.store.BunDB().NewDelete().
|
||||||
Model(&types.InstalledIntegration{}).
|
Model(&integrationstypes.InstalledIntegration{}).
|
||||||
Where("type = ?", integrationType).
|
Where("type = ?", integrationType).
|
||||||
Where("org_id = ?", orgId).
|
Where("org_id = ?", orgId).
|
||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import (
|
|||||||
querierAPI "github.com/SigNoz/signoz/pkg/querier"
|
querierAPI "github.com/SigNoz/signoz/pkg/querier"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
||||||
@@ -71,11 +70,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheForTraceDetail, err := memorycache.New(context.TODO(), signoz.Instrumentation.ToProviderSettings(), cache.Config{
|
cacheForTraceDetail, err := memorycache.New(context.TODO(), signoz.Instrumentation.ToProviderSettings(), cache.Config{
|
||||||
Provider: "memory",
|
Provider: "memory",
|
||||||
Memory: cache.Memory{
|
Memory: cache.Memory{
|
||||||
@@ -127,7 +121,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
|||||||
Reader: reader,
|
Reader: reader,
|
||||||
RuleManager: rm,
|
RuleManager: rm,
|
||||||
IntegrationsController: integrationsController,
|
IntegrationsController: integrationsController,
|
||||||
CloudIntegrationsController: cloudIntegrationsController,
|
|
||||||
LogsParsingPipelineController: logParsingPipelineController,
|
LogsParsingPipelineController: logParsingPipelineController,
|
||||||
FluxInterval: config.Querier.FluxInterval,
|
FluxInterval: config.Querier.FluxInterval,
|
||||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||||
@@ -135,6 +128,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
|||||||
Signoz: signoz,
|
Signoz: signoz,
|
||||||
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
|
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
|
||||||
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
|
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
|
||||||
|
Logger: signoz.Instrumentation.Logger(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IntegrationUserEmail string
|
|
||||||
|
|
||||||
const (
|
|
||||||
AWSIntegrationUserEmail IntegrationUserEmail = "aws-integration@signoz.io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllIntegrationUserEmails = []IntegrationUserEmail{
|
|
||||||
AWSIntegrationUserEmail,
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
// Normal integration uses just the installed_integration table
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type InstalledIntegration struct {
|
|
||||||
bun.BaseModel `bun:"table:installed_integration"`
|
|
||||||
|
|
||||||
Identifiable
|
|
||||||
Type string `json:"type" bun:"type,type:text,unique:org_id_type"`
|
|
||||||
Config InstalledIntegrationConfig `json:"config" bun:"config,type:text"`
|
|
||||||
InstalledAt time.Time `json:"installed_at" bun:"installed_at,default:current_timestamp"`
|
|
||||||
OrgID string `json:"org_id" bun:"org_id,type:text,unique:org_id_type,references:organizations(id),on_delete:cascade"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstalledIntegrationConfig map[string]interface{}
|
|
||||||
|
|
||||||
// For serializing from db
|
|
||||||
func (c *InstalledIntegrationConfig) Scan(src interface{}) error {
|
|
||||||
var data []byte
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
data = v
|
|
||||||
case string:
|
|
||||||
data = []byte(v)
|
|
||||||
default:
|
|
||||||
return errors.NewInternalf(errors.CodeInternal, "tried to scan from %T instead of string or bytes", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(data, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing to db
|
|
||||||
func (c *InstalledIntegrationConfig) Value() (driver.Value, error) {
|
|
||||||
filterSetJson, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "could not serialize integration config to JSON")
|
|
||||||
}
|
|
||||||
return filterSetJson, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
// Cloud integration uses the cloud_integration table
|
|
||||||
// and cloud_integrations_service table
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type CloudIntegration struct {
|
|
||||||
bun.BaseModel `bun:"table:cloud_integration"`
|
|
||||||
|
|
||||||
Identifiable
|
|
||||||
TimeAuditable
|
|
||||||
Provider string `json:"provider" bun:"provider,type:text,unique:provider_id"`
|
|
||||||
Config *AccountConfig `json:"config" bun:"config,type:text"`
|
|
||||||
AccountID *string `json:"account_id" bun:"account_id,type:text"`
|
|
||||||
LastAgentReport *AgentReport `json:"last_agent_report" bun:"last_agent_report,type:text"`
|
|
||||||
RemovedAt *time.Time `json:"removed_at" bun:"removed_at,type:timestamp,nullzero"`
|
|
||||||
OrgID string `bun:"org_id,type:text,unique:provider_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *CloudIntegration) Status() AccountStatus {
|
|
||||||
status := AccountStatus{}
|
|
||||||
if a.LastAgentReport != nil {
|
|
||||||
lastHeartbeat := a.LastAgentReport.TimestampMillis
|
|
||||||
status.Integration.LastHeartbeatTsMillis = &lastHeartbeat
|
|
||||||
}
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *CloudIntegration) Account() Account {
|
|
||||||
ca := Account{Id: a.ID.StringValue(), Status: a.Status()}
|
|
||||||
|
|
||||||
if a.AccountID != nil {
|
|
||||||
ca.CloudAccountId = *a.AccountID
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Config != nil {
|
|
||||||
ca.Config = *a.Config
|
|
||||||
} else {
|
|
||||||
ca.Config = DefaultAccountConfig()
|
|
||||||
}
|
|
||||||
return ca
|
|
||||||
}
|
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CloudAccountId string `json:"cloud_account_id"`
|
|
||||||
Config AccountConfig `json:"config"`
|
|
||||||
Status AccountStatus `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountStatus struct {
|
|
||||||
Integration AccountIntegrationStatus `json:"integration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountIntegrationStatus struct {
|
|
||||||
LastHeartbeatTsMillis *int64 `json:"last_heartbeat_ts_ms"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultAccountConfig() AccountConfig {
|
|
||||||
return AccountConfig{
|
|
||||||
EnabledRegions: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountConfig struct {
|
|
||||||
EnabledRegions []string `json:"regions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing from db
|
|
||||||
func (c *AccountConfig) Scan(src any) error {
|
|
||||||
var data []byte
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
data = v
|
|
||||||
case string:
|
|
||||||
data = []byte(v)
|
|
||||||
default:
|
|
||||||
return errors.NewInternalf(errors.CodeInternal, "tried to scan from %T instead of string or bytes", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(data, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing to db
|
|
||||||
func (c *AccountConfig) Value() (driver.Value, error) {
|
|
||||||
if c == nil {
|
|
||||||
return nil, errors.NewInternalf(errors.CodeInternal, "cloud account config is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't serialize cloud account config to JSON")
|
|
||||||
}
|
|
||||||
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
|
|
||||||
return string(serialized), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentReport struct {
|
|
||||||
TimestampMillis int64 `json:"timestamp_millis"`
|
|
||||||
Data map[string]any `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing from db
|
|
||||||
func (r *AgentReport) Scan(src any) error {
|
|
||||||
var data []byte
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
data = v
|
|
||||||
case string:
|
|
||||||
data = []byte(v)
|
|
||||||
default:
|
|
||||||
return errors.NewInternalf(errors.CodeInternal, "tried to scan from %T instead of string or bytes", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing to db
|
|
||||||
func (r *AgentReport) Value() (driver.Value, error) {
|
|
||||||
if r == nil {
|
|
||||||
return nil, errors.NewInternalf(errors.CodeInternal, "agent report is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WrapInternalf(
|
|
||||||
err, errors.CodeInternal, "couldn't serialize agent report to JSON",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
|
|
||||||
return string(serialized), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudIntegrationService struct {
|
|
||||||
bun.BaseModel `bun:"table:cloud_integration_service,alias:cis"`
|
|
||||||
|
|
||||||
Identifiable
|
|
||||||
TimeAuditable
|
|
||||||
Type string `bun:"type,type:text,notnull,unique:cloud_integration_id_type"`
|
|
||||||
Config CloudServiceConfig `bun:"config,type:text"`
|
|
||||||
CloudIntegrationID string `bun:"cloud_integration_id,type:text,notnull,unique:cloud_integration_id_type,references:cloud_integrations(id),on_delete:cascade"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudServiceLogsConfig struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
S3Buckets map[string][]string `json:"s3_buckets,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudServiceMetricsConfig struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudServiceConfig struct {
|
|
||||||
Logs *CloudServiceLogsConfig `json:"logs,omitempty"`
|
|
||||||
Metrics *CloudServiceMetricsConfig `json:"metrics,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing from db
|
|
||||||
func (c *CloudServiceConfig) Scan(src any) error {
|
|
||||||
var data []byte
|
|
||||||
switch src := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
data = src
|
|
||||||
case string:
|
|
||||||
data = []byte(src)
|
|
||||||
default:
|
|
||||||
return errors.NewInternalf(errors.CodeInternal, "tried to scan from %T instead of string or bytes", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(data, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serializing to db
|
|
||||||
func (c *CloudServiceConfig) Value() (driver.Value, error) {
|
|
||||||
if c == nil {
|
|
||||||
return nil, errors.NewInternalf(errors.CodeInternal, "cloud service config is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WrapInternalf(
|
|
||||||
err, errors.CodeInternal, "couldn't serialize cloud service config to JSON",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
|
|
||||||
return string(serialized), nil
|
|
||||||
}
|
|
||||||
497
pkg/types/integrationstypes/cloudintegration.go
Normal file
497
pkg/types/integrationstypes/cloudintegration.go
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
package integrationstypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generic utility functions for JSON serialization/deserialization
|
||||||
|
|
||||||
|
// UnmarshalJSON is a generic function to unmarshal JSON data into any type
|
||||||
|
func UnmarshalJSON[T any](src []byte, target *T) error {
|
||||||
|
err := json.Unmarshal(src, target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WrapInternalf(
|
||||||
|
err, errors.CodeInternal, "couldn't deserialize JSON",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON is a generic function to marshal any type to JSON
|
||||||
|
func MarshalJSON[T any](source *T) ([]byte, error) {
|
||||||
|
if source == nil {
|
||||||
|
return nil, errors.NewInternalf(errors.CodeInternal, "source is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WrapInternalf(
|
||||||
|
err, errors.CodeInternal, "couldn't serialize to JSON",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return serialized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloudProvider defines the interface to be implemented by different cloud providers.
|
||||||
|
// This is generic interface so it will be accepting and returning generic types instead of concrete.
|
||||||
|
// It's the cloud provider's responsibility to cast them to appropriate types and validate
|
||||||
|
type CloudProvider interface {
|
||||||
|
GetName() CloudProviderType
|
||||||
|
|
||||||
|
AgentCheckIn(ctx context.Context, req *PostableAgentCheckInPayload) (any, error)
|
||||||
|
GenerateConnectionArtifact(ctx context.Context, req *PostableConnectionArtifact) (any, error)
|
||||||
|
GetAccountStatus(ctx context.Context, orgID, accountID string) (*GettableAccountStatus, error)
|
||||||
|
|
||||||
|
ListServices(ctx context.Context, orgID string, accountID *string) (any, error) // returns either GettableAWSServices or GettableAzureServices
|
||||||
|
GetServiceDetails(ctx context.Context, req *GetServiceDetailsReq) (any, error)
|
||||||
|
ListConnectedAccounts(ctx context.Context, orgID string) (*GettableConnectedAccountsList, error)
|
||||||
|
GetDashboard(ctx context.Context, req *GettableDashboard) (*dashboardtypes.Dashboard, error)
|
||||||
|
GetAvailableDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
|
||||||
|
|
||||||
|
UpdateAccountConfig(ctx context.Context, req *PatchableAccountConfig) (any, error) // req can be either PatchableAWSAccountConfig or PatchableAzureAccountConfig
|
||||||
|
UpdateServiceConfig(ctx context.Context, req *PatchableServiceConfig) (any, error)
|
||||||
|
|
||||||
|
DisconnectAccount(ctx context.Context, orgID, accountID string) (*CloudIntegration, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableDashboard struct {
|
||||||
|
ID string
|
||||||
|
OrgID valuer.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableCloudIntegrationConnectionParams struct {
|
||||||
|
IngestionUrl string `json:"ingestion_url,omitempty"`
|
||||||
|
IngestionKey string `json:"ingestion_key,omitempty"`
|
||||||
|
SigNozAPIUrl string `json:"signoz_api_url,omitempty"`
|
||||||
|
SigNozAPIKey string `json:"signoz_api_key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableIngestionKey struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
// other attributes from gateway response not included here since they are not being used.
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableIngestionKeysSearch struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data []GettableIngestionKey `json:"data"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableCreateIngestionKey struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data GettableIngestionKey `json:"data"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableDeployment struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ClusterInfo struct {
|
||||||
|
Region struct {
|
||||||
|
DNS string `json:"dns"`
|
||||||
|
} `json:"region"`
|
||||||
|
} `json:"cluster"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableConnectedAccountsList struct {
|
||||||
|
Accounts []*Account `json:"accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigNozAWSAgentConfig represents requirements for agent deployment in user's AWS account
|
||||||
|
type SigNozAWSAgentConfig struct {
|
||||||
|
// The region in which SigNoz agent should be installed.
|
||||||
|
Region string `json:"region"`
|
||||||
|
|
||||||
|
IngestionUrl string `json:"ingestion_url"`
|
||||||
|
IngestionKey string `json:"ingestion_key"`
|
||||||
|
SigNozAPIUrl string `json:"signoz_api_url"`
|
||||||
|
SigNozAPIKey string `json:"signoz_api_key"`
|
||||||
|
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostableConnectionArtifact struct {
|
||||||
|
OrgID string
|
||||||
|
Data []byte // either PostableAWSConnectionUrl or PostableAzureConnectionCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostableConnectionArtifactTyped[AgentConfigT any, AccountConfigT any] struct {
|
||||||
|
AccountId *string `json:"account_id,omitempty"` // Optional. To be specified for updates.
|
||||||
|
AgentConfig *AgentConfigT `json:"agent_config"`
|
||||||
|
AccountConfig *AccountConfigT `json:"account_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostableAWSConnectionUrl = PostableConnectionArtifactTyped[SigNozAWSAgentConfig, AWSAccountConfig]
|
||||||
|
type PostableAzureConnectionCommand = PostableConnectionArtifactTyped[SigNozAzureAgentConfig, AzureAccountConfig]
|
||||||
|
|
||||||
|
type SigNozAzureAgentConfig struct {
|
||||||
|
IngestionUrl string `json:"ingestion_url"`
|
||||||
|
IngestionKey string `json:"ingestion_key"`
|
||||||
|
SigNozAPIUrl string `json:"signoz_api_url"`
|
||||||
|
SigNozAPIKey string `json:"signoz_api_key"`
|
||||||
|
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GettableConnectionArtifact represents base structure for connection artifacts
|
||||||
|
type GettableConnectionArtifact[T any] struct {
|
||||||
|
AccountId string `json:"account_id"`
|
||||||
|
Artifact T `json:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAWSConnectionArtifact struct {
|
||||||
|
ConnectionUrl string `json:"connection_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAzureConnectionArtifact struct {
|
||||||
|
AzureShellConnectionCommand string `json:"az_shell_connection_command"`
|
||||||
|
AzureCliConnectionCommand string `json:"az_cli_connection_command"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAWSConnectionUrl struct {
|
||||||
|
AccountId string `json:"account_id"`
|
||||||
|
ConnectionUrl string `json:"connection_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAzureConnectionCommand struct {
|
||||||
|
AccountId string `json:"account_id"`
|
||||||
|
AzureShellConnectionCommand string `json:"az_shell_connection_command"`
|
||||||
|
AzureCliConnectionCommand string `json:"az_cli_connection_command"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAccountStatus struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
CloudAccountId *string `json:"cloud_account_id,omitempty"`
|
||||||
|
Status AccountStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostableAgentCheckInPayload struct {
|
||||||
|
ID string `json:"account_id"`
|
||||||
|
AccountID string `json:"cloud_account_id"`
|
||||||
|
// Arbitrary cloud specific Agent data
|
||||||
|
Data map[string]any `json:"data,omitempty"`
|
||||||
|
OrgID string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSAgentIntegrationConfig struct {
|
||||||
|
EnabledRegions []string `json:"enabled_regions"`
|
||||||
|
TelemetryCollectionStrategy *AWSCollectionStrategy `json:"telemetry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureAgentIntegrationConfig struct {
|
||||||
|
DeploymentRegion string `json:"deployment_region"` // will not be changed once set
|
||||||
|
EnabledResourceGroups []string `json:"resource_groups"`
|
||||||
|
// TelemetryCollectionStrategy is map of service to telemetry config
|
||||||
|
TelemetryCollectionStrategy map[string]*AzureCollectionStrategy `json:"telemetry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAgentCheckIn[T any] struct {
|
||||||
|
AccountId string `json:"account_id"`
|
||||||
|
CloudAccountId string `json:"cloud_account_id"`
|
||||||
|
RemovedAt *time.Time `json:"removed_at"`
|
||||||
|
IntegrationConfig T `json:"integration_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAWSAgentCheckIn = GettableAgentCheckIn[AWSAgentIntegrationConfig]
|
||||||
|
type GettableAzureAgentCheckIn = GettableAgentCheckIn[AzureAgentIntegrationConfig]
|
||||||
|
|
||||||
|
type PatchableServiceConfig struct {
|
||||||
|
OrgID string `json:"org_id"`
|
||||||
|
ServiceId string `json:"service_id"`
|
||||||
|
Config []byte `json:"config"` // json serialized config
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchableCloudServiceConfig[T any] struct {
|
||||||
|
CloudAccountId string `json:"cloud_account_id"`
|
||||||
|
Config *T `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchableAWSCloudServiceConfig = PatchableCloudServiceConfig[AWSCloudServiceConfig]
|
||||||
|
type PatchableAzureCloudServiceConfig = PatchableCloudServiceConfig[AzureCloudServiceConfig]
|
||||||
|
|
||||||
|
type AWSCloudServiceConfig struct {
|
||||||
|
Logs *AWSCloudServiceLogsConfig `json:"logs,omitempty"`
|
||||||
|
Metrics *AWSCloudServiceMetricsConfig `json:"metrics,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureCloudServiceConfig struct {
|
||||||
|
Logs []*AzureCloudServiceLogsConfig `json:"logs,omitempty"`
|
||||||
|
Metrics []*AzureCloudServiceMetricsConfig `json:"metrics,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AWSCloudServiceConfig) Validate(def *AWSDefinition) error {
|
||||||
|
if def.Id != S3Sync && a.Logs != nil && a.Logs.S3Buckets != nil {
|
||||||
|
return errors.NewInvalidInputf(errors.CodeInvalidInput, "s3 buckets can only be added to service-type[%s]", S3Sync)
|
||||||
|
} else if def.Id == S3Sync && a.Logs != nil && a.Logs.S3Buckets != nil {
|
||||||
|
for region := range a.Logs.S3Buckets {
|
||||||
|
if _, found := ValidAWSRegions[region]; !found {
|
||||||
|
return errors.NewInvalidInputf(CodeInvalidCloudRegion, "invalid cloud region: %s", region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AzureCloudServiceConfig) Validate(def *AzureDefinition) error {
|
||||||
|
logsMap := make(map[string]bool)
|
||||||
|
metricsMap := make(map[string]bool)
|
||||||
|
|
||||||
|
if def.Strategy != nil && def.Strategy.Logs != nil {
|
||||||
|
for _, log := range def.Strategy.Logs {
|
||||||
|
logsMap[log.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if def.Strategy != nil && def.Strategy.Metrics != nil {
|
||||||
|
for _, metric := range def.Strategy.Metrics {
|
||||||
|
metricsMap[metric.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, log := range a.Logs {
|
||||||
|
if _, found := logsMap[log.Name]; !found {
|
||||||
|
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid log name: %s", log.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metric := range a.Metrics {
|
||||||
|
if _, found := metricsMap[metric.Name]; !found {
|
||||||
|
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid metric name: %s", metric.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureCloudServiceLogsConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureCloudServiceMetricsConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchServiceConfigResponse struct {
|
||||||
|
ServiceId string `json:"id"`
|
||||||
|
Config any `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchableAccountConfig struct {
|
||||||
|
OrgID string
|
||||||
|
AccountId string
|
||||||
|
Data []byte // can be either AWSAccountConfig or AzureAccountConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchableAccountConfigTyped[T any] struct {
|
||||||
|
Config *T `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchableAWSAccountConfig = PatchableAccountConfigTyped[AWSAccountConfig]
|
||||||
|
type PatchableAzureAccountConfig = PatchableAccountConfigTyped[AzureAccountConfig]
|
||||||
|
|
||||||
|
type AWSAccountConfig struct {
|
||||||
|
EnabledRegions []string `json:"regions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureAccountConfig struct {
|
||||||
|
DeploymentRegion string `json:"deployment_region,omitempty"`
|
||||||
|
EnabledResourceGroups []string `json:"resource_groups,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableServices[T any] struct {
|
||||||
|
Services []T `json:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAWSServices = GettableServices[AWSServiceSummary]
|
||||||
|
type GettableAzureServices = GettableServices[AzureServiceSummary]
|
||||||
|
|
||||||
|
type GetServiceDetailsReq struct {
|
||||||
|
OrgID valuer.UUID
|
||||||
|
ServiceId string
|
||||||
|
CloudAccountID *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// DATABASE TYPES
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Cloud integration uses the cloud_integration table
|
||||||
|
// and cloud_integrations_service table
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type CloudIntegration struct {
|
||||||
|
bun.BaseModel `bun:"table:cloud_integration"`
|
||||||
|
|
||||||
|
types.Identifiable
|
||||||
|
types.TimeAuditable
|
||||||
|
Provider string `json:"provider" bun:"provider,type:text,unique:provider_id"`
|
||||||
|
Config string `json:"config" bun:"config,type:text"` // json serialized config
|
||||||
|
AccountID *string `json:"account_id" bun:"account_id,type:text"`
|
||||||
|
LastAgentReport *AgentReport `json:"last_agent_report" bun:"last_agent_report,type:text"`
|
||||||
|
RemovedAt *time.Time `json:"removed_at" bun:"removed_at,type:timestamp,nullzero"`
|
||||||
|
OrgID string `bun:"org_id,type:text,unique:provider_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *CloudIntegration) Status() AccountStatus {
|
||||||
|
status := AccountStatus{}
|
||||||
|
if a.LastAgentReport != nil {
|
||||||
|
lastHeartbeat := a.LastAgentReport.TimestampMillis
|
||||||
|
status.Integration.LastHeartbeatTsMillis = &lastHeartbeat
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *CloudIntegration) Account(cloudProvider CloudProviderType) *Account {
|
||||||
|
ca := &Account{Id: a.ID.StringValue(), Status: a.Status()}
|
||||||
|
|
||||||
|
if a.AccountID != nil {
|
||||||
|
ca.CloudAccountId = *a.AccountID
|
||||||
|
}
|
||||||
|
|
||||||
|
ca.Config = map[string]interface{}{}
|
||||||
|
|
||||||
|
if len(a.Config) < 1 {
|
||||||
|
return ca
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cloudProvider {
|
||||||
|
case CloudProviderAWS:
|
||||||
|
config := new(AWSAccountConfig)
|
||||||
|
_ = UnmarshalJSON([]byte(a.Config), config)
|
||||||
|
ca.Config = config
|
||||||
|
case CloudProviderAzure:
|
||||||
|
config := new(AzureAccountConfig)
|
||||||
|
_ = UnmarshalJSON([]byte(a.Config), config)
|
||||||
|
ca.Config = config
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return ca
|
||||||
|
}
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
CloudAccountId string `json:"cloud_account_id"`
|
||||||
|
Config any `json:"config"` // AWSAccountConfig or AzureAccountConfig
|
||||||
|
Status AccountStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountStatus struct {
|
||||||
|
Integration AccountIntegrationStatus `json:"integration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountIntegrationStatus struct {
|
||||||
|
LastHeartbeatTsMillis *int64 `json:"last_heartbeat_ts_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultAWSAccountConfig() AWSAccountConfig {
|
||||||
|
return AWSAccountConfig{
|
||||||
|
EnabledRegions: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultAzureAccountConfig() AzureAccountConfig {
|
||||||
|
return AzureAccountConfig{
|
||||||
|
DeploymentRegion: "",
|
||||||
|
EnabledResourceGroups: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceSummary[T any] struct {
|
||||||
|
DefinitionMetadata
|
||||||
|
Config *T `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSServiceSummary = ServiceSummary[AWSCloudServiceConfig]
|
||||||
|
type AzureServiceSummary = ServiceSummary[AzureCloudServiceConfig]
|
||||||
|
|
||||||
|
type GettableAWSServiceDetails struct {
|
||||||
|
AWSDefinition
|
||||||
|
Config *AWSCloudServiceConfig `json:"config"`
|
||||||
|
ConnectionStatus *ServiceConnectionStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableAzureServiceDetails struct {
|
||||||
|
AzureDefinition
|
||||||
|
Config *AzureCloudServiceConfig `json:"config"`
|
||||||
|
ConnectionStatus *ServiceConnectionStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceConnectionStatus struct {
|
||||||
|
Logs []*SignalConnectionStatus `json:"logs"`
|
||||||
|
Metrics []*SignalConnectionStatus `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignalConnectionStatus struct {
|
||||||
|
CategoryID string `json:"category"`
|
||||||
|
CategoryDisplayName string `json:"category_display_name"`
|
||||||
|
LastReceivedTsMillis int64 `json:"last_received_ts_ms"` // epoch milliseconds
|
||||||
|
LastReceivedFrom string `json:"last_received_from"` // resource identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentReport struct {
|
||||||
|
TimestampMillis int64 `json:"timestamp_millis"`
|
||||||
|
Data map[string]any `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan scans data from db
|
||||||
|
func (r *AgentReport) Scan(src any) error {
|
||||||
|
var data []byte
|
||||||
|
switch v := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
data = v
|
||||||
|
case string:
|
||||||
|
data = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.NewInternalf(errors.CodeInternal, "tried to scan from %T instead of string or bytes", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value serializes data to bytes for db insertion
|
||||||
|
func (r *AgentReport) Value() (driver.Value, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, errors.NewInternalf(errors.CodeInternal, "agent report is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WrapInternalf(
|
||||||
|
err, errors.CodeInternal, "couldn't serialize agent report to JSON",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return serialized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudIntegrationService struct {
|
||||||
|
bun.BaseModel `bun:"table:cloud_integration_service,alias:cis"`
|
||||||
|
|
||||||
|
types.Identifiable
|
||||||
|
types.TimeAuditable
|
||||||
|
Type string `bun:"type,type:text,notnull,unique:cloud_integration_id_type"`
|
||||||
|
Config string `bun:"config,type:text"` // json serialized config
|
||||||
|
CloudIntegrationID string `bun:"cloud_integration_id,type:text,notnull,unique:cloud_integration_id_type,references:cloud_integrations(id),on_delete:cascade"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSCloudServiceLogsConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
S3Buckets map[string][]string `json:"s3_buckets,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSCloudServiceMetricsConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
199
pkg/types/integrationstypes/cloudservicedefinitions.go
Normal file
199
pkg/types/integrationstypes/cloudservicedefinitions.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package integrationstypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
S3Sync = "s3sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AWSDefinition = ServiceDefinition[AWSCollectionStrategy]
|
||||||
|
type AzureDefinition = ServiceDefinition[AzureCollectionStrategy]
|
||||||
|
|
||||||
|
var _ Definition = &AWSDefinition{}
|
||||||
|
var _ Definition = &AzureDefinition{}
|
||||||
|
|
||||||
|
type ServiceDefinition[T any] struct {
|
||||||
|
DefinitionMetadata
|
||||||
|
Overview string `json:"overview"` // markdown
|
||||||
|
Assets Assets `json:"assets"`
|
||||||
|
SupportedSignals SupportedSignals `json:"supported_signals"`
|
||||||
|
DataCollected DataCollected `json:"data_collected"`
|
||||||
|
IngestionStatusCheck *IngestionStatusCheck `json:"ingestion_status_check,omitempty"`
|
||||||
|
Strategy *T `json:"telemetry_collection_strategy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (def *ServiceDefinition[T]) PopulateDashboardURLs(cloudProvider CloudProviderType, svcId string) {
|
||||||
|
for i := range def.Assets.Dashboards {
|
||||||
|
dashboardId := def.Assets.Dashboards[i].Id
|
||||||
|
url := "/dashboard/" + GetCloudIntegrationDashboardID(cloudProvider, svcId, dashboardId)
|
||||||
|
def.Assets.Dashboards[i].Url = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (def *ServiceDefinition[T]) GetId() string {
|
||||||
|
return def.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (def *ServiceDefinition[T]) Validate() error {
|
||||||
|
seenDashboardIds := map[string]interface{}{}
|
||||||
|
|
||||||
|
if def.Strategy == nil {
|
||||||
|
return errors.NewInternalf(errors.CodeInternal, "telemetry_collection_strategy is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dd := range def.Assets.Dashboards {
|
||||||
|
if _, seen := seenDashboardIds[dd.Id]; seen {
|
||||||
|
return errors.NewInternalf(errors.CodeInternal, "multiple dashboards found with id %s for Azure Integration", dd.Id)
|
||||||
|
}
|
||||||
|
seenDashboardIds[dd.Id] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefinitionMetadata struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Definition interface {
|
||||||
|
GetId() string
|
||||||
|
Validate() error
|
||||||
|
PopulateDashboardURLs(cloudProvider CloudProviderType, svcId string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngestionStatusCheck struct {
|
||||||
|
Metrics []*IngestionStatusCheckCategory `json:"metrics"`
|
||||||
|
Logs []*IngestionStatusCheckCategory `json:"logs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngestionStatusCheckCategory struct {
|
||||||
|
Category string `json:"category"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
Checks []*IngestionStatusCheckAttribute `json:"checks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngestionStatusCheckAttribute struct {
|
||||||
|
Key string `json:"key"` // search key (metric name or log message)
|
||||||
|
Attributes []*IngestionStatusCheckAttributeFilter `json:"attributes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngestionStatusCheckAttributeFilter struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Operator string `json:"operator"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
type Assets struct {
|
||||||
|
Dashboards []Dashboard `json:"dashboards"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupportedSignals struct {
|
||||||
|
Logs bool `json:"logs"`
|
||||||
|
Metrics bool `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataCollected struct {
|
||||||
|
Logs []CollectedLogAttribute `json:"logs"`
|
||||||
|
Metrics []CollectedMetric `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectedLogAttribute struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectedMetric struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSCollectionStrategy struct {
|
||||||
|
Metrics *AWSMetricsStrategy `json:"aws_metrics,omitempty"`
|
||||||
|
Logs *AWSLogsStrategy `json:"aws_logs,omitempty"`
|
||||||
|
S3Buckets map[string][]string `json:"s3_buckets,omitempty"` // Only available in S3 Sync Service Type in AWS
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureCollectionStrategy struct {
|
||||||
|
Metrics []*AzureMetricsStrategy `json:"azure_metrics,omitempty"`
|
||||||
|
Logs []*AzureLogsStrategy `json:"azure_logs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSMetricsStrategy struct {
|
||||||
|
// to be used as https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-metricstream.html#cfn-cloudwatch-metricstream-includefilters
|
||||||
|
StreamFilters []struct {
|
||||||
|
// json tags here are in the shape expected by AWS API as detailed at
|
||||||
|
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-metricstream-metricstreamfilter.html
|
||||||
|
Namespace string `json:"Namespace"`
|
||||||
|
MetricNames []string `json:"MetricNames,omitempty"`
|
||||||
|
} `json:"cloudwatch_metric_stream_filters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSLogsStrategy struct {
|
||||||
|
Subscriptions []struct {
|
||||||
|
// subscribe to all logs groups with specified prefix.
|
||||||
|
// eg: `/aws/rds/`
|
||||||
|
LogGroupNamePrefix string `json:"log_group_name_prefix"`
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html
|
||||||
|
// "" implies no filtering is required.
|
||||||
|
FilterPattern string `json:"filter_pattern"`
|
||||||
|
} `json:"cloudwatch_logs_subscriptions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureMetricsStrategy struct {
|
||||||
|
CategoryType string `json:"category_type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureLogsStrategy struct {
|
||||||
|
CategoryType string `json:"category_type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dashboard struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Image string `json:"image"`
|
||||||
|
Definition *dashboardtypes.StorableDashboardData `json:"definition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCloudIntegrationDashboardID(cloudProvider valuer.String, svcId, dashboardId string) string {
|
||||||
|
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDashboardsFromAssets(svcId string, cloudProvider CloudProviderType, createdAt *time.Time, assets Assets) []*dashboardtypes.Dashboard {
|
||||||
|
dashboards := make([]*dashboardtypes.Dashboard, 0)
|
||||||
|
|
||||||
|
for _, d := range assets.Dashboards {
|
||||||
|
author := fmt.Sprintf("%s-integration", cloudProvider)
|
||||||
|
dashboards = append(dashboards, &dashboardtypes.Dashboard{
|
||||||
|
ID: GetCloudIntegrationDashboardID(cloudProvider, svcId, d.Id),
|
||||||
|
Locked: true,
|
||||||
|
Data: *d.Definition,
|
||||||
|
TimeAuditable: types.TimeAuditable{
|
||||||
|
CreatedAt: *createdAt,
|
||||||
|
UpdatedAt: *createdAt,
|
||||||
|
},
|
||||||
|
UserAuditable: types.UserAuditable{
|
||||||
|
CreatedBy: author,
|
||||||
|
UpdatedBy: author,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboards
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cloudintegrations
|
package integrationstypes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
@@ -41,3 +41,62 @@ var ValidAWSRegions = map[string]bool{
|
|||||||
"us-west-1": true, // US West (N. California).
|
"us-west-1": true, // US West (N. California).
|
||||||
"us-west-2": true, // US West (Oregon).
|
"us-west-2": true, // US West (Oregon).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ValidAzureRegions = map[string]bool{
|
||||||
|
"australiacentral": true,
|
||||||
|
"australiacentral2": true,
|
||||||
|
"australiaeast": true,
|
||||||
|
"australiasoutheast": true,
|
||||||
|
"austriaeast": true,
|
||||||
|
"belgiumcentral": true,
|
||||||
|
"brazilsouth": true,
|
||||||
|
"brazilsoutheast": true,
|
||||||
|
"canadacentral": true,
|
||||||
|
"canadaeast": true,
|
||||||
|
"centralindia": true,
|
||||||
|
"centralus": true,
|
||||||
|
"chilecentral": true,
|
||||||
|
"denmarkeast": true,
|
||||||
|
"eastasia": true,
|
||||||
|
"eastus": true,
|
||||||
|
"eastus2": true,
|
||||||
|
"francecentral": true,
|
||||||
|
"francesouth": true,
|
||||||
|
"germanynorth": true,
|
||||||
|
"germanywestcentral": true,
|
||||||
|
"indonesiacentral": true,
|
||||||
|
"israelcentral": true,
|
||||||
|
"italynorth": true,
|
||||||
|
"japaneast": true,
|
||||||
|
"japanwest": true,
|
||||||
|
"koreacentral": true,
|
||||||
|
"koreasouth": true,
|
||||||
|
"malaysiawest": true,
|
||||||
|
"mexicocentral": true,
|
||||||
|
"newzealandnorth": true,
|
||||||
|
"northcentralus": true,
|
||||||
|
"northeurope": true,
|
||||||
|
"norwayeast": true,
|
||||||
|
"norwaywest": true,
|
||||||
|
"polandcentral": true,
|
||||||
|
"qatarcentral": true,
|
||||||
|
"southafricanorth": true,
|
||||||
|
"southafricawest": true,
|
||||||
|
"southcentralus": true,
|
||||||
|
"southindia": true,
|
||||||
|
"southeastasia": true,
|
||||||
|
"spaincentral": true,
|
||||||
|
"swedencentral": true,
|
||||||
|
"switzerlandnorth": true,
|
||||||
|
"switzerlandwest": true,
|
||||||
|
"uaecentral": true,
|
||||||
|
"uaenorth": true,
|
||||||
|
"uksouth": true,
|
||||||
|
"ukwest": true,
|
||||||
|
"westcentralus": true,
|
||||||
|
"westeurope": true,
|
||||||
|
"westindia": true,
|
||||||
|
"westus": true,
|
||||||
|
"westus2": true,
|
||||||
|
"westus3": true,
|
||||||
|
}
|
||||||
109
pkg/types/integrationstypes/integration.go
Normal file
109
pkg/types/integrationstypes/integration.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package integrationstypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloudProviderType type alias
|
||||||
|
type CloudProviderType = valuer.String
|
||||||
|
|
||||||
|
var (
|
||||||
|
CloudProviderAWS = valuer.NewString("aws")
|
||||||
|
CloudProviderAzure = valuer.NewString("azure")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CodeCloudProviderInvalidInput = errors.MustNewCode("invalid_cloud_provider")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCloudProvider(provider string) (CloudProviderType, error) {
|
||||||
|
switch provider {
|
||||||
|
case CloudProviderAWS.String(), CloudProviderAzure.String():
|
||||||
|
return valuer.NewString(provider), nil
|
||||||
|
default:
|
||||||
|
return CloudProviderType{}, errors.NewInvalidInputf(CodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
AWSIntegrationUserEmail = valuer.MustNewEmail("aws-integration@signoz.io")
|
||||||
|
AzureIntegrationUserEmail = valuer.MustNewEmail("azure-integration@signoz.io")
|
||||||
|
)
|
||||||
|
|
||||||
|
var IntegrationUserEmails = []valuer.Email{
|
||||||
|
AWSIntegrationUserEmail,
|
||||||
|
AzureIntegrationUserEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsCloudIntegrationDashboardUuid(dashboardUuid string) bool {
|
||||||
|
parts := strings.SplitN(dashboardUuid, "--", 4)
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[0] == "cloud-integration"
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCloudProviderFromDashboardID(dashboardUuid string) (CloudProviderType, error) {
|
||||||
|
parts := strings.SplitN(dashboardUuid, "--", 4)
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return valuer.String{}, errors.NewInvalidInputf(CodeCloudProviderInvalidInput, "invalid dashboard uuid: %s", dashboardUuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
providerStr := parts[1]
|
||||||
|
|
||||||
|
cloudProvider, err := NewCloudProvider(providerStr)
|
||||||
|
if err != nil {
|
||||||
|
return CloudProviderType{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloudProvider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Normal integration uses just the installed_integration table
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type InstalledIntegration struct {
|
||||||
|
bun.BaseModel `bun:"table:installed_integration"`
|
||||||
|
|
||||||
|
types.Identifiable
|
||||||
|
Type string `json:"type" bun:"type,type:text,unique:org_id_type"`
|
||||||
|
Config InstalledIntegrationConfig `json:"config" bun:"config,type:text"`
|
||||||
|
InstalledAt time.Time `json:"installed_at" bun:"installed_at,default:current_timestamp"`
|
||||||
|
OrgID string `json:"org_id" bun:"org_id,type:text,unique:org_id_type,references:organizations(id),on_delete:cascade"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstalledIntegrationConfig map[string]interface{}
|
||||||
|
|
||||||
|
// Scan scans data from db
|
||||||
|
func (c *InstalledIntegrationConfig) Scan(src interface{}) error {
|
||||||
|
var data []byte
|
||||||
|
switch v := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
data = v
|
||||||
|
case string:
|
||||||
|
data = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.NewInternalf(errors.CodeInternal, "tried to scan from %T instead of string or bytes", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value serializes data to db
|
||||||
|
func (c *InstalledIntegrationConfig) Value() (driver.Value, error) {
|
||||||
|
filterSetJson, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WrapInternalf(err, errors.CodeInternal, "could not serialize integration config to JSON")
|
||||||
|
}
|
||||||
|
return filterSetJson, nil
|
||||||
|
}
|
||||||
@@ -139,5 +139,5 @@ def test_generate_connection_url_unsupported_provider(
|
|||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
assert "error" in response_data, "Response should contain 'error' field"
|
assert "error" in response_data, "Response should contain 'error' field"
|
||||||
assert (
|
assert (
|
||||||
"unsupported cloud provider" in response_data["error"].lower()
|
"invalid cloud provider: gcp" in response_data["error"]["message"]
|
||||||
), "Error message should indicate unsupported provider"
|
), "Error message should indicate unsupported provider"
|
||||||
|
|||||||
@@ -193,7 +193,6 @@ def test_get_service_details_with_account(
|
|||||||
assert "overview" in data, "Service details should have 'overview' field"
|
assert "overview" in data, "Service details should have 'overview' field"
|
||||||
assert "assets" in data, "Service details should have 'assets' field"
|
assert "assets" in data, "Service details should have 'assets' field"
|
||||||
assert "config" in data, "Service details should have 'config' field"
|
assert "config" in data, "Service details should have 'config' field"
|
||||||
assert "status" in data, "Config should have 'status' field"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -347,8 +346,8 @@ def test_update_service_config_without_account(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
|
response.status_code == HTTPStatus.NOT_FOUND
|
||||||
), f"Expected 500 for non-existent account, got {response.status_code}"
|
), f"Expected 400 for non-existent account, got {response.status_code}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user