Skip to content

Commit

Permalink
feat: add database generator (KusionStack#435)
Browse files Browse the repository at this point in the history
  • Loading branch information
liu-hm19 authored Sep 4, 2023
1 parent 2ce275f commit 6d61d87
Show file tree
Hide file tree
Showing 8 changed files with 604 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package accessories

import (
"fmt"
"os"
"strings"

v1 "k8s.io/api/core/v1"
"kusionstack.io/kusion/pkg/generator/appconfiguration"
"kusionstack.io/kusion/pkg/models"
"kusionstack.io/kusion/pkg/models/appconfiguration/accessories/database"
)

const (
alicloudDBInstance = "alicloud_db_instance"
alicloudDBConnection = "alicloud_db_connection"
alicloudRDSAccount = "alicloud_rds_account"
defaultAlicloudProvider = "registry.terraform.io/aliyun/alicloud/1.209.0"
)

var (
tfProviderAlicloud = os.Getenv("TF_PROVIDER_ALICLOUD")
alicloudProviderRegion = os.Getenv("ALICLOUD_PROVIDER_REGION")
)

type alicloudServerlessConfig struct {
AutoPause bool `yaml:"auto_pause" json:"auto_pause"`
SwitchForce bool `yaml:"switch_force" json:"switch_force"`
MaxCapacity int `yaml:"max_capacity,omitempty" json:"max_capacity,omitempty"`
MinCapacity int `yaml:"min_capacity,omitempty" json:"min_capacity,omitempty"`
}

func (g *databaseGenerator) generateAlicloudResources(db *database.Database, spec *models.Spec) (*v1.Secret, error) {
// Set the terraform random and alicloud provider.
randomProvider := &models.Provider{}
if err := randomProvider.SetString(randomProviderURL); err != nil {
return nil, err
}

// The region of the alicloud provider must be set.
if alicloudProviderRegion == "" {
return nil, fmt.Errorf("the region of the alicloud provider must be set")
}

var providerURL string
alicloudProvider := &models.Provider{}
if tfProviderAlicloud == "" {
providerURL = defaultAlicloudProvider
} else {
providerURL = tfProviderAlicloud
}

if err := alicloudProvider.SetString(providerURL); err != nil {
return nil, err
}

// Build alicloud_db_instance.
alicloudDBInstanceID, r := g.generateAlicloudDBInstance(alicloudProviderRegion, alicloudProvider, db)
spec.Resources = append(spec.Resources, r)

// Build alicloud_db_connection for alicloud_db_instance.
var alicloudDBConnectionID string
if isPublicAccessible(db.SecurityIPs) {
alicloudDBConnectionID, r = g.generateAlicloudDBConnection(alicloudDBInstanceID, alicloudProviderRegion, alicloudProvider, db)
spec.Resources = append(spec.Resources, r)
}

// Build random_password for alicloud_rds_account.
randomPasswordID, r := g.generateTFRandomPassword(randomProvider)
spec.Resources = append(spec.Resources, r)

// Build alicloud_rds_account.
r = g.generateAlicloudRDSAccount(db.Username, randomPasswordID, alicloudDBInstanceID, alicloudProviderRegion, alicloudProvider, db)
spec.Resources = append(spec.Resources, r)

// Inject the host address, username and password into k8s secret.
password := appconfiguration.KusionPathDependency(randomPasswordID, "result")
hostAddress := appconfiguration.KusionPathDependency(alicloudDBInstanceID, "connection_string")
if !db.PrivateRouting {
// Set the public network connection string as the host address.
hostAddress = appconfiguration.KusionPathDependency(alicloudDBConnectionID, "connection_string")
}

return g.generateDBSecret(hostAddress, db.Username, password, spec)
}

func (g *databaseGenerator) generateAlicloudDBInstance(region string,
provider *models.Provider, db *database.Database,
) (string, models.Resource) {
dbAttrs := map[string]interface{}{
"category": db.Category,
"engine": db.Engine,
"engine_version": db.Version,
"instance_storage": db.Size,
"instance_type": db.InstanceType,
"security_ips": db.SecurityIPs,
"vswitch_id": db.SubnetID,
}

// Set serverless specific attributes.
if strings.Contains(db.Category, "serverless") {
dbAttrs["db_instance_storage_type"] = "cloud_essd"
dbAttrs["instance_charge_type"] = "Serverless"

serverlessConfig := alicloudServerlessConfig{
MaxCapacity: 8,
MinCapacity: 1,
}
if db.Engine == "SQLServer" {
serverlessConfig.MinCapacity = 2
} else if db.Engine == "MySQL" {
serverlessConfig.AutoPause = false
serverlessConfig.SwitchForce = false
}
dbAttrs["serverless_config"] = []alicloudServerlessConfig{
serverlessConfig,
}
}

id := appconfiguration.TerraformResourceID(provider, alicloudDBInstance, g.appName)
pvdExts := appconfiguration.ProviderExtensions(provider, map[string]any{
"region": region,
}, alicloudDBInstance)

return id, appconfiguration.TerraformResource(id, nil, dbAttrs, pvdExts)
}

func (g *databaseGenerator) generateAlicloudDBConnection(dbInstanceID, region string,
provider *models.Provider, db *database.Database,
) (string, models.Resource) {
dbConnectionAttrs := map[string]interface{}{
"instance_id": appconfiguration.KusionPathDependency(dbInstanceID, "id"),
}

id := appconfiguration.TerraformResourceID(provider, alicloudDBConnection, g.appName)
pvdExts := appconfiguration.ProviderExtensions(provider, map[string]any{
"region": region,
}, alicloudDBConnection)

return id, appconfiguration.TerraformResource(id, nil, dbConnectionAttrs, pvdExts)
}

func (g *databaseGenerator) generateAlicloudRDSAccount(accountName, randomPasswordID, dbInstanceID, region string,
provider *models.Provider, db *database.Database,
) models.Resource {
rdsAccountAttrs := map[string]interface{}{
"account_name": accountName,
"account_password": appconfiguration.KusionPathDependency(randomPasswordID, "result"),
"account_type": "Super",
"db_instance_id": appconfiguration.KusionPathDependency(dbInstanceID, "id"),
}

id := appconfiguration.TerraformResourceID(provider, alicloudRDSAccount, g.appName)
pvdExts := appconfiguration.ProviderExtensions(provider, map[string]any{
"region": region,
}, alicloudRDSAccount)

return appconfiguration.TerraformResource(id, nil, rdsAccountAttrs, pvdExts)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package accessories

import (
"fmt"
"os"

v1 "k8s.io/api/core/v1"
"kusionstack.io/kusion/pkg/generator/appconfiguration"
"kusionstack.io/kusion/pkg/models"
"kusionstack.io/kusion/pkg/models/appconfiguration/accessories/database"
)

const (
awsSecurityGroup = "aws_security_group"
awsDBInstance = "aws_db_instance"
defaultAWSProvider = "registry.terraform.io/hashicorp/aws/5.0.1"
)

var (
tfProviderAWS = os.Getenv("TF_PROVIDER_AWS")
awsProviderRegion = os.Getenv("AWS_PROVIDER_REGION")
)

type awsSecurityGroupTraffic struct {
CidrBlocks []string `yaml:"cidr_blocks,omitempty" json:"cidr_blocks,omitempty"`
Protocol string `yaml:"protocol,omitempty" json:"protoco,omitempty"`
FromPort int `yaml:"from_port,omitempty" json:"from_port,omitempty"`
ToPort int `yaml:"to_port,omitempty" json:"to_port,omitempty"`
}

func (g *databaseGenerator) generateAWSResources(db *database.Database, spec *models.Spec) (*v1.Secret, error) {
// Set the terraform random and aws provider.
randomProvider := &models.Provider{}
if err := randomProvider.SetString(randomProviderURL); err != nil {
return nil, err
}

// The region of the aws provider must be set.
if awsProviderRegion == "" {
return nil, fmt.Errorf("the region of the aws provider must be set")
}

var providerURL string
awsProvider := &models.Provider{}
if tfProviderAWS == "" {
providerURL = defaultAWSProvider
} else {
providerURL = tfProviderAWS
}

if err := awsProvider.SetString(providerURL); err != nil {
return nil, err
}

// Build random_password for aws_db_instance.
randomPasswordID, r := g.generateTFRandomPassword(randomProvider)
spec.Resources = append(spec.Resources, r)

// Build aws_security group for aws_db_instance.
awsSecurityGroupID, r, err := g.generateAWSSecurityGroup(awsProvider, awsProviderRegion, db)
if err != nil {
return nil, err
}
spec.Resources = append(spec.Resources, r)

// Build aws_db_instance.
awsDBInstanceID, r := g.generateAWSDBInstance(awsProviderRegion, awsSecurityGroupID, randomPasswordID, awsProvider, db)
spec.Resources = append(spec.Resources, r)

// Inject the database host address, username and password into k8s secret.
hostAddress := appconfiguration.KusionPathDependency(awsDBInstanceID, "address")
password := appconfiguration.KusionPathDependency(randomPasswordID, "result")

return g.generateDBSecret(hostAddress, db.Username, password, spec)
}

func (g *databaseGenerator) generateAWSSecurityGroup(
provider *models.Provider,
region string,
db *database.Database,
) (string, models.Resource, error) {
// SecurityIPs should be in the format of IP address or Classes Inter-Domain
// Routing (CIDR) mode.
for _, ip := range db.SecurityIPs {
if !isIPAddress(ip) && !isCIDR(ip) {
return "", models.Resource{}, fmt.Errorf("illegal security ip format: %v", ip)
}
}

sgAttrs := map[string]interface{}{
"egress": []awsSecurityGroupTraffic{
{
CidrBlocks: []string{"0.0.0.0/0"},
Protocol: "-1",
FromPort: 0,
ToPort: 0,
},
},
"ingress": []awsSecurityGroupTraffic{
{
CidrBlocks: db.SecurityIPs,
Protocol: "tcp",
FromPort: 3306,
ToPort: 3306,
},
},
}

id := appconfiguration.TerraformResourceID(provider, awsSecurityGroup, g.appName+dbResSuffix)
pvdExts := appconfiguration.ProviderExtensions(provider, map[string]any{
"region": region,
}, awsSecurityGroup)

return id, appconfiguration.TerraformResource(id, nil, sgAttrs, pvdExts), nil
}

func (g *databaseGenerator) generateAWSDBInstance(region, awsSecurityGroupID, randomPasswordID string,
provider *models.Provider, db *database.Database,
) (string, models.Resource) {
dbAttrs := map[string]interface{}{
"allocated_storage": db.Size,
"engine": db.Engine,
"engine_version": db.Version,
"identifier": g.appName,
"instance_class": db.InstanceType,
"password": appconfiguration.KusionPathDependency(randomPasswordID, "result"),
"publicly_accessible": isPublicAccessible(db.SecurityIPs),
"skip_final_snapshot": true,
"username": db.Username,
"vpc_security_groups_ids": []string{
appconfiguration.KusionPathDependency(awsSecurityGroupID, "id"),
},
}

if db.SubnetID != "" {
dbAttrs["db_subnet_group_name"] = db.SubnetID
}

id := appconfiguration.TerraformResourceID(provider, awsDBInstance, g.appName)
pvdExts := appconfiguration.ProviderExtensions(provider, map[string]any{
"region": region,
}, awsDBInstance)

return id, appconfiguration.TerraformResource(id, nil, dbAttrs, pvdExts)
}
Loading

0 comments on commit 6d61d87

Please sign in to comment.