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: add network, mysql and postgres modules #62

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions modules/mysql/example/dev/example_workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
modules:
kusionstack/[email protected]:
default:
databaseName: test-database
11 changes: 11 additions & 0 deletions modules/mysql/example/dev/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "example"

[dependencies]
kam = { git = "https://github.com/KusionStack/kam.git", tag = "0.1.0" }
network = { oci = "oci://ghcr.io/kusionstack/network", tag = "0.1.0" }
mysql = { oci = "oci://ghcr.io/kusionstack/mysql", tag = "0.1.0" }

[profile]
entries = ["main.k"]

34 changes: 34 additions & 0 deletions modules/mysql/example/dev/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# The configuration codes in perspective of developers.
import kam.v1.app_configuration as ac
import kam.v1.workload as wl
import kam.v1.workload.container as c
import network as n
import mysql.mysql

quickstart: ac.AppConfiguration {
workload: wl.Service {
containers: {
quickstart: c.Container {
image: "kusionstack/kusion-quickstart:latest"
env: {
"DB_HOST": "$(KUSION_DB_HOST_TEST_DATABASE)"
"DB_USERNAME": "$(KUSION_DB_USERNAME_TEST_DATABASE)"
"DB_PASSWORD": "$(KUSION_DB_PASSWORD_TEST_DATABASE)"
}
}
}
}
accessories: {
"network": n.Network {
ports: [
n.Port {
port: 8080
}
]
}
"mysql": mysql.MySQL {
type: "local"
version: "8.0"
}
}
}
1 change: 1 addition & 0 deletions modules/mysql/example/dev/stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: dev
1 change: 1 addition & 0 deletions modules/mysql/example/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: example
3 changes: 3 additions & 0 deletions modules/mysql/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "mysql"
version = "0.1.0"
8 changes: 5 additions & 3 deletions modules/mysql/mysql.k
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ schema MySQL:

import catalog.models.schema.v1.accessories.mysql

mysql: mysql.MySQL {
type: "local"
version: "5.7"
accessories: {
"mysql": mysql.MySQL {
type: "local"
version: "8.0"
}
}
"""

Expand Down
36 changes: 36 additions & 0 deletions modules/mysql/src/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
TEST?=$$(go list ./... | grep -v 'vendor')
###### chang variables below according to your own modules ###
NAMESPACE=kusionstack
NAME=mysql
VERSION=0.1.0
BINARY=../bin/kusion-module-${NAME}_${VERSION}

LOCAL_ARCH := $(shell uname -m)
ifeq ($(LOCAL_ARCH),x86_64)
GOARCH_LOCAL := amd64
else
GOARCH_LOCAL := $(LOCAL_ARCH)
endif
export GOOS_LOCAL := $(shell uname|tr 'A-Z' 'a-z')
export OS_ARCH ?= $(GOARCH_LOCAL)

default: install

build-darwin:
GOOS=darwin GOARCH=arm64 go build -o ${BINARY} ./${NAME}

install: build-darwin
# copy module binary to $KUSION_HOME. e.g. ~/.kusion/modules/kusionstack/mysql/v0.1.0/darwin/arm64/kusion-module-mysql_0.1.0
mkdir -p ${KUSION_HOME}/modules/${NAMESPACE}/${NAME}/${VERSION}/${GOOS_LOCAL}/${OS_ARCH}
cp ${BINARY} ${KUSION_HOME}/modules/${NAMESPACE}/${NAME}/${VERSION}/${GOOS_LOCAL}/${OS_ARCH}

release:
GOOS=darwin GOARCH=arm64 go build -o ${BINARY}_darwin_arm64 ./${NAME}
GOOS=darwin GOARCH=amd64 go build -o ${BINARY}_darwin_amd64 ./${NAME}
GOOS=linux GOARCH=arm64 go build -o ${BINARY}_linux_arm64 ./${NAME}
GOOS=linux GOARCH=amd64 go build -o ${BINARY}_linux_amd64 ./${NAME}
GOOS=windows GOARCH=amd64 go build -o ${BINARY}_windows_amd64 ./${NAME}
GOOS=windows GOARCH=386 go build -o ${BINARY}_windows_386 ./${NAME}

test:
TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 5m
217 changes: 217 additions & 0 deletions modules/mysql/src/alicloud_rds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package main

import (
"errors"
"os"
"strings"

"kusionstack.io/kusion-module-framework/pkg/module"
apiv1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/modules"
)

var ErrEmptyAlicloudProviderRegion = errors.New("empty alicloud provider region")

var (
alicloudRegionEnv = "ALICLOUD_REGION"
alicloudDBInstance = "alicloud_db_instance"
alicloudDBConnection = "alicloud_db_connection"
alicloudRDSAccount = "alicloud_rds_account"
)

var defaultAlicloudProviderCfg = apiv1.ProviderConfig{
Source: "aliyun/alicloud",
Version: "1.209.1",
}

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"`
}

// GenerateAlicloudResources generates Alicloud provided MySQL database instance.
func (mysql *MySQL) GenerateAlicloudResources(request *module.GeneratorRequest) ([]apiv1.Resource, []apiv1.Patcher, error) {
var resources []apiv1.Resource
var patchers []apiv1.Patcher

// Set the Alicloud provider with the default provider config.
alicloudProviderCfg := defaultAlicloudProviderCfg

// Get the Alicloud Terraform provider region, which should not be empty.
var region string
if region = module.TerraformProviderRegion(alicloudProviderCfg); region == "" {
region = os.Getenv(alicloudRegionEnv)
}
if region == "" {
return nil, nil, ErrEmptyAlicloudProviderRegion
}

// Build random_password resource.
randomPasswordRes, randomPasswordID, err := mysql.GenerateTFRandomPassword(request)
if err != nil {
return nil, nil, err
}
resources = append(resources, *randomPasswordRes)

// Build alicloud_db_instance resource.
alicloudDBInstanceRes, alicloudDBInstanceID, err := mysql.generateAlicloudDBInstance(
alicloudProviderCfg, region,
)
if err != nil {
return nil, nil, err
}
resources = append(resources, *alicloudDBInstanceRes)

// Build alicloud_db_connection resource.
var alicloudDBConnectionRes *apiv1.Resource
var alicloudDBConnectionID string
if IsPublicAccessible(mysql.SecurityIPs) {
alicloudDBConnectionRes, alicloudDBConnectionID, err = mysql.generateAlicloudDBConnection(
alicloudProviderCfg,
region, alicloudDBInstanceID,
)
if err != nil {
return nil, nil, err
}

resources = append(resources, *alicloudDBConnectionRes)
}

// Build alicloud_rds_account resuorce.
alicloudRDSAccountRes, err := mysql.generateAlicloudRDSAccount(
alicloudProviderCfg,
region, mysql.Username, randomPasswordID, alicloudDBInstanceID,
)
if err != nil {
return nil, nil, err
}
resources = append(resources, *alicloudRDSAccountRes)

hostAddress := modules.KusionPathDependency(alicloudDBInstanceID, "connection_string")
if !mysql.PrivateRouting {
// Set the public network connection string as the host address.
hostAddress = modules.KusionPathDependency(alicloudDBConnectionID, "connection_string")
}
password := modules.KusionPathDependency(randomPasswordID, "result")

// Build Kubernetes Secret with the hostAddress, username and password of the Alicloud provided MySQL instance,
// and inject the credentials as the environment variable patcher.
dbSecret, pathcer, err := mysql.GenerateDBSecret(request, hostAddress, mysql.Username, password)
if err != nil {
return nil, nil, err
}
resources = append(resources, *dbSecret)
patchers = append(patchers, *pathcer)

return resources, patchers, nil
}

// generateAlicloudDBInstance generates alicloud_db_instance resource
// for the Alicloud provided MySQL database instance.
func (mysql *MySQL) generateAlicloudDBInstance(alicloudProviderCfg apiv1.ProviderConfig,
region string,
) (*apiv1.Resource, string, error) {
resAttrs := map[string]interface{}{
"category": mysql.Category,
"engine": "MySQL",
"engine_version": mysql.Version,
"instance_storage": mysql.Size,
"instance_type": mysql.InstanceType,
"security_ips": mysql.SecurityIPs,
"vswitch_id": mysql.SubnetID,
"instance_name": mysql.DatabaseName,
}

// Set the serverless-specific attributes of the alicloud_db_instance resource.
if strings.Contains(mysql.Category, "serverless") {
resAttrs["db_instance_storage_type"] = "cloud_essd"
resAttrs["instance_charge_type"] = "Serverless"

serverlessConfig := alicloudServerlessConfig{
MaxCapacity: 8,
MinCapacity: 1,
}
serverlessConfig.AutoPause = false
serverlessConfig.SwitchForce = false

resAttrs["serverless_config"] = []alicloudServerlessConfig{
serverlessConfig,
}
}

id, err := module.TerraformResourceID(alicloudProviderCfg, alicloudDBInstance, mysql.DatabaseName)
if err != nil {
return nil, "", err
}

resExts, err := module.TerraformProviderExtensions(alicloudProviderCfg, map[string]any{"region": region}, alicloudDBInstance)
if err != nil {
return nil, "", err
}

resource, err := module.WrapTFResourceToKusionResource(id, resAttrs, resExts, nil)
if err != nil {
return nil, "", err
}

return resource, id, nil
}

// generateAlicloudDBConnection generates alicloud_db_connection resource
// for the Alicloud provided MySQL database instance.
func (mysql *MySQL) generateAlicloudDBConnection(alicloudProviderCfg apiv1.ProviderConfig,
region, dbInstanceID string,
) (*apiv1.Resource, string, error) {
resAttrs := map[string]interface{}{
"instance_id": modules.KusionPathDependency(dbInstanceID, "id"),
}

id, err := module.TerraformResourceID(alicloudProviderCfg, alicloudDBConnection, mysql.DatabaseName)
if err != nil {
return nil, "", err
}

resExts, err := module.TerraformProviderExtensions(alicloudProviderCfg, map[string]any{"region": region}, alicloudDBConnection)
if err != nil {
return nil, "", err
}

resource, err := module.WrapTFResourceToKusionResource(id, resAttrs, resExts, nil)
if err != nil {
return nil, "", err
}

return resource, id, nil
}

// generateAlicloudRDSAccount generates alicloud_rds_account resource
// for the Alicloud provided MySQL database instance.
func (mysql *MySQL) generateAlicloudRDSAccount(alicloudProviderCfg apiv1.ProviderConfig,
region, accountName, randomPasswordID, dbInstanceID string,
) (*apiv1.Resource, error) {
resAttrs := map[string]interface{}{
"account_name": accountName,
"account_password": modules.KusionPathDependency(randomPasswordID, "result"),
"account_type": "Super",
"db_instance_id": modules.KusionPathDependency(dbInstanceID, "id"),
}

id, err := module.TerraformResourceID(alicloudProviderCfg, alicloudRDSAccount, mysql.DatabaseName)
if err != nil {
return nil, err
}

resExts, err := module.TerraformProviderExtensions(alicloudProviderCfg, map[string]any{"region": region}, alicloudRDSAccount)
if err != nil {
return nil, err
}

resource, err := module.WrapTFResourceToKusionResource(id, resAttrs, resExts, nil)
if err != nil {
return nil, err
}

return resource, nil
}
Loading
Loading