feat: [CDE-718]: Adding support for cde-gateways. (#3696)

* feat: [CDE-718]: Adding existence checks in cde-gateway related migration scripts.
* feat: [CDE-718]: Updating the report stats service.
* feat: [CDE-718]: Adding cde-gateway related tables and services.
This commit is contained in:
Dhruv Dhruv 2025-04-22 05:08:21 +00:00 committed by Harness
parent 952a6d852f
commit 7c42a497e5
13 changed files with 412 additions and 2 deletions

View File

@ -29,6 +29,7 @@ func NewService(
templateStore store.InfraProviderTemplateStore,
factory infraprovider.Factory,
spaceFinder refcache.SpaceFinder,
gatewayStore store.CDEGatewayStore,
) *Service {
return &Service{
tx: tx,
@ -38,6 +39,7 @@ func NewService(
infraProviderFactory: factory,
spaceFinder: spaceFinder,
gitspaceConfigStore: gitspaceConfigStore,
gatewayStore: gatewayStore,
}
}
@ -49,4 +51,5 @@ type Service struct {
infraProviderTemplateStore store.InfraProviderTemplateStore
infraProviderFactory infraprovider.Factory
spaceFinder refcache.SpaceFinder
gatewayStore store.CDEGatewayStore
}

View File

@ -0,0 +1,70 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package infraprovider
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/types"
)
func (c *Service) ListGateways(ctx context.Context, filter *types.CDEGatewayFilter) ([]*types.CDEGateway, error) {
if filter == nil || len(filter.InfraProviderConfigIDs) == 0 {
return nil, fmt.Errorf("cde-gateway filter is required")
}
if filter.HealthReportValidityInMins == 0 {
filter.HealthReportValidityInMins = 5
}
gateways, err := c.gatewayStore.List(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to list gateways: %w", err)
}
infraProviderConfigMap := make(map[int64]string)
for _, gateway := range gateways {
if _, ok := infraProviderConfigMap[gateway.InfraProviderConfigID]; !ok {
infraProviderConfig, err := c.infraProviderConfigStore.Find(ctx, gateway.InfraProviderConfigID, false)
if err != nil {
return nil, fmt.Errorf("failed to find infra provider config %d while listing gateways: %w",
gateway.InfraProviderConfigID, err)
}
infraProviderConfigMap[gateway.InfraProviderConfigID] = infraProviderConfig.Identifier
}
gateway.InfraProviderConfigIdentifier = infraProviderConfigMap[gateway.InfraProviderConfigID]
spaceCore, err := c.spaceFinder.FindByID(ctx, gateway.SpaceID)
if err != nil {
return nil, fmt.Errorf("failed to find space %d while listing gateways: %w", gateway.SpaceID, err)
}
gateway.SpacePath = spaceCore.Path
if gateway.Updated < time.Now().Add(-time.Duration(filter.HealthReportValidityInMins)*time.Minute).UnixMilli() {
gateway.Health = types.GatewayHealthUnhealthy
}
if gateway.Health != types.GatewayHealthHealthy || gateway.EnvoyHealth != types.GatewayHealthHealthy {
gateway.OverallHealth = types.GatewayHealthUnhealthy
} else {
gateway.OverallHealth = types.GatewayHealthHealthy
}
}
return gateways, nil
}

View File

@ -0,0 +1,47 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package infraprovider
import (
"context"
"time"
"github.com/harness/gitness/types"
)
func (c *Service) ReportStats(
ctx context.Context,
spaceCore *types.SpaceCore,
infraProviderConfig *types.InfraProviderConfig,
in *types.CDEGatewayStats,
) error {
gateway := types.CDEGateway{
InfraProviderConfigID: infraProviderConfig.ID,
InfraProviderConfigIdentifier: infraProviderConfig.Identifier,
SpaceID: spaceCore.ID,
SpacePath: spaceCore.Path,
}
gateway.Name = in.Name
gateway.GroupName = in.GroupName
gateway.Region = in.Region
gateway.Zone = in.Zone
gateway.Version = in.Version
gateway.Health = in.Health
gateway.EnvoyHealth = in.EnvoyHealth
gateway.Created = time.Now().UnixMilli()
gateway.Updated = gateway.Created
return c.gatewayStore.Upsert(ctx, &gateway)
}

View File

@ -35,7 +35,8 @@ func ProvideInfraProvider(
templateStore store.InfraProviderTemplateStore,
infraProviderFactory infraprovider.Factory,
spaceFinder refcache.SpaceFinder,
gatewayStore store.CDEGatewayStore,
) *Service {
return NewService(tx, gitspaceConfigStore, resourceStore, configStore, templateStore, infraProviderFactory,
spaceFinder)
spaceFinder, gatewayStore)
}

View File

@ -1352,4 +1352,9 @@ type (
end int64,
) ([]types.UsageMetric, error)
}
CDEGatewayStore interface {
Upsert(ctx context.Context, in *types.CDEGateway) error
List(ctx context.Context, filter *types.CDEGatewayFilter) ([]*types.CDEGateway, error)
}
)

View File

@ -0,0 +1,179 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"time"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
var _ store.CDEGatewayStore = (*CDEGatewayStore)(nil)
const (
cdeGatewayIDColumn = `cgate_id`
cdeGatewayInsertColumns = `
cgate_name,
cgate_group_name,
cgate_space_id,
cgate_infra_provider_config_id,
cgate_region,
cgate_zone,
cgate_version,
cgate_health,
cgate_envoy_health,
cgate_created,
cgate_updated
`
cdeGatewaySelectColumns = cdeGatewayIDColumn + "," + cdeGatewayInsertColumns
cdeGatewayTable = `cde_gateways`
)
// NewCDEGatewayStore returns a new CDEGatewayStore.
func NewCDEGatewayStore(db *sqlx.DB) *CDEGatewayStore {
return &CDEGatewayStore{
db: db,
}
}
// CDEGatewayStore implements store.CDEGatewayStore backed by a relational database.
type CDEGatewayStore struct {
db *sqlx.DB
}
func (c *CDEGatewayStore) Upsert(ctx context.Context, in *types.CDEGateway) error {
stmt := database.Builder.
Insert(cdeGatewayTable).
Columns(cdeGatewayInsertColumns).
Values(
in.Name,
in.GroupName,
in.SpaceID,
in.InfraProviderConfigID,
in.Region,
in.Zone,
in.Version,
in.Health,
in.EnvoyHealth,
in.Created,
in.Updated).
Suffix(`
ON CONFLICT (cgate_space_id, cgate_infra_provider_config_id, cgate_region, cgate_group_name, cgate_name)
DO UPDATE
SET
cgate_health = EXCLUDED.cgate_health,
cgate_envoy_health = EXCLUDED.cgate_envoy_health,
cgate_updated = EXCLUDED.cgate_updated,
cgate_zone = EXCLUDED.cgate_zone,
cgate_version = EXCLUDED.cgate_version`)
sql, args, err := stmt.ToSql()
if err != nil {
return errors.Wrap(err, "Failed to convert squirrel builder to sql")
}
db := dbtx.GetAccessor(ctx, c.db)
if _, err = db.ExecContext(ctx, sql, args...); err != nil {
return database.ProcessSQLErrorf(
ctx, err, "cde gateway upsert create query failed for %s", in.Name)
}
return nil
}
func (c *CDEGatewayStore) List(ctx context.Context, filter *types.CDEGatewayFilter) ([]*types.CDEGateway, error) {
stmt := database.Builder.
Select(cdeGatewaySelectColumns).
From(cdeGatewayTable)
if filter != nil && len(filter.InfraProviderConfigIDs) > 0 {
stmt = stmt.Where(squirrel.Eq{"cgate_infra_provider_config_id": filter.InfraProviderConfigIDs})
}
if filter != nil && filter.Health == types.GatewayHealthHealthy {
stmt = stmt.Where(squirrel.Eq{"cgate_health": filter.Health}).
Where(squirrel.Eq{"cgate_envoy_health": filter.Health}).
Where(squirrel.Gt{"cgate_updated": time.Now().Add(
-time.Duration(filter.HealthReportValidityInMins) * time.Minute).UnixMilli()})
}
if filter != nil && filter.Health == types.GatewayHealthUnhealthy {
stmt = stmt.Where(
squirrel.Or{
squirrel.LtOrEq{"cgate_updated": time.Now().Add(
time.Minute * -time.Duration(filter.HealthReportValidityInMins)).UnixMilli()},
squirrel.Eq{"cgate_envoy_health": filter.Health},
},
)
}
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql")
}
db := dbtx.GetAccessor(ctx, c.db)
dst := new([]*cdeGateway)
if err := db.SelectContext(ctx, dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list cde gatways")
}
return entitiesToDTOs(*dst), nil
}
type cdeGateway struct {
ID int64 `db:"cgate_id"`
Name string `db:"cgate_name"`
GroupName string `db:"cgate_group_name"`
SpaceID int64 `db:"cgate_space_id"`
InfraProviderConfigID int64 `db:"cgate_infra_provider_config_id"`
Region string `db:"cgate_region"`
Zone string `db:"cgate_zone"`
Version string `db:"cgate_version"`
Health string `db:"cgate_health"`
EnvoyHealth string `db:"cgate_envoy_health"`
Created int64 `db:"cgate_created"`
Updated int64 `db:"cgate_updated"`
}
func entitiesToDTOs(entities []*cdeGateway) []*types.CDEGateway {
var dtos []*types.CDEGateway
for _, entity := range entities {
dtos = append(dtos, entityToDTO(*entity))
}
return dtos
}
func entityToDTO(entity cdeGateway) *types.CDEGateway {
dto := &types.CDEGateway{}
dto.Name = entity.Name
dto.GroupName = entity.GroupName
dto.SpaceID = entity.SpaceID
dto.InfraProviderConfigID = entity.InfraProviderConfigID
dto.Region = entity.Region
dto.Zone = entity.Zone
dto.Version = entity.Version
dto.Health = entity.Health
dto.EnvoyHealth = entity.EnvoyHealth
dto.Created = entity.Created
dto.Updated = entity.Updated
return dto
}

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS cde_gateways;

View File

@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS cde_gateways
(
cgate_id BIGSERIAL PRIMARY KEY,
cgate_name TEXT NOT NULL,
cgate_group_name TEXT NOT NULL,
cgate_region TEXT NOT NULL,
cgate_zone TEXT NOT NULL,
cgate_version TEXT NOT NULL,
cgate_health TEXT NOT NULL,
cgate_space_id BIGINT NOT NULL,
cgate_infra_provider_config_id BIGINT NOT NULL,
cgate_envoy_health TEXT NOT NULL,
cgate_created BIGINT NOT NULL,
cgate_updated BIGINT NOT NULL,
CONSTRAINT fk_cgate_space_id FOREIGN KEY (cgate_space_id)
REFERENCES spaces (space_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT fk_cgate_infra_provider_config_id FOREIGN KEY (cgate_infra_provider_config_id)
REFERENCES infra_provider_configs (ipconf_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
CREATE UNIQUE INDEX IF NOT EXISTS cde_gateways_space_id_infra_config_id_region_group_name_name
ON cde_gateways (cgate_space_id, cgate_infra_provider_config_id, cgate_region, cgate_group_name, cgate_name);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS cde_gateways;

View File

@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS cde_gateways
(
cgate_id INTEGER PRIMARY KEY AUTOINCREMENT,
cgate_name TEXT NOT NULL,
cgate_group_name TEXT NOT NULL,
cgate_region TEXT NOT NULL,
cgate_zone TEXT NOT NULL,
cgate_version TEXT NOT NULL,
cgate_health TEXT NOT NULL,
cgate_space_id BIGINT NOT NULL,
cgate_infra_provider_config_id BIGINT NOT NULL,
cgate_envoy_health TEXT NOT NULL,
cgate_created BIGINT NOT NULL,
cgate_updated BIGINT NOT NULL,
CONSTRAINT fk_cgate_space_id FOREIGN KEY (cgate_space_id)
REFERENCES spaces (space_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT fk_cgate_infra_provider_config_id FOREIGN KEY (cgate_infra_provider_config_id)
REFERENCES infra_provider_configs (ipconf_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
CREATE UNIQUE INDEX IF NOT EXISTS cde_gateways_space_id_infra_config_id_region_group_name_name
ON cde_gateways (cgate_space_id, cgate_infra_provider_config_id, cgate_region, cgate_group_name, cgate_name);

View File

@ -74,6 +74,7 @@ var WireSet = wire.NewSet(
ProvideInfraProviderTemplateStore,
ProvideInfraProvisionedStore,
ProvideUsageMetricStore,
ProvideCDEGatewayStore,
)
// migrator is helper function to set up the database by performing automated
@ -355,3 +356,7 @@ func ProvideInfraProvisionedStore(db *sqlx.DB) store.InfraProvisionedStore {
func ProvideUsageMetricStore(db *sqlx.DB) store.UsageMetricStore {
return NewUsageMetricsStore(db)
}
func ProvideCDEGatewayStore(db *sqlx.DB) store.CDEGatewayStore {
return NewCDEGatewayStore(db)
}

View File

@ -347,7 +347,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
}
dockerProvider := infraprovider.ProvideDockerProvider(dockerConfig, dockerClientFactory, reporter4)
factory := infraprovider.ProvideFactory(dockerProvider)
infraproviderService := infraprovider2.ProvideInfraProvider(transactor, gitspaceConfigStore, infraProviderResourceStore, infraProviderConfigStore, infraProviderTemplateStore, factory, spaceFinder)
cdeGatewayStore := database.ProvideCDEGatewayStore(db)
infraproviderService := infraprovider2.ProvideInfraProvider(transactor, gitspaceConfigStore, infraProviderResourceStore, infraProviderConfigStore, infraProviderTemplateStore, factory, spaceFinder, cdeGatewayStore)
gitnessSCM := scm.ProvideGitnessSCM(repoStore, repoFinder, gitInterface, tokenStore, principalStore, provider)
genericSCM := scm.ProvideGenericSCM()
scmFactory := scm.ProvideFactory(gitnessSCM, genericSCM)

45
types/cde_gateway.go Normal file
View File

@ -0,0 +1,45 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
const GatewayHealthHealthy = "healthy"
const GatewayHealthUnhealthy = "unhealthy"
type CDEGatewayStats struct {
Name string `json:"name"`
GroupName string `json:"group_name"`
Region string `json:"region"`
Zone string `json:"zone"`
Health string `json:"health"`
EnvoyHealth string `json:"envoy_health"`
Version string `json:"version"`
}
type CDEGateway struct {
CDEGatewayStats
SpaceID int64 `json:"space_id,omitempty"`
SpacePath string `json:"space_path"`
InfraProviderConfigID int64 `json:"infra_provider_config_id,omitempty"`
InfraProviderConfigIdentifier string `json:"infra_provider_config_identifier"`
OverallHealth string `json:"overall_health,omitempty"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
}
type CDEGatewayFilter struct {
Health string `json:"health,omitempty"`
HealthReportValidityInMins int `json:"health_report_validity_in_mins,omitempty"`
InfraProviderConfigIDs []int64 `json:"infra_provider_config_ids,omitempty"`
}