Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: aws integration: UI facing QS api for cloud account management #6771

Merged
merged 39 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5d1bfdc
feat: init app/cloud_integrations
raj-k-singh Jan 2, 2025
c206638
feat: get API test started for cloudintegrations account lifecycle
raj-k-singh Jan 3, 2025
9973cad
feat: cloudintegrations: get controller started
raj-k-singh Jan 3, 2025
45302ce
feat: cloud integrations: add cloudintegrations.Controller to APIHand…
raj-k-singh Jan 3, 2025
d36848e
feat: cloud integrations: get routes started
raj-k-singh Jan 3, 2025
f864c0f
feat: cloud integrations: get accounts table schema started
raj-k-singh Jan 3, 2025
3434819
feat: cloud integrations: get cloudProviderAccountsSQLRepository started
raj-k-singh Jan 5, 2025
cc07436
feat: cloud integrations: cloudProviderAccountsSQLRepository.listAcco…
raj-k-singh Jan 5, 2025
218b4f8
feat: cloud integrations: http handler and controller plumbing for /g…
raj-k-singh Jan 5, 2025
c3e4957
feat: cloud integrations: cloudProviderAccountsSQLRepository.upsert
raj-k-singh Jan 6, 2025
21d9253
feat: cloud integrations: finish up with /generate-connection-url
raj-k-singh Jan 6, 2025
19fca6f
feat: cloud integrations: add cloudProviderAccountsRepository.get
raj-k-singh Jan 7, 2025
93a7087
feat: cloud integrations: add API test expectation for being able to …
raj-k-singh Jan 7, 2025
97156dd
feat: cloud integrations: add http handler and controller method for …
raj-k-singh Jan 7, 2025
946610b
feat: cloud integrations: ensure unconnected accounts aren't included…
raj-k-singh Jan 7, 2025
2cae202
feat: cloud integrations: add test expectation for agent check in req…
raj-k-singh Jan 7, 2025
e3ff59f
feat: cloud integrations: agent check in API
raj-k-singh Jan 7, 2025
e4ee24b
feat: cloud integrations: ensure polling for status after agent check…
raj-k-singh Jan 7, 2025
13d663f
feat: cloud integrations: ensure account included in connected accoun…
raj-k-singh Jan 7, 2025
c81c05d
feat: cloud integrations: add API expectation for updating account co…
raj-k-singh Jan 7, 2025
9029cb2
feat: cloud integrations: API for updating cloud account config
raj-k-singh Jan 7, 2025
e7e09be
feat: cloud integrations: expectation for agent receiving latest conf…
raj-k-singh Jan 7, 2025
cece0f1
feat: cloud integrations: expectation for disconnecting cloud account…
raj-k-singh Jan 7, 2025
045e5cb
feat: cloud integrations: API for disconnecting cloud accounts
raj-k-singh Jan 7, 2025
8028a18
feat: cloud integrations: some cleanup
raj-k-singh Jan 8, 2025
c8ff9b1
feat: cloud integrations: some more cleanup
raj-k-singh Jan 8, 2025
b883e98
feat: cloud integrations: repo: scope rows by cloud provider
raj-k-singh Jan 8, 2025
c9f4397
feat: testutils: refactor out helper for creating a test sqlite DB
raj-k-singh Jan 8, 2025
34d480a
feat: cloud integrations: controller: add test validating regeneratio…
raj-k-singh Jan 8, 2025
85ce980
feat: cloud integrations: controller: validations for agent check ins
raj-k-singh Jan 8, 2025
d26e388
feat: cloud integrations: connected account response structure
raj-k-singh Jan 8, 2025
12cc83f
feat: cloud integrations: API response account structure
raj-k-singh Jan 8, 2025
5e1246b
feat: cloud integrations: some more cleanup
raj-k-singh Jan 9, 2025
4125815
feat: cloud integrations: remove cloudProviderAccountsRepository.GetById
raj-k-singh Jan 9, 2025
26bd4e3
feat: cloud integrations: shouldn't be able to disconnect non-existen…
raj-k-singh Jan 9, 2025
2a33c29
feat: cloud integrations: validate agents can't check in to cloud acc…
raj-k-singh Jan 9, 2025
2ff511e
feat: cloud integrations: ensure agents can't check in to cloud accou…
raj-k-singh Jan 9, 2025
35e1027
feat: cloud integrations: remove stray import of ee/model in cloudint…
raj-k-singh Jan 10, 2025
66810c4
Merge branch 'main' into feat/aws-integration-ui-facing-api-0
raj-k-singh Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ee/query-service/app/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/usage"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
"go.signoz.io/signoz/pkg/query-service/app/integrations"
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/cache"
Expand All @@ -34,6 +35,7 @@ type APIHandlerOptions struct {
FeatureFlags baseint.FeatureLookup
LicenseManager *license.Manager
IntegrationsController *integrations.Controller
CloudIntegrationsController *cloudintegrations.Controller
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
Cache cache.Cache
Gateway *httputil.ReverseProxy
Expand Down Expand Up @@ -62,6 +64,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
RuleManager: opts.RulesManager,
FeatureFlags: opts.FeatureFlags,
IntegrationsController: opts.IntegrationsController,
CloudIntegrationsController: opts.CloudIntegrationsController,
LogsParsingPipelineController: opts.LogsParsingPipelineController,
Cache: opts.Cache,
FluxInterval: opts.FluxInterval,
Expand Down
10 changes: 10 additions & 0 deletions ee/query-service/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (

"go.signoz.io/signoz/pkg/query-service/agentConf"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
baseexplorer "go.signoz.io/signoz/pkg/query-service/app/explorer"
"go.signoz.io/signoz/pkg/query-service/app/integrations"
Expand Down Expand Up @@ -221,6 +222,13 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
)
}

cloudIntegrationsController, err := cloudintegrations.NewController(localDB)
if err != nil {
return nil, fmt.Errorf(
"couldn't create cloud provider integrations controller: %w", err,
)
}

// ingestion pipelines manager
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
localDB, "sqlite", integrationsController.GetPipelinesForInstalledIntegrations,
Expand Down Expand Up @@ -271,6 +279,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
FeatureFlags: lm,
LicenseManager: lm,
IntegrationsController: integrationsController,
CloudIntegrationsController: cloudIntegrationsController,
LogsParsingPipelineController: logParsingPipelineController,
Cache: c,
FluxInterval: fluxInterval,
Expand Down Expand Up @@ -370,6 +379,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web *web.Web) (*
apiHandler.RegisterRoutes(r, am)
apiHandler.RegisterLogsRoutes(r, am)
apiHandler.RegisterIntegrationRoutes(r, am)
apiHandler.RegisterCloudIntegrationsRoutes(r, am)
apiHandler.RegisterQueryRangeV3Routes(r, am)
apiHandler.RegisterInfraMetricsRoutes(r, am)
apiHandler.RegisterQueryRangeV4Routes(r, am)
Expand Down
5 changes: 5 additions & 0 deletions pkg/query-service/app/cloudintegrations/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SigNoz Cloud Integrations

Cloud integrations are unlike the rest of SigNoz integrations.
They have a different UX and so require a different API.
They will also be limited in number and are not expected to have community contributed implementations
247 changes: 247 additions & 0 deletions pkg/query-service/app/cloudintegrations/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package cloudintegrations

import (
"context"
"fmt"
"slices"
"time"

"github.com/jmoiron/sqlx"
"go.signoz.io/signoz/pkg/query-service/model"
)

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 {
repo cloudProviderAccountsRepository
}

func NewController(db *sqlx.DB) (
*Controller, error,
) {
repo, err := newCloudProviderAccountsRepository(db)
if err != nil {
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
}

return &Controller{
repo: repo,
}, nil
}

type Account struct {
Id string `json:"id"`
CloudAccountId string `json:"cloud_account_id"`
Config AccountConfig `json:"config"`
Status AccountStatus `json:"status"`
}

type ConnectedAccountsListResponse struct {
Accounts []Account `json:"accounts"`
}

func (c *Controller) ListConnectedAccounts(
ctx context.Context, cloudProvider string,
) (
*ConnectedAccountsListResponse, *model.ApiError,
) {
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
return nil, apiErr
}

accountRecords, apiErr := c.repo.listConnected(ctx, cloudProvider)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't list cloud accounts")
}

connectedAccounts := []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 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"`
}

type GenerateConnectionUrlResponse struct {
AccountId string `json:"account_id"`
ConnectionUrl string `json:"connection_url"`
}

func (c *Controller) GenerateConnectionUrl(
ctx context.Context, 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.repo.upsert(
ctx, cloudProvider, req.AccountId, &req.AccountConfig, nil, nil, nil,
)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
}

// TODO(Raj): Add actual cloudformation template for AWS integration after it has been shipped.
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
connectionUrl := fmt.Sprintf(
"https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks/quickcreate?stackName=SigNozIntegration/",
req.AgentConfig.Region, req.AgentConfig.Region,
)

return &GenerateConnectionUrlResponse{
AccountId: account.Id,
ConnectionUrl: connectionUrl,
}, nil
}

type AccountStatusResponse struct {
Id string `json:"id"`
Status AccountStatus `json:"status"`
}

func (c *Controller) GetAccountStatus(
ctx context.Context, cloudProvider string, accountId string,
) (
*AccountStatusResponse, *model.ApiError,
) {
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
return nil, apiErr
}

account, apiErr := c.repo.get(ctx, cloudProvider, accountId)
if apiErr != nil {
return nil, apiErr
}

resp := AccountStatusResponse{
Id: account.Id,
Status: account.status(),
}

return &resp, nil
}

type AgentCheckInRequest struct {
AccountId string `json:"account_id"`
CloudAccountId string `json:"cloud_account_id"`
// Arbitrary cloud specific Agent data
Data map[string]any `json:"data,omitempty"`
}

type AgentCheckInResponse struct {
Account AccountRecord `json:"account"`
}

func (c *Controller) CheckInAsAgent(
ctx context.Context, cloudProvider string, req AgentCheckInRequest,
) (*AgentCheckInResponse, *model.ApiError) {
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
return nil, apiErr
}

existingAccount, apiErr := c.repo.get(ctx, cloudProvider, req.AccountId)
if existingAccount != nil && existingAccount.CloudAccountId != nil && *existingAccount.CloudAccountId != req.CloudAccountId {
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.CloudAccountId, existingAccount.Id, cloudProvider, *existingAccount.CloudAccountId,
))
}

existingAccount, apiErr = c.repo.getConnectedCloudAccount(ctx, cloudProvider, req.CloudAccountId)
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
if existingAccount != nil && existingAccount.Id != req.AccountId {
return nil, model.BadRequest(fmt.Errorf(
"can't check in to %s account %s with id %s. already connected with id %s",
cloudProvider, req.CloudAccountId, req.AccountId, existingAccount.Id,
))
}

agentReport := AgentReport{
TimestampMillis: time.Now().UnixMilli(),
Data: req.Data,
}

account, apiErr := c.repo.upsert(
ctx, cloudProvider, &req.AccountId, nil, &req.CloudAccountId, &agentReport, nil,
)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
}

return &AgentCheckInResponse{
Account: *account,
}, nil
}

type UpdateAccountConfigRequest struct {
Config AccountConfig `json:"config"`
}

func (c *Controller) UpdateAccountConfig(
ctx context.Context,
cloudProvider string,
accountId string,
req UpdateAccountConfigRequest,
) (*Account, *model.ApiError) {
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
return nil, apiErr
}

accountRecord, apiErr := c.repo.upsert(
ctx, 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, cloudProvider string, accountId string,
) (*AccountRecord, *model.ApiError) {
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
return nil, apiErr
}

account, apiErr := c.repo.get(ctx, cloudProvider, accountId)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't disconnect account")
}

tsNow := time.Now()
account, apiErr = c.repo.upsert(
ctx, cloudProvider, &accountId, nil, nil, nil, &tsNow,
)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't disconnect account")
}

return account, nil
}
Loading
Loading