Skip to content

Commit

Permalink
Refactor: Profile and Task struct integration, directory reorganizati…
Browse files Browse the repository at this point in the history
…on, and abstracted data management. Update: Added transactions and utf8mb4 support in MySQL, improved NCP OS S3 command handling.
  • Loading branch information
heedaeshin committed Sep 6, 2024
1 parent dc4371c commit a85a618
Show file tree
Hide file tree
Showing 24 changed files with 599 additions and 268 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ go.work

# mc-data-manager binary, built with `go build`
mc-data-manager

# Credential
profile.json
2 changes: 0 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package cmd
import (
"os"

"github.com/cloud-barista/mc-data-manager/internal/log"
"github.com/cloud-barista/mc-data-manager/models"
"github.com/spf13/cobra"
)
Expand All @@ -32,7 +31,6 @@ var rootCmd = &cobra.Command{
Long: `It is a tool that builds an environment for verification of data migration technology and
generates test data necessary for data migration.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
log.Info("Data Manager started")
return nil
},
}
Expand Down
225 changes: 91 additions & 134 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,189 +1,146 @@
/*
Copyright 2023 The Cloud-Barista Authors.
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 config

import (
"context"
"encoding/json"
"errors"
"fmt"

"cloud.google.com/go/firestore"
"cloud.google.com/go/storage"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/s3"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"google.golang.org/api/option"
"io"
"os"
"path/filepath"
"sync"
)

func validateInputs(username, password, host *string, port *int) error {
if username == nil || password == nil || host == nil || port == nil {
return errors.New("The input is invalid")
}
return nil
// ConfigManager structure definition
type ConfigManager struct {
DefaultProfile string
ProfileManager ProfileManager
configFilePath string
mu sync.Mutex
}

func newAWSConfig(accesskey, secretkey, region string) (*aws.Config, error) {
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accesskey, secretkey, "")),
config.WithRegion(region),
config.WithRetryMaxAttempts(5),
)

// NewConfigManager loads the config from the specified path
func NewConfigManager(configPath string) (*ConfigManager, error) {
configFilePath := filepath.Join(configPath, "config.json")
defaultProfile, err := loadDefaultProfile(configFilePath)
if err != nil {
return nil, err
}

return &cfg, nil
profilePath := filepath.Join(configPath, "profile", "profile.json")

return &ConfigManager{
DefaultProfile: defaultProfile,
ProfileManager: NewProfileManager(profilePath),
configFilePath: configFilePath,
}, nil
}

func newAWSConfigWithEndpoint(serviceID, accesskey, secretkey, region, endpoint string) (*aws.Config, error) {
customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
if service == serviceID {
return aws.Endpoint{
PartitionID: "aws",
URL: endpoint,
SigningRegion: region,
}, nil
}

return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})

cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accesskey, secretkey, "")),
config.WithRegion(region),
config.WithRetryMaxAttempts(5),
config.WithEndpointResolver(customResolver),
)
// loadDefaultProfile loads the default profile from the config file
func loadDefaultProfile(configFilePath string) (string, error) {
file, err := os.Open(configFilePath)
if err != nil {
return "", err
}
defer file.Close()

data, err := io.ReadAll(file)
if err != nil {
return nil, err
return "", err
}

return &cfg, nil
}
var config struct {
DefaultProfile string `json:"defaultProfile"`
}

func newNCPMongoDBConfig(username, password, host string, port int) *options.ClientOptions {
dc := true
return &options.ClientOptions{
Auth: &options.Credential{
Username: username,
Password: password,
},
Direct: &dc,
Hosts: []string{fmt.Sprintf("%s:%d", host, port)},
if err := json.Unmarshal(data, &config); err != nil {
return "", err
}
}

func NewNCPMongoDBClient(username, password, host string, port int) (*mongo.Client, error) {
if err := validateInputs(&username, &password, &host, &port); err != nil {
return nil, err
if config.DefaultProfile == "" {
return "", errors.New("defaultProfile not set in config.json")
}
return mongo.Connect(context.Background(), newNCPMongoDBConfig(username, password, host, port))

return config.DefaultProfile, nil
}

func NewS3Client(accesskey, secretkey, region string) (*s3.Client, error) {
cfg, err := newAWSConfig(accesskey, secretkey, region)
// CreateConfig creates a new config.json file with the given data
func (cm *ConfigManager) CreateConfig(configData map[string]interface{}) error {
cm.mu.Lock()
defer cm.mu.Unlock()

file, err := os.Create(cm.configFilePath)
if err != nil {
return nil, err
return err
}
defer file.Close()

return s3.NewFromConfig(*cfg, func(o *s3.Options) {
o.UsePathStyle = true
}), nil
}

func NewS3ClientWithEndpoint(accesskey, secretkey, region string, endpoint string) (*s3.Client, error) {
cfg, err := newAWSConfigWithEndpoint(s3.ServiceID, accesskey, secretkey, region, endpoint)
data, err := json.MarshalIndent(configData, "", " ")
if err != nil {
return nil, err
return err
}

return s3.NewFromConfig(*cfg, func(o *s3.Options) {
o.UsePathStyle = true
}), nil
_, err = file.Write(data)
return err
}

func NewDynamoDBClient(accesskey, secretkey, region string) (*dynamodb.Client, error) {
cfg, err := newAWSConfig(accesskey, secretkey, region)
// ReadConfig reads the config.json file and returns the data
func (cm *ConfigManager) ReadConfig() (map[string]interface{}, error) {
cm.mu.Lock()
defer cm.mu.Unlock()

file, err := os.Open(cm.configFilePath)
if err != nil {
return nil, err
}
defer file.Close()

return dynamodb.NewFromConfig(*cfg), nil
}

func NewDynamoDBClientWithEndpoint(accesskey, secretkey, region string, endpoint string) (*dynamodb.Client, error) {
cfg, err := newAWSConfigWithEndpoint(dynamodb.ServiceID, accesskey, secretkey, region, endpoint)
data, err := io.ReadAll(file)
if err != nil {
return nil, err
}

return dynamodb.NewFromConfig(*cfg), nil
}

func NewGCPClient(credentialsJson string) (*storage.Client, error) {
var client *storage.Client
var err error
ctx := context.TODO()
switch {

case credentialsJson != "":
client, err = storage.NewClient(ctx, option.WithCredentialsJSON([]byte(credentialsJson)))

default:
return nil, errors.New("either credentialsFile or credentialsJson must be provided")
}
var configData map[string]interface{}
err = json.Unmarshal(data, &configData)
if err != nil {
return nil, err
}
return client, nil

return configData, nil
}

func NewFireStoreClient(credentialsJson, projectID, databaseID string) (*firestore.Client, error) {
var client *firestore.Client
var err error
// UpdateConfig updates the config.json file with the given data
func (cm *ConfigManager) UpdateConfig(updatedData map[string]interface{}) error {
cm.mu.Lock()
defer cm.mu.Unlock()

ctx := context.TODO()
if databaseID != "" {
client, err = firestore.NewClientWithDatabase(ctx, projectID, databaseID, option.WithCredentialsJSON([]byte(credentialsJson)))
} else {
client, err = firestore.NewClient(ctx, projectID, option.WithCredentialsJSON([]byte(credentialsJson)))
}
// Read the current data
currentData, err := cm.ReadConfig()
if err != nil {
return nil, err
return err
}

return client, nil
// Merge the updated data
for key, value := range updatedData {
currentData[key] = value
}

// Write the merged data back to the file
return cm.CreateConfig(currentData)
}

func NewFireStoreClientWithDatabase(credentialsFile, projectID, databaseID string) (*firestore.Client, error) {
client, err := firestore.NewClientWithDatabase(
context.TODO(),
projectID,
databaseID,
option.WithCredentialsFile(credentialsFile),
)
// DeleteConfig deletes the config.json file
func (cm *ConfigManager) DeleteConfig() error {
cm.mu.Lock()
defer cm.mu.Unlock()

err := os.Remove(cm.configFilePath)
if err != nil {
return nil, err
return err
}

return client, nil
return nil
}

// GetDefaultCredentials returns the default profile credentials
func (cm *ConfigManager) GetDefaultCredentials(provider string) (interface{}, error) {
return cm.ProfileManager.LoadCredentialsByProfile(cm.DefaultProfile, provider)
}
Loading

0 comments on commit a85a618

Please sign in to comment.