Browse Source

Add table to track datastores (#4150)

Co-authored-by: Stefan McShane <stefanmcshane@users.noreply.github.com>
Feroze Mohideen 2 years ago
parent
commit
f7ca842fc4

+ 34 - 0
internal/models/datastore.go

@@ -0,0 +1,34 @@
+package models
+
+import (
+	"github.com/google/uuid"
+	"gorm.io/gorm"
+)
+
+// Datastore is a database model that represents a Porter-provisioned datastore
+type Datastore struct {
+	gorm.Model
+
+	// ID is a uuid that references the datastore
+	ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
+
+	// ProjectID is the ID of the project that the datastore belongs to
+	ProjectID uint
+
+	// Name is the name of the datastore
+	Name string
+
+	// CloudProvider is the cloud provider that hosts the Kubernetes Cluster. Accepted values: [AWS, GCP, AZURE]
+	CloudProvider string `json:"cloud_provider"`
+
+	// CloudProviderCredentialIdentifier is a reference to find the credentials required for access the cluster's API.
+	// This was likely the credential that was used to create the cluster.
+	// For AWS EKS clusters, this will be an ARN for the final target role in the assume role chain.
+	CloudProviderCredentialIdentifier string `json:"cloud_provider_credential_identifier"`
+
+	// Type is the type of datastore. Accepted values: [RDS, ELASTICACHE]
+	Type string `json:"type"`
+
+	// Engine is the engine of the datastore. Accepted values: [POSTGRES, AURORA-POSTGRES, REDIS]
+	Engine string `json:"engine"`
+}

+ 15 - 0
internal/repository/datastore.go

@@ -0,0 +1,15 @@
+package repository
+
+import (
+	"context"
+
+	"github.com/porter-dev/porter/internal/models"
+)
+
+// DatastoreRepository represents the set of queries on the Datastore model
+type DatastoreRepository interface {
+	// GetByProjectIDAndName retrieves a datastore by project id and name
+	GetByProjectIDAndName(ctx context.Context, projectID uint, name string) (*models.Datastore, error)
+	// Insert inserts a datastore into the database
+	Insert(ctx context.Context, datastore *models.Datastore) (*models.Datastore, error)
+}

+ 91 - 0
internal/repository/gorm/datastore.go

@@ -0,0 +1,91 @@
+package gorm
+
+import (
+	"context"
+	"time"
+
+	"github.com/google/uuid"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/telemetry"
+	"gorm.io/gorm"
+)
+
+// DatastoreRepository uses gorm.DB for querying the database
+type DatastoreRepository struct {
+	db *gorm.DB
+}
+
+// NewDatastoreRepository returns a DatastoreRepository
+func NewDatastoreRepository(db *gorm.DB) *DatastoreRepository {
+	return &DatastoreRepository{db}
+}
+
+// Insert inserts a datastore into the database
+func (repo *DatastoreRepository) Insert(ctx context.Context, datastore *models.Datastore) (*models.Datastore, error) {
+	ctx, span := telemetry.NewSpan(ctx, "gorm-insert-datastore")
+	defer span.End()
+
+	if datastore == nil {
+		return nil, telemetry.Error(ctx, span, nil, "datastore is nil")
+	}
+
+	if datastore.ProjectID == 0 {
+		return nil, telemetry.Error(ctx, span, nil, "project id is 0")
+	}
+
+	if datastore.Name == "" {
+		return nil, telemetry.Error(ctx, span, nil, "name is empty")
+	}
+
+	if datastore.CloudProvider == "" {
+		return nil, telemetry.Error(ctx, span, nil, "cloud provider is empty")
+	}
+
+	if datastore.CloudProviderCredentialIdentifier == "" {
+		return nil, telemetry.Error(ctx, span, nil, "cloud provider credential identifier is empty")
+	}
+
+	if datastore.Type == "" {
+		return nil, telemetry.Error(ctx, span, nil, "type is empty")
+	}
+
+	if datastore.Engine == "" {
+		return nil, telemetry.Error(ctx, span, nil, "engine is empty")
+	}
+
+	if datastore.ID == uuid.Nil {
+		datastore.ID = uuid.New()
+	}
+	if datastore.CreatedAt.IsZero() {
+		datastore.CreatedAt = time.Now().UTC()
+	}
+	if datastore.UpdatedAt.IsZero() {
+		datastore.UpdatedAt = time.Now().UTC()
+	}
+
+	if err := repo.db.Save(datastore).Error; err != nil {
+		return nil, telemetry.Error(ctx, span, err, "error saving datastore")
+	}
+
+	return datastore, nil
+}
+
+// GetByProjectIDAndName returns a datastore by project ID and name
+func (repo *DatastoreRepository) GetByProjectIDAndName(ctx context.Context, projectId uint, name string) (*models.Datastore, error) {
+	ctx, span := telemetry.NewSpan(ctx, "gorm-get-datastore")
+	defer span.End()
+
+	if projectId == 0 {
+		return nil, telemetry.Error(ctx, span, nil, "project id is 0")
+	}
+	if name == "" {
+		return nil, telemetry.Error(ctx, span, nil, "name is empty")
+	}
+
+	datastore := &models.Datastore{}
+	if err := repo.db.Where("project_id = ? AND name = ?", projectId, name).Limit(1).Find(&datastore).Error; err != nil {
+		return nil, telemetry.Error(ctx, span, err, "error finding datastore")
+	}
+
+	return datastore, nil
+}

+ 1 - 0
internal/repository/gorm/migrate.go

@@ -67,6 +67,7 @@ func AutoMigrate(db *gorm.DB, debug bool) error {
 		&models.DeploymentTarget{},
 		&models.AppTemplate{},
 		&models.GithubWebhook{},
+		&models.Datastore{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},

+ 7 - 0
internal/repository/gorm/repository.go

@@ -57,6 +57,7 @@ type GormRepository struct {
 	appRevision               repository.AppRevisionRepository
 	appTemplate               repository.AppTemplateRepository
 	githubWebhook             repository.GithubWebhookRepository
+	datastore                 repository.DatastoreRepository
 }
 
 func (t *GormRepository) User() repository.UserRepository {
@@ -263,6 +264,11 @@ func (t *GormRepository) GithubWebhook() repository.GithubWebhookRepository {
 	return t.githubWebhook
 }
 
+// Datastore returns the DatastoreRepository interface implemented by gorm
+func (t *GormRepository) Datastore() repository.DatastoreRepository {
+	return t.datastore
+}
+
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
 func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.CredentialStorage) repository.Repository {
@@ -317,5 +323,6 @@ func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.Creden
 		appRevision:               NewAppRevisionRepository(db),
 		appTemplate:               NewAppTemplateRepository(db),
 		githubWebhook:             NewGithubWebhookRepository(db),
+		datastore:                 NewDatastoreRepository(db),
 	}
 }

+ 1 - 0
internal/repository/repository.go

@@ -51,4 +51,5 @@ type Repository interface {
 	AppRevision() AppRevisionRepository
 	AppTemplate() AppTemplateRepository
 	GithubWebhook() GithubWebhookRepository
+	Datastore() DatastoreRepository
 }

+ 29 - 0
internal/repository/test/datastore.go

@@ -0,0 +1,29 @@
+package test
+
+import (
+	"context"
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// DatastoreRepository is a test repository that implements repository.DatastoreRepository
+type DatastoreRepository struct {
+	canQuery bool
+}
+
+// NewDatastoreRepository returns the test DatastoreRepository
+func NewDatastoreRepository() repository.DatastoreRepository {
+	return &DatastoreRepository{canQuery: false}
+}
+
+// GetByProjectIDAndName retrieves a datastore by project id and name
+func (repo *DatastoreRepository) GetByProjectIDAndName(ctx context.Context, projectID uint, name string) (*models.Datastore, error) {
+	return nil, errors.New("cannot read database")
+}
+
+// Insert inserts a datastore into the database
+func (repo *DatastoreRepository) Insert(ctx context.Context, datastore *models.Datastore) (*models.Datastore, error) {
+	return nil, errors.New("cannot write database")
+}

+ 7 - 0
internal/repository/test/repository.go

@@ -55,6 +55,7 @@ type TestRepository struct {
 	appRevision               repository.AppRevisionRepository
 	appTemplate               repository.AppTemplateRepository
 	githubWebhook             repository.GithubWebhookRepository
+	datastore                 repository.DatastoreRepository
 }
 
 func (t *TestRepository) User() repository.UserRepository {
@@ -261,6 +262,11 @@ func (t *TestRepository) GithubWebhook() repository.GithubWebhookRepository {
 	return t.githubWebhook
 }
 
+// Datastore returns a test DatastoreRepository
+func (t *TestRepository) Datastore() repository.DatastoreRepository {
+	return t.datastore
+}
+
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
 func NewRepository(canQuery bool, failingMethods ...string) repository.Repository {
@@ -315,5 +321,6 @@ func NewRepository(canQuery bool, failingMethods ...string) repository.Repositor
 		appRevision:               NewAppRevisionRepository(),
 		appTemplate:               NewAppTemplateRepository(),
 		githubWebhook:             NewGithubWebhookRepository(),
+		datastore:                 NewDatastoreRepository(),
 	}
 }