Skip to content

Commit

Permalink
Merge pull request #64 from uselagoon/feature/fix_port_and_add_tests_…
Browse files Browse the repository at this point in the history
…for_sync_command

Fixes logic error with ssh port and introduces regression test
  • Loading branch information
Tim Clifford authored Aug 23, 2022
2 parents 9060e17 + 1ca44d6 commit b06a223
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 92 deletions.
192 changes: 100 additions & 92 deletions cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package cmd

import (
"fmt"
"github.com/manifoldco/promptui"
"github.com/mitchellh/mapstructure"
"log"
"os"
"strings"

"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"github.com/spf13/viper"
synchers "github.com/uselagoon/lagoon-sync/synchers"
Expand All @@ -29,120 +29,124 @@ var noCliInteraction bool
var dryRun bool
var verboseSSH bool
var RsyncArguments string
var runSyncProcess synchers.RunSyncProcessFunctionType

var syncCmd = &cobra.Command{
Use: "sync [mariadb|files|mongodb|postgres|etc.]",
Short: "Sync a resource type",
Long: `Use Lagoon-Sync to sync an external environments resources with the local environment`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
Run: syncCommandRun,
}

SyncerType := args[0]
viper.Set("syncer-type", args[0])
func syncCommandRun(cmd *cobra.Command, args []string) {

lagoonConfigBytestream, err := LoadLagoonConfig(cfgFile)
if err != nil {
utils.LogFatalError("Couldn't load lagoon config file - ", err.Error())
}
SyncerType := args[0]
viper.Set("syncer-type", args[0])

configRoot, err := synchers.UnmarshallLagoonYamlToLagoonSyncStructure(lagoonConfigBytestream)
if err != nil {
log.Fatalf("There was an issue unmarshalling the sync configuration from %v: %v", viper.ConfigFileUsed(), err)
}
lagoonConfigBytestream, err := LoadLagoonConfig(cfgFile)
if err != nil {
utils.LogFatalError("Couldn't load lagoon config file - ", err.Error())
}

// If no project flag is given, find project from env var.
if ProjectName == "" {
project, exists := os.LookupEnv("LAGOON_PROJECT")
if exists {
ProjectName = strings.Replace(project, "_", "-", -1)
}
if configRoot.Project != "" {
ProjectName = configRoot.Project
}
}
configRoot, err := synchers.UnmarshallLagoonYamlToLagoonSyncStructure(lagoonConfigBytestream)
if err != nil {
log.Fatalf("There was an issue unmarshalling the sync configuration from %v: %v", viper.ConfigFileUsed(), err)
}

// Set service default to 'cli'
if ServiceName == "" {
ServiceName = getServiceName(SyncerType)
// If no project flag is given, find project from env var.
if ProjectName == "" {
project, exists := os.LookupEnv("LAGOON_PROJECT")
if exists {
ProjectName = strings.Replace(project, "_", "-", -1)
}

sourceEnvironment := synchers.Environment{
ProjectName: ProjectName,
EnvironmentName: sourceEnvironmentName,
ServiceName: ServiceName,
if configRoot.Project != "" {
ProjectName = configRoot.Project
}
}

// We assume that the target environment is local if it's not passed as an argument
if targetEnvironmentName == "" {
targetEnvironmentName = synchers.LOCAL_ENVIRONMENT_NAME
}
targetEnvironment := synchers.Environment{
ProjectName: ProjectName,
EnvironmentName: targetEnvironmentName,
ServiceName: ServiceName,
}
// Set service default to 'cli'
if ServiceName == "" {
ServiceName = getServiceName(SyncerType)
}

var lagoonSyncer synchers.Syncer
lagoonSyncer, err = synchers.GetSyncerForTypeFromConfigRoot(SyncerType, configRoot)
if err != nil {
utils.LogFatalError(err.Error(), nil)
}
sourceEnvironment := synchers.Environment{
ProjectName: ProjectName,
EnvironmentName: sourceEnvironmentName,
ServiceName: ServiceName,
}

if ProjectName == "" {
utils.LogFatalError("No Project name given", nil)
}
// We assume that the target environment is local if it's not passed as an argument
if targetEnvironmentName == "" {
targetEnvironmentName = synchers.LOCAL_ENVIRONMENT_NAME
}
targetEnvironment := synchers.Environment{
ProjectName: ProjectName,
EnvironmentName: targetEnvironmentName,
ServiceName: ServiceName,
}

if !noCliInteraction {
confirmationResult, err := confirmPrompt(fmt.Sprintf("Project: %s - you are about to sync %s from %s to %s, is this correct",
ProjectName,
SyncerType,
sourceEnvironmentName, targetEnvironmentName))
if err != nil || !confirmationResult {
utils.LogFatalError("User cancelled sync - exiting", nil)
}
}
var lagoonSyncer synchers.Syncer
lagoonSyncer, err = synchers.GetSyncerForTypeFromConfigRoot(SyncerType, configRoot)
if err != nil {
utils.LogFatalError(err.Error(), nil)
}

// SSH Config from file
sshConfig := synchers.SSHOptions{}
if configRoot.LagoonSync["ssh"] != nil {
mapstructure.Decode(configRoot.LagoonSync["ssh"], &sshConfig)
}
sshHost := SSHHost
if sshConfig.Host != "" && SSHHost == "ssh.lagoon.amazeeio.cloud" {
sshHost = sshConfig.Host
}
sshPort := SSHPort
if sshConfig.Port != "" && SSHPort == "32222" {
sshPort = sshConfig.Port
}
if ProjectName == "" {
utils.LogFatalError("No Project name given", nil)
}

sshKey := SSHKey
if sshConfig.PrivateKey != "" && SSHKey == "" {
sshKey = sshConfig.PrivateKey
}
sshVerbose := SSHVerbose
if sshConfig.Verbose && !sshVerbose {
sshVerbose = sshConfig.Verbose
}
sshOptions := synchers.SSHOptions{
Host: sshHost,
PrivateKey: sshKey,
Port: sshPort,
Verbose: sshVerbose,
RsyncArgs: RsyncArguments,
if !noCliInteraction {
confirmationResult, err := confirmPrompt(fmt.Sprintf("Project: %s - you are about to sync %s from %s to %s, is this correct",
ProjectName,
SyncerType,
sourceEnvironmentName, targetEnvironmentName))
if err != nil || !confirmationResult {
utils.LogFatalError("User cancelled sync - exiting", nil)
}
}

// SSH Config from file
sshConfig := synchers.SSHOptions{}
if configRoot.LagoonSync["ssh"] != nil {
mapstructure.Decode(configRoot.LagoonSync["ssh"], &sshConfig)
}
sshHost := SSHHost
if sshConfig.Host != "" && SSHHost == "ssh.lagoon.amazeeio.cloud" {
sshHost = sshConfig.Host
}
sshPort := SSHPort
if sshConfig.Port != "" && SSHPort == "32222" {
sshPort = sshConfig.Port
}

utils.LogDebugInfo("Config that is used for SSH", sshOptions)
sshKey := SSHKey
if sshConfig.PrivateKey != "" && SSHKey == "" {
sshKey = sshConfig.PrivateKey
}

err = synchers.RunSyncProcess(sourceEnvironment, targetEnvironment, lagoonSyncer, SyncerType, dryRun, sshOptions)
if err != nil {
utils.LogFatalError("There was an error running the sync process", err)
}
sshVerbose := SSHVerbose
if sshConfig.Verbose && !sshVerbose {
sshVerbose = sshConfig.Verbose
}
sshOptions := synchers.SSHOptions{
Host: sshHost,
PrivateKey: sshKey,
Port: sshPort,
Verbose: sshVerbose,
RsyncArgs: RsyncArguments,
}

if !dryRun {
log.Printf("\n------\nSuccessful sync of %s from %s to %s\n------", SyncerType, sourceEnvironment.GetOpenshiftProjectName(), targetEnvironment.GetOpenshiftProjectName())
}
},
utils.LogDebugInfo("Config that is used for SSH", sshOptions)

err = runSyncProcess(sourceEnvironment, targetEnvironment, lagoonSyncer, SyncerType, dryRun, sshOptions)
if err != nil {
utils.LogFatalError("There was an error running the sync process", err)
}

if !dryRun {
log.Printf("\n------\nSuccessful sync of %s from %s to %s\n------", SyncerType, sourceEnvironment.GetOpenshiftProjectName(), targetEnvironment.GetOpenshiftProjectName())
}
}

func getServiceName(SyncerType string) string {
Expand Down Expand Up @@ -180,4 +184,8 @@ func init() {
syncCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Don't run the commands, just preview what will be run")
syncCmd.PersistentFlags().StringVarP(&RsyncArguments, "rsync-args", "r", "--omit-dir-times --no-perms --no-group --no-owner --chmod=ugo=rwX --recursive --compress", "Pass through arguments to change the behaviour of rsync")

// By default, we hook up the syncers.RunSyncProcess function to the runSyncProcess variable
// by doing this, it lets us easily override it for testing the command - but for most of the time
// this should be okay.
runSyncProcess = synchers.RunSyncProcess
}
76 changes: 76 additions & 0 deletions cmd/sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package cmd

import (
"errors"
"fmt"
"github.com/spf13/cobra"
"github.com/uselagoon/lagoon-sync/synchers"
"testing"
)

func Test_syncCommandRun(t *testing.T) {
type args struct {
cmd *cobra.Command
args []string
}
tests := []struct {
name string
lagoonYmlFile string
args args
runSyncProcess synchers.RunSyncProcessFunctionType //This will be the thing that drives the actual test
wantsError bool
}{
{
name: "Tests defaults",
lagoonYmlFile: "../test-resources/sync-test/tests-defaults/.lagoon.yml",
args: args{
cmd: nil,
args: []string{
"mariadb",
},
},
runSyncProcess: func(sourceEnvironment synchers.Environment, targetEnvironment synchers.Environment, lagoonSyncer synchers.Syncer, syncerType string, dryRun bool, sshOptions synchers.SSHOptions) error {
if sshOptions.Port != "32222" {
return errors.New(fmt.Sprintf("Expecting ssh port 32222 - found: %v", sshOptions.Port))
}

if sshOptions.Host != "ssh.lagoon.amazeeio.cloud" {
return errors.New(fmt.Sprintf("Expecting ssh host ssh.lagoon.amazeeio.cloud - found: %v", sshOptions.Host))
}

return nil
},
wantsError: false,
},
{
name: "Tests Lagoon yaml",
lagoonYmlFile: "../test-resources/sync-test/tests-lagoon-yml/.lagoon.yml",
args: args{
cmd: nil,
args: []string{
"mariadb",
},
},
runSyncProcess: func(sourceEnvironment synchers.Environment, targetEnvironment synchers.Environment, lagoonSyncer synchers.Syncer, syncerType string, dryRun bool, sshOptions synchers.SSHOptions) error {
if sshOptions.Port != "777" {
return errors.New(fmt.Sprintf("Expecting ssh port 777 - found: %v", sshOptions.Port))
}

if sshOptions.Host != "example.ssh.lagoon.amazeeio.cloud" {
return errors.New(fmt.Sprintf("Expecting ssh host ssh.lagoon.amazeeio.cloud - found: %v", sshOptions.Host))
}

return nil
},
wantsError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runSyncProcess = tt.runSyncProcess
cfgFile = tt.lagoonYmlFile
noCliInteraction = true
syncCommandRun(tt.args.cmd, tt.args.args)
})
}
}
2 changes: 2 additions & 0 deletions synchers/syncutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func UnmarshallLagoonYamlToLagoonSyncStructure(data []byte) (SyncherConfigRoot,
return lagoonConfig, nil
}

type RunSyncProcessFunctionType = func(sourceEnvironment Environment, targetEnvironment Environment, lagoonSyncer Syncer, syncerType string, dryRun bool, sshOptions SSHOptions) error

func RunSyncProcess(sourceEnvironment Environment, targetEnvironment Environment, lagoonSyncer Syncer, syncerType string, dryRun bool, sshOptions SSHOptions) error {
var err error

Expand Down
67 changes: 67 additions & 0 deletions test-resources/sync-test/tests-defaults/.lagoon.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Example .lagoon.yml file with lagoon-sync config added which is used by the sync tool.
docker-compose-yaml: docker-compose.yml

project: "lagoon-sync"

lagoon-sync:
mariadb:
config:
hostname: "$MARIADB_HOST"
username: "$MARIADB_USERNAME"
password: "$MARIADB_PASSWORD"
port: "$MARIADB_PORT"
database: "$MARIADB_DATABASE"
ignore-table:
- "table_to_ignore"
ignore-table-data:
- "cache_data"
- "cache_menu"
local:
config:
hostname: "mariadb"
username: "drupal"
password: "drupal"
port: "3306"
database: "drupal"
postgres:
config:
hostname: "$POSTGRES_HOST"
username: "$POSTGRES_USERNAME"
password: "$POSTGRES_PASSWORD"
port: "5432"
database: "$POSTGRES_DATABASE"
exclude-table:
- "table_to_ignore"
exclude-table-data:
- "cache_data"
- "cache_menu"
local:
config:
hostname: "postgres"
username: "drupal"
password: "drupal"
port: "3306"
database: "drupal"
mongodb:
config:
hostname: "$MONGODB_HOST"
port: "$MONGODB_SERVICE_PORT"
database: "MONGODB_DATABASE"
local:
config:
hostname: "$MONGODB_HOST"
port: "27017"
database: "local"
files:
config:
sync-directory: "/app/web/sites/default/files"
local:
config:
sync-directory: "/app/web/sites/default/files"
drupalconfig:
config:
syncpath: "./config/sync"
local:
overrides:
config:
syncpath: "./config/sync"
Loading

0 comments on commit b06a223

Please sign in to comment.