diff --git a/README.md b/README.md index 738517c..ff53171 100644 --- a/README.md +++ b/README.md @@ -4,285 +4,144 @@ Lagoon-sync is cli tool written in Go that fundamentally provides the functional Lagoon-sync offers: * Sync commands for databases such as `mariadb`, `postgres` and `mongodb` -* Php/node-based framework support such as Drupal, Laravel or Node.js * Standard file transfer support with `files` syncer * Has built-in default configuration values for syncing out-the-box -* Provides an easy way to override sync configuration via `.lagoon.yml` or `.lagoon-sync.yml` files +* Provides an easy way to override sync configuration via `.lagoon-sync.yml` files * Offers `--dry-run` flag to see what commands would be executed before running a transfer * `--no-interaction` can be used to auto-run all processes without prompt - useful for CI/builds * `config` command shows the configuration of the current environment * There is a `--show-debug` flag to output more verbose logging for debugging -* Lagoon-sync uses `rsync` for the transfer of data, and will automatically detect and install `rsync` if it is not available on target environments * Secure cross-platform self-updating with `selfUpdate` command -# Installing +# Documentation -You can run `lagoon-sync` as a single binary by downloading from `https://github.com/uselagoon/lagoon-sync/releases/latest`. +See this document for a brief tutorial on getting started with `lagoon-sync`. Other topics covered in the documentation are -MacOS: `lagoon-sync_*.*.*_darwin_amd64` -Linux (3 variants available): `lagoon-sync_*.*.*_linux_386` -Windows: `lagoon-sync_*.*.*_windows_amd64.exe` +* [Installation options](./documentation/INSTALLATION.md) +* [More details about custom configurations](./documentation/CONFIG.md) +* [Lagoon-sync usage examples](./documentation/EXAMPLE_SYNCS.md) +* [Writing custom synchers](./documentation/CUSTOM.md) +* [Contributing](./documentation/CONTRIBUTING.md) +* [Odds and ends](./documentation/MISC.md) -To install via bash: -## macOS (with M1 processors) +# Tutorial - Getting started with lagoon-sync - DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep darwin_arm64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync +Here we'll describe the typical use case for lagoon-sync. While it's able to do quite a number of things, we're going to +speak about the standard use case - that is, how do we use lagoon-sync to sync our databases and our files between environments. -## macOS (with Intel processors) +We'll focus on a very simple example, setting up `lagoon-sync` for a Laravel project. - DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep darwin_amd64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync +This tutorial assumes that you've already [lagoonized](https://docs.lagoon.sh/lagoonizing/) your project, and that you have a `docker-compose.yml` file that +describes the services that you're going to need. -## Linux (386) +## Where does `lagoon-sync` actually run? - DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep linux_386) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync +This is a common question we get, because it can be kind of confusing. Where exactly are you supposed to run `lagoon-sync`? -## Linux (amd64) +Well, it will always run _inside a container_ - it's not a tool like the lagoon cli that just runs from anywhere. +`lagoon-sync` is essentially a wrapper around commands like `mysqldump`, `rsync`, `mongodump`, etc. +In fact, everything that `lagoon-sync` does, you could do manually if you `ssh`ed into your running containers and typed out the various commands. - DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep linux_amd64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync +There is no special, secret sauce. It's more like a collection of neat `bash` scripts than anything else. And so, like with `bash` scripts, +it needs to run in the actual containers. -## Linux (arm64) +This means that `lagoon-sync` needs to be available insider your container - typically, we find the easiest way of doing this is +including it in your `cli` dockerfile, if you have one. - DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep linux_arm64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync +## .lagoon-sync.yml -# Usage +You can run `lagoon-sync` in a myriad ways. But here is the simplest, most straight forward, that should work in most cases. +We encourage this pattern. -Lagoon-sync has the following core commands: +You can add a `.lagoon-sync.yml` file to the root of your application's source code, alongside your `.lagoon.yml` file. -``` -$ lagoon-sync -lagoon-sync is a tool for syncing resources between environments in Lagoon hosted applications. This includes files, databases, and configurations. - -Usage: - lagoon-sync [command] - -Available Commands: - completion generate the autocompletion script for the specified shell - config Print the config that is being used by lagoon-sync - help Help about any command - selfUpdate Update this tool to the latest version - sync Sync a resource type - version Print the version number of lagoon-sync - -Flags: - --config string config file (default is .lagoon.yaml) (default "./.lagoon.yml") - -h, --help help for lagoon-sync - --show-debug Shows debug information - -t, --toggle Help message for toggle - -v, --version version for lagoon-sync - -Use "lagoon-sync [command] --help" for more information about a command. -``` +This `.lagoon-sync.yml` will describe all of the syncs that you might want `lagoon-sync` to do. Each option for syncing will +appear as a separate item under the `lagoon-sync:` key. -## sync - -Sync transfers are executed with `$lagoon-sync sync ` and require at least a syncer type `[mariadb|files|mongodb|postgres|drupalconfig]`, a valid project name `-p` and source environment `-e`. By default, if you do not provide an optional target environment `-t` then `local` is used. - -``` -lagoon-sync sync - -Usage: - lagoon-sync sync [mariadb|files|mongodb|postgres|etc.] [flags] - -Flags: - -c, --configuration-file string File containing sync configuration. - --dry-run Don't run the commands, just preview what will be run - -h, --help help for sync - --no-interaction Disallow interaction - -p, --project-name string The Lagoon project name of the remote system - -r, --rsync-args string Pass through arguments to change the behaviour of rsync (default "--omit-dir-times --no-perms --no-group --no-owner --chmod=ugo=rwX -r") - -s, --service-name string The service name (default is 'cli' - -e, --source-environment-name string The Lagoon environment name of the source system - -i, --ssh-key string Specify path to a specific SSH key to use for authentication - -t, --target-environment-name string The target environment name (defaults to local) - --verbose Run ssh commands in verbose (useful for debugging) - -Global Flags: - --config string config file (default is .lagoon.yaml) (default "./.lagoon.yml") - --show-debug Shows debug information -``` - -## config - -The `config` command will output all current configuration information it can find on the environment. This is used, for example, to gather prerequisite data which can be used to determine how `lagoon-sync` should proceed with a transfer. For example, when running the tool on a environment that doesn't have rsync, then the syncer will know to install a static copy of rsync on that machine for us. This is because rsync requires that you need to have it available on both environments in order to transfer. - -This can be run with: - -`$ lagoon-sync config` - -## Example syncs - -As with all sync commands, if you run into issues you can run `--show-debug` to see extra log information. There is also the `config` command which is useful to see what configuration files are active. - -### Mariadb sync from remote source -> local environment -An example sync between a `mariadb` database from a remote source environment to your local instance may go as follows: - -Running `$ lagoon-sync sync mariadb -p amazeelabsv4-com -e dev --dry-run` would dry-run a process that takes a database dump, runs a data transfer and then finally syncs the local database with the latest dump. - -### Mariadb sync from remote source -> remote target environment -To transfer between remote environments you can pass in a target argument such as: - -`$ lagoon-sync sync mariadb -p amazeelabsv4-com -e prod -t dev --dry-run` - -### Mariadb sync from remote source to local file (*Dump only*) - -It's also possible to simply generate a backup from one of the remote servers by using the options -`--skip-target-cleanup=true`, which doesn't delete temporary transfer files, and `--skip-target-import=true` which -skips actually importing the database locally. - -`$ lagoon-sync sync mariadb -p amazeelabsv4-com -e prod -t dev --skip-target-cleanup=true --skip-target-import=true` - -You will then see the transfer-resource name listed in the output. - -This command would attempt to sync mariadb databases from `prod` to `dev` environments. - -## Configuring lagoon-sync - -Lagoon-sync configuration can be managed via yaml-formatted configuration files. The paths to these config files can be defined either by the `--config` argument, or by environment variables (`LAGOON_SYNC_PATH` or `LAGOON_SYNC_DEFAULTS_PATH`). - -The order of configuration precedence is as follows: - -1. `--config` argument (e.g. `lagoon-sync [command] --config ./.custom-lagoon-sync-config.yaml`). -2. `.lagoon.yaml` files (i.e. in project root, or `lagoon` directory). If an `.lagoon.yml` is available within the project, then this file will be used as the active configuration file by default. -3. `LAGOON_SYNC_PATH` or `LAGOON_SYNC_DEFAULTS_PATH` environment variables. -4. Finally, if no config file can be found the default configuration will be used a safely written to a new '.lagoon.yml` - -There are some configuration examples in the `examples` directory of this repo. - -2021/01/22 11:34:10 (DEBUG) Using config file: /lagoon/.lagoon-sync -2021/01/22 11:34:10 (DEBUG) Config that will be used for sync: - { - "Config": { - "DbHostname": "$MARIADB_HOST", - "DbUsername": "$MARIADB_USERNAME", - "DbPassword": "$MARIADB_PASSWORD", - "DbPort": "$MARIADB_PORT", - "DbDatabase": "$MARIADB_DATABASE", - ... - -To recap, the configuration files that can be used by default, in order of priority when available are: -* /lagoon/.lagoon-sync-defaults -* /lagoon/.lagoon-sync -* .lagoon.yml - -### Custom synchers - -It's possible to extend lagoon-sync to define your own sync processes. As lagoon-sync is essentially a -script runner that runs commands on target and source systems, as well as transferring data between the two systems, -it's possible to define commands that generate the transfer resource and consume it on the target. - -For instance, if you have [mtk](https://github.com/skpr/mtk) set up on the target machine, it should be possible to -define a custom syncher that makes use of mtk to generate a sanitized DB dump on the source, and then use mysql to -import it on the target. - -This is done by defining three things: -* The transfer resource name (what file is going to be synced across the network) - in this case let's call it "/tmp/dump.sql" -* The command(s) to run on the source -* The command(s) to run target +Let's look at an [example](./examples/tutorial/.lagoon-sync.yml): ``` lagoon-sync: - mtkdump: - transfer-resource: "/tmp/dump.sql" - source: - commands: - - "mtk-dump > {{ .transferResource }}" - target: - commands: - - "mysql -h${MARIADB_HOST:-mariadb} -u${MARIADB_USERNAME:-drupal} -p${MARIADB_PASSWORD:-drupal} -P${MARIADB_PORT:-3306} ${MARIADB_DATABASE:-drupal} < {{ .transfer-resource }}" -``` - -This can then be called by running the following: -``` -lagoon-sync sync mtkdump -p -e -``` - -### Custom configuration files -If you don't want your configuration file inside `/lagoon` and want to give it another name then you can define a custom file and tell sync to use that by providing the file path. This can be done with `--config` flag such as:Config files that can be used in order of priority: -- .lagoon-sync-defaults _(no yaml ext neeeded)_ -- .lagoon-sync _(no yaml ext neeeded)_ -- .lagoon.yml _Main config file - path can be given as an argument with `--config`, default is `.lagoon.yml`_ -å -``` -$ lagoon-sync sync mariadb -p mysite-com -e dev --config=/app/.lagoon-sync --show-debug - -2021/01/22 11:43:50 (DEBUG) Using config file: /app/.lagoon-sync -``` - -You can also use an environment variable to set the config sync path with either `LAGOON_SYNC_PATH` or `LAGOON_SYNC_DEFAULTS_PATH`. - -``` -$ LAGOON_SYNC_PATH=/app/.lagoon-sync lagoon-sync sync mariadb -p mysite-com -e dev --show-debug - -2021/01/22 11:46:42 (DEBUG) LAGOON_SYNC_PATH env var found: /app/.lagoon-sync -2021/01/22 11:46:42 (DEBUG) Using config file: /app/.lagoon-sync + mariadb: + type: mariadb + config: + hostname: "${MARIADB_HOST:-mariadb}" + username: "${MARIADB_USERNAME:-lagoon}" + password: "${MARIADB_PASSWORD:-lagoon}" + port: "${MARIADB_PORT:-3306}" + database: "${MARIADB_DATABASE:-lagoon}" + cli: + type: files + config: + sync-directory: "/app/storage/" ``` +This lagoon sync config above describes two synchers - `mariadb` for the database, and `cli` for the files. +With this in my `.lagoon-sync.yml`, inside my `cli` container, I can run the command +`lagoon-sync sync cli -p myprojectname -e sourceenvironment` and `lagoon-sync` will rsync all the files in `sourceenvironment`'s +`/app/storage/` directory into my local environment. -To double check which config file is loaded you can also run the `lagoon-sync config` command. - +The same will be the case, except it will sync the database, if I ran `lagoon-sync sync mariadb -p myprojectname -e sourceenvironment`. -### Example sync config overrides +Note, that in the example above, `mariadb` and `cli` are simply aliases, we could have renamed them to `mydatabase` and `filestorage` like so: ``` lagoon-sync: - mariadb: - config: - hostname: "${MARIADB_HOST:-mariadb}" - username: "${MARIADB_USERNAME:-drupal}" - password: "${MARIADB_PASSWORD:-drupal}" - port: "${MARIADB_PORT:-3306}" - database: "${MARIADB_DATABASE:-drupal}" - files: + mydatabase: + type: mariadb config: - sync-directory: "/app/web/sites/default/files" - drupalconfig: +<..snip../> + filestorage: + type: files config: - syncpath: "./config/sync" + sync-directory: "/app/storage/" ``` -# Useful things -## Updating lagoon-sync +and run the syncs with `lagoon-sync sync mydatabase -p mypr...` and `lagoon-sync sync filestorage -p mypr...` +The nested keys `cli` and `mariadb` are simply names - it's the `type:` key that tells `lagoon-sync` what it's actually syncing. -It's possible to safely perform a cross-platform update of your lagoon-sync binary by running the `$ lagoon-sync selfUpdate` command. This will look for the latest release, then download the corresponding checksum and signature of the executable on GitHub, and verify its integrity and authenticity before it performs the update. The binary used to perform the update will then replace itself (if successful) to the new version. If an error occurs then the update will roll back to the previous stable version. +You can define as many of these synchers as you need - if you have multiple databases, for instance, or, more likely, if you +have multiple files/directories you'd like to sync separately. -``` -$ lagoon-sync selfUpdate +## How should I be generating a `.lagoon-sync.yml`? -Downloading binary from https://github.com/uselagoon/lagoon-sync/releases/download/v0.4.4/lagoon-sync_0.4.4_linux_386 -Checksum for linux_386: 61a55bd793d5745b6196ffd5bb87263aba85629f55ee0eaf53c771a0720adefd -Good signature from "amazeeio" -Applying update... -Successfully updated binary at: /usr/bin/lagoon-sync -``` +This is the part of the process that seems to trip most people up, so we've made it fairly simple. -You can check version with `$ lagoon-sync --version` +If you'd like to generate a `.lagoon-sync.yml`, you can use `lagoon-sync`'s built in functions `generate` and `interactive-config`. -## Installing binary from script - Drupal example +The `generate` command tries to take your lagoonized `docker-compose.yml` file and generate a `.lagoon-sync.yml` file based +off what it finds in the service definition. -This example will run a script that will install a Linux lagoon-sync binary and default configuration file for a Drupal project. +The [example](./examples/tutorial/.lagoon-sync.yml) above was actually generated by [this docker-compose file](./examples/tutorial/docker-compose.yml). -``` -wget -q -O - https://gist.githubusercontent.com/timclifford/cec9fe3ddf8d0805e4801d132dfce682/raw/a9979ff24290a500f53df09723774216603de6b5/lagoon-sync-drupal-install.sh | bash -``` +In order to generate a file, simply run `lagoon-sync generate ./docker-compose.yml -o .lagoon-sync.yml` and it should, +hopefully, generate a reasonable lagoon sync config. + +If you'd like to be more hands-on you can run `lagoon-sync interactive-config -o .lagoon-sync.yml` and you'll be presented with a +menu that you can use to generate a sync config. + +## What's all this about clusters? + +If your project is on anything except the amazeeio cluster, which are the defaults +and you're running lagoon-sync from a local container, you may have to set these variables +you can grab this information from running the lagoon cli's `lagoon config list` +this will output the ssh endpoints and ports you need. -# Contributing +Typically, though, this information is also available in the environment variables +LAGOON_CONFIG_SSH_HOST and LAGOON_CONFIG_SSH_PORT -Setting up locally: +These, for instance, are the amazeeio defaults - and should do for most people. +If you're on your own cluster, these are the same values that will be in your `.lagoon.yml` -* `make all` Installs missing dependencies, runs tests and build locally. -* `make build` Compiles binary based on current go env. -* `make local-build-linux` Compile linix binary. -* `make local-build-darwin` Compile macOS (darwin) binary. -* `make check-current-tag-version` Check the current version. -* `make clean` Remove all build files and assets. +* `ssh: ssh.lagoon.amazeeio.cloud:32222` +* `api: https://api.lagoon.amazeeio.cloud/graphql` -## Releases +## Please note -We are using [goreleaser](https://github.com/goreleaser/goreleaser) for the official build, release and publish steps that will be run from a GitHub Action on a pushed tag event. +At the moment, the generator and wizard only support the most commonly used cases - files, mariadb, and postgres. +Mongodb is actually a far more complex beast, and we'll add some more support for it in the future. -Locally, we can run `make release-test` to check if our changes will build. If compiling was successful we can commit our changes and then run `make release-[patch|minor|major]` to tag with next release number and it will push up to GitHub. A GitHub action will then be triggered which will publish the official release using goreleaser. -Prior to that, we can locally test our release to ensure that it will successfully build with `make release-test`. If compiling was successful we can commit our changes and then run `make release-[patch|minor|major]` to tag with next release number and it will push up to GitHub. A GitHub action will then be triggered which will publish the official release using goreleaser. diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..ece414b --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "bytes" + "fmt" + "github.com/spf13/cobra" + "github.com/uselagoon/lagoon-sync/generator" + "log" + "os" + "text/template" +) + +var generateCmd = &cobra.Command{ + Use: "generate path/to/docker-compose.yml", + Short: "Generate a lagoon-sync configuration stanza from a docker-compose file", + Long: `Attempts to generate a lagoon-sync configuration from a docker-compose file. +Currently supports filesystem definitions, mariadb/mysql services, and postgres. +`, + + Args: cobra.MinimumNArgs(1), + Run: genCommandRun, +} + +var outputfile string + +func genCommandRun(cmd *cobra.Command, args []string) { + + _, err := os.Stat(args[0]) + if err != nil { + log.Fatal(err) + } + + project, err := generator.LoadComposeFile(args[0]) + if err != nil { + log.Fatal(err) + } + + services := generator.ProcessServicesFromCompose(project) + + stanza, err := generator.BuildConfigStanzaFromServices(services) + + const yamlTemplate = ` +{{ .Sync }} +` + + tmpl, err := template.New("yaml").Parse(yamlTemplate) + if err != nil { + log.Fatal(err) + } + + var output bytes.Buffer + err = tmpl.Execute(&output, struct { + Sync string + }{ + Sync: stanza, + }) + + if err != nil { + log.Fatal(err) + } + + if outputfile != "" { + // Create or open the file + file, err := os.Create(outputfile) + if err != nil { + fmt.Println("Error creating file:", err) + return + } + defer file.Close() + + // Write the string to the file + _, err = file.WriteString(output.String()) + if err != nil { + fmt.Println("Error writing to file:", err) + return + } + fmt.Println("Successfully wrote output to : " + outputfile) + } else { + fmt.Println(output.String()) + } + +} + +func init() { + rootCmd.AddCommand(generateCmd) + generateCmd.PersistentFlags().StringVarP(&outputfile, "outputfile", "o", "", "Write output to file - outputs to STDOUT if unset") +} diff --git a/cmd/root.go b/cmd/root.go index 3caee6f..15757f3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -122,9 +122,9 @@ func processConfig(cfgFile string) error { // Next, check for common lagoon config files and override defaults. for _, path := range paths { cfgFiles := []string{ - filepath.Join(path, ".lagoon.yml"), filepath.Join(path, ".lagoon-sync.yml"), filepath.Join(path, ".lagoon-sync"), + filepath.Join(path, ".lagoon.yml"), } for _, filePath := range cfgFiles { if utils.FileExists(filePath) { diff --git a/cmd/root_test.go b/cmd/root_test.go deleted file mode 100644 index 6c39c75..0000000 --- a/cmd/root_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package cmd - -import "testing" - -func Test_processConfigEnv(t *testing.T) { - type args struct { - paths []string - DefaultConfigFileName string - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "Initial test", - args: args{ - paths: []string{"../test-resources/config-tests/initial-test"}, - DefaultConfigFileName: "intial-test-lagoon.yml", - }, - want: "intial-test-lagoon.yml", - wantErr: false, - }, - { - name: "Initial test - Empty path", - args: args{ - paths: []string{}, - DefaultConfigFileName: "../test-resources/config-tests/initial-test/intial-test-lagoon.yml", - }, - want: "../test-resources/config-tests/initial-test/intial-test-lagoon.yml", - wantErr: false, - }, - { - name: "Initial test - Multiple paths", - args: args{ - paths: []string{"../test-resources/sync-test/tests-defaults", "../test-resources/sync-test/tests-lagoon-yml", "../test-resources/config-tests/initial-test"}, - DefaultConfigFileName: ".lagoon.yml", - }, - want: ".lagoon.yml", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := processConfigEnv(tt.args.paths, tt.args.DefaultConfigFileName) - if (err != nil) != tt.wantErr { - t.Errorf("processConfigEnv() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("processConfigEnv() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/cmd/sync.go b/cmd/sync.go index 8acd00c..9fab8cf 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -52,9 +52,17 @@ var syncCmd = &cobra.Command{ } func syncCommandRun(cmd *cobra.Command, args []string) { + + // SyncerType can be one of two things + // 1. a direct reference to a syncer - i.e. mariadb, postgres, files + // 2. a reference to an alias in the configuration file (.lagoon.yml/.lagoon-sync.yml) + // 3. a reference to a custom syncer, also defined in the config file. SyncerType := args[0] + viper.Set("syncer-type", args[0]) + // configRoot will be filled with the configuration information that is passed + // to the syncers when they're run. var configRoot synchers.SyncherConfigRoot if viper.ConfigFileUsed() == "" { @@ -112,7 +120,7 @@ func syncCommandRun(cmd *cobra.Command, args []string) { // Syncers are registered in their init() functions - so here we attempt to match // the syncer type with the argument passed through to this command // (e.g. if we're running `lagoon-sync sync mariadb --...options follow` the function - // GetSyncersForTypeFromConfigRoot will return a prepared mariadb syncher object) + // GetSyncersForTypeFromConfigRoot will return a prepared mariadb syncer object) lagoonSyncer, err := synchers.GetSyncerForTypeFromConfigRoot(SyncerType, configRoot) if err != nil { // Let's ask the custom syncer if this will work, if so, we fall back on it ... @@ -142,13 +150,29 @@ func syncCommandRun(cmd *cobra.Command, args []string) { if configRoot.LagoonSync["ssh"] != nil { mapstructure.Decode(configRoot.LagoonSync["ssh"], &sshConfig) } + sshHost := SSHHost - if sshConfig.Host != "" && SSHHost == "ssh.lagoon.amazeeio.cloud" { - sshHost = sshConfig.Host + if SSHHost == "ssh.lagoon.amazeeio.cloud" { // we're using the default - lets see if there are other options + envSshHost, exists := os.LookupEnv("LAGOON_CONFIG_SSH_HOST") + if exists { // we prioritize env data + sshHost = envSshHost + } else { + if sshConfig.Host != "" { + sshHost = sshConfig.Host + } + } } + sshPort := SSHPort - if sshConfig.Port != "" && SSHPort == "32222" { - sshPort = sshConfig.Port + if SSHPort == "32222" { // we're using the default - lets see if there are other options + envSshPort, exists := os.LookupEnv("LAGOON_CONFIG_SSH_PORT") + if exists { // we prioritize env data + sshPort = envSshPort + } else { + if sshConfig.Port != "" { + sshPort = sshConfig.Port + } + } } sshKey := SSHKey @@ -238,6 +262,9 @@ func syncCommandRun(cmd *cobra.Command, args []string) { } } +// getServiceName will return the name of the service in which we run the commands themselves. This is typically +// the cli pod in a project +// TODO: this needs to be expanded to be dynamic in the future. func getServiceName(SyncerType string) string { if SyncerType == "mongodb" { return SyncerType diff --git a/cmd/wizard.go b/cmd/wizard.go new file mode 100644 index 0000000..f168dd6 --- /dev/null +++ b/cmd/wizard.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + "github.com/uselagoon/lagoon-sync/generator" + "log" + "os" +) + +var wizardCmd = &cobra.Command{ + Use: "interactive-config", + Short: "Generate a lagoon-sync configuration stanza interactively", + Long: `This command shows a wizard that will help with generating a .lagoon-sync.yml style yaml stanza`, + Run: genwizCommandRun, +} + +func genwizCommandRun(cmd *cobra.Command, args []string) { + + str, gerr := generator.RunWizard() + + if gerr != nil { + log.Fatal(gerr) + } + + if outputfile != "" { + // Create or open the file + file, err := os.Create(outputfile) + if err != nil { + fmt.Println("Error creating file:", err) + return + } + defer file.Close() + + // Write the string to the file + _, err = file.WriteString(str) + if err != nil { + fmt.Println("Error writing to file:", err) + return + } + fmt.Println("Successfully wrote output to : " + outputfile) + } else { + fmt.Println(str) + } + +} + +func init() { + rootCmd.AddCommand(wizardCmd) + wizardCmd.PersistentFlags().StringVarP(&outputfile, "outputfile", "o", "", "Write output to file - outputs to STDOUT if unset") +} diff --git a/documentation/CONFIG.md b/documentation/CONFIG.md new file mode 100644 index 0000000..199c583 --- /dev/null +++ b/documentation/CONFIG.md @@ -0,0 +1,82 @@ + +# config + +The `config` command will output all current configuration information it can find on the environment. This is used, for example, to gather prerequisite data which can be used to determine how `lagoon-sync` should proceed with a transfer. For example, when running the tool on a environment that doesn't have rsync, then the syncer will know to install a static copy of rsync on that machine for us. This is because rsync requires that you need to have it available on both environments in order to transfer. + +This can be run with: + +`$ lagoon-sync config` + +# Configuring lagoon-sync + +Lagoon-sync configuration can be managed via yaml-formatted configuration files. The paths to these config files can be defined either by the `--config` argument, or by environment variables (`LAGOON_SYNC_PATH` or `LAGOON_SYNC_DEFAULTS_PATH`). + +The order of configuration precedence is as follows: + +1. `--config` argument (e.g. `lagoon-sync [command] --config ./.custom-lagoon-sync-config.yaml`). +2. `.lagoon-sync.yml` typically contains a separate lagoon-sync configuration. Although this can, if required, be merged into the `.lagoon.yml` file +3. `.lagoon.yaml` files (i.e. in project root, or `lagoon` directory). If an `.lagoon.yml` is available within the project, then this file will be used as the active configuration file by default. +4. `LAGOON_SYNC_PATH` or `LAGOON_SYNC_DEFAULTS_PATH` environment variables. +5. Finally, if no config file can be found the default configuration will be used a safely written to a new '.lagoon.yml` + +There are some configuration examples in the `examples` directory of this repo. + +2021/01/22 11:34:10 (DEBUG) Using config file: /lagoon/.lagoon-sync +2021/01/22 11:34:10 (DEBUG) Config that will be used for sync: +{ +"Config": { +"DbHostname": "$MARIADB_HOST", +"DbUsername": "$MARIADB_USERNAME", +"DbPassword": "$MARIADB_PASSWORD", +"DbPort": "$MARIADB_PORT", +"DbDatabase": "$MARIADB_DATABASE", +... + +To recap, the configuration files that can be used by default, in order of priority when available are: +* /lagoon/.lagoon-sync-defaults +* /lagoon/.lagoon-sync +* .lagoon.yml + + +## Custom configuration files +If you don't want your configuration file inside `/lagoon` and want to give it another name then you can define a custom file and tell sync to use that by providing the file path. This can be done with `--config` flag such as:Config files that can be used in order of priority: +- .lagoon-sync-defaults _(no yaml ext neeeded)_ +- .lagoon-sync _(no yaml ext neeeded)_ +- .lagoon.yml _Main config file - path can be given as an argument with `--config`, default is `.lagoon.yml`_ + å +``` +$ lagoon-sync sync mariadb -p mysite-com -e dev --config=/app/.lagoon-sync --show-debug + +2021/01/22 11:43:50 (DEBUG) Using config file: /app/.lagoon-sync +``` + +You can also use an environment variable to set the config sync path with either `LAGOON_SYNC_PATH` or `LAGOON_SYNC_DEFAULTS_PATH`. + +``` +$ LAGOON_SYNC_PATH=/app/.lagoon-sync lagoon-sync sync mariadb -p mysite-com -e dev --show-debug + +2021/01/22 11:46:42 (DEBUG) LAGOON_SYNC_PATH env var found: /app/.lagoon-sync +2021/01/22 11:46:42 (DEBUG) Using config file: /app/.lagoon-sync +``` + + +To double check which config file is loaded you can also run the `lagoon-sync config` command. + + +### Example sync config overrides +``` +lagoon-sync: + mariadb: + config: + hostname: "${MARIADB_HOST:-mariadb}" + username: "${MARIADB_USERNAME:-drupal}" + password: "${MARIADB_PASSWORD:-drupal}" + port: "${MARIADB_PORT:-3306}" + database: "${MARIADB_DATABASE:-drupal}" + files: + config: + sync-directory: "/app/web/sites/default/files" + drupalconfig: + config: + syncpath: "./config/sync" +``` diff --git a/documentation/CONTRIBUTING.md b/documentation/CONTRIBUTING.md new file mode 100644 index 0000000..4a1891f --- /dev/null +++ b/documentation/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contributing + +Setting up locally: + +* `make all` Installs missing dependencies, runs tests and build locally. +* `make build` Compiles binary based on current go env. +* `make local-build-linux` Compile linix binary. +* `make local-build-darwin` Compile macOS (darwin) binary. +* `make check-current-tag-version` Check the current version. +* `make clean` Remove all build files and assets. + +## Releases + +We are using [goreleaser](https://github.com/goreleaser/goreleaser) for the official build, release and publish steps that will be run from a GitHub Action on a pushed tag event. + +Locally, we can run `make release-test` to check if our changes will build. If compiling was successful we can commit our changes and then run `make release-[patch|minor|major]` to tag with next release number and it will push up to GitHub. A GitHub action will then be triggered which will publish the official release using goreleaser. + +Prior to that, we can locally test our release to ensure that it will successfully build with `make release-test`. If compiling was successful we can commit our changes and then run `make release-[patch|minor|major]` to tag with next release number and it will push up to GitHub. A GitHub action will then be triggered which will publish the official release using goreleaser. diff --git a/documentation/CUSTOM.md b/documentation/CUSTOM.md new file mode 100644 index 0000000..a007e9a --- /dev/null +++ b/documentation/CUSTOM.md @@ -0,0 +1,31 @@ +# Custom synchers + +It's possible to extend lagoon-sync to define your own sync processes. As lagoon-sync is essentially a +script runner that runs commands on target and source systems, as well as transferring data between the two systems, +it's possible to define commands that generate the transfer resource and consume it on the target. + +For instance, if you have [mtk](https://github.com/skpr/mtk) set up on the target machine, it should be possible to +define a custom syncher that makes use of mtk to generate a sanitized DB dump on the source, and then use mysql to +import it on the target. + +This is done by defining three things: +* The transfer resource name (what file is going to be synced across the network) - in this case let's call it "/tmp/dump.sql" +* The command(s) to run on the source +* The command(s) to run target + +``` +lagoon-sync: + mtkdump: + transfer-resource: "/tmp/dump.sql" + source: + commands: + - "mtk-dump > {{ .transferResource }}" + target: + commands: + - "mysql -h${MARIADB_HOST:-mariadb} -u${MARIADB_USERNAME:-drupal} -p${MARIADB_PASSWORD:-drupal} -P${MARIADB_PORT:-3306} ${MARIADB_DATABASE:-drupal} < {{ .transfer-resource }}" +``` + +This can then be called by running the following: +``` +lagoon-sync sync mtkdump -p -e +``` diff --git a/documentation/EXAMPLE_SYNCS.md b/documentation/EXAMPLE_SYNCS.md new file mode 100644 index 0000000..fbfbb79 --- /dev/null +++ b/documentation/EXAMPLE_SYNCS.md @@ -0,0 +1,56 @@ +# The 'sync' command + +Sync transfers are executed with `$lagoon-sync sync ` and require at least a syncer type `[mariadb|files|mongodb|postgres|drupalconfig]`, a valid project name `-p` and source environment `-e`. By default, if you do not provide an optional target environment `-t` then `local` is used. + +``` +lagoon-sync sync + +Usage: + lagoon-sync sync [mariadb|files|mongodb|postgres|etc.] [flags] + +Flags: + -c, --configuration-file string File containing sync configuration. + --dry-run Don't run the commands, just preview what will be run + -h, --help help for sync + --no-interaction Disallow interaction + -p, --project-name string The Lagoon project name of the remote system + -r, --rsync-args string Pass through arguments to change the behaviour of rsync (default "--omit-dir-times --no-perms --no-group --no-owner --chmod=ugo=rwX -r") + -s, --service-name string The service name (default is 'cli' + -e, --source-environment-name string The Lagoon environment name of the source system + -i, --ssh-key string Specify path to a specific SSH key to use for authentication + -t, --target-environment-name string The target environment name (defaults to local) + --verbose Run ssh commands in verbose (useful for debugging) + +Global Flags: + --config string config file (default is .lagoon.yaml) (default "./.lagoon.yml") + --show-debug Shows debug information +``` + + + +## Example syncs + +As with all sync commands, if you run into issues you can run `--show-debug` to see extra log information. There is also the `config` command which is useful to see what configuration files are active. + +### Mariadb sync from remote source -> local environment +An example sync between a `mariadb` database from a remote source environment to your local instance may go as follows: + +Running `$ lagoon-sync sync mariadb -p amazeelabsv4-com -e dev --dry-run` would dry-run a process that takes a database dump, runs a data transfer and then finally syncs the local database with the latest dump. + +### Mariadb sync from remote source -> remote target environment +To transfer between remote environments you can pass in a target argument such as: + +`$ lagoon-sync sync mariadb -p amazeelabsv4-com -e prod -t dev --dry-run` + +### Mariadb sync from remote source to local file (*Dump only*) + +It's also possible to simply generate a backup from one of the remote servers by using the options +`--skip-target-cleanup=true`, which doesn't delete temporary transfer files, and `--skip-target-import=true` which +skips actually importing the database locally. + +`$ lagoon-sync sync mariadb -p amazeelabsv4-com -e prod -t dev --skip-target-cleanup=true --skip-target-import=true` + +You will then see the transfer-resource name listed in the output. + +This command would attempt to sync mariadb databases from `prod` to `dev` environments. + diff --git a/documentation/INSTALLATION.md b/documentation/INSTALLATION.md new file mode 100644 index 0000000..15d20e6 --- /dev/null +++ b/documentation/INSTALLATION.md @@ -0,0 +1,31 @@ +# Installing + +You can run `lagoon-sync` as a single binary by downloading from `https://github.com/uselagoon/lagoon-sync/releases/latest`. + +MacOS: `lagoon-sync_*.*.*_darwin_amd64` +Linux (3 variants available): `lagoon-sync_*.*.*_linux_386` +Windows: `lagoon-sync_*.*.*_windows_amd64.exe` + +To install via bash: + + +## macOS (with M1 processors) + + DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep darwin_arm64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync + +## macOS (with Intel processors) + + DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep darwin_amd64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync + +## Linux (386) + + DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep linux_386) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync + +## Linux (amd64) + + DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep linux_amd64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync + +## Linux (arm64) + + DOWNLOAD_PATH=$(curl -sL "https://api.github.com/repos/uselagoon/lagoon-sync/releases/latest" | grep "browser_download_url" | cut -d \" -f 4 | grep linux_arm64) && wget -O /usr/local/bin/lagoon-sync $DOWNLOAD_PATH && chmod a+x /usr/local/bin/lagoon-sync + diff --git a/documentation/MISC.md b/documentation/MISC.md new file mode 100644 index 0000000..bf5a90e --- /dev/null +++ b/documentation/MISC.md @@ -0,0 +1,24 @@ +# Useful things +## Updating lagoon-sync + +It's possible to safely perform a cross-platform update of your lagoon-sync binary by running the `$ lagoon-sync selfUpdate` command. This will look for the latest release, then download the corresponding checksum and signature of the executable on GitHub, and verify its integrity and authenticity before it performs the update. The binary used to perform the update will then replace itself (if successful) to the new version. If an error occurs then the update will roll back to the previous stable version. + +``` +$ lagoon-sync selfUpdate + +Downloading binary from https://github.com/uselagoon/lagoon-sync/releases/download/v0.4.4/lagoon-sync_0.4.4_linux_386 +Checksum for linux_386: 61a55bd793d5745b6196ffd5bb87263aba85629f55ee0eaf53c771a0720adefd +Good signature from "amazeeio" +Applying update... +Successfully updated binary at: /usr/bin/lagoon-sync +``` + +You can check version with `$ lagoon-sync --version` + +## Installing binary from script - Drupal example + +This example will run a script that will install a Linux lagoon-sync binary and default configuration file for a Drupal project. + +``` +wget -q -O - https://gist.githubusercontent.com/timclifford/cec9fe3ddf8d0805e4801d132dfce682/raw/a9979ff24290a500f53df09723774216603de6b5/lagoon-sync-drupal-install.sh | bash +``` diff --git a/examples/tutorial/.lagoon-sync.yml b/examples/tutorial/.lagoon-sync.yml new file mode 100644 index 0000000..1c865ca --- /dev/null +++ b/examples/tutorial/.lagoon-sync.yml @@ -0,0 +1,28 @@ +# Below is your configuration for lagoon-sync. +# These data can live in either a separate .lagoon-sync.yml file +# or your .lagoon.yml file. + +# If your project is on anything except the amazeeio cluster, which are the defaults +# and you're running lagoon-sync from a local container, you may have to set these variables +# you can grab this information from running the lagoon cli's "lagoon config list" +# this will output the ssh endpoints and ports you need. +# Typically, though, this information is also available in the environment variables +# LAGOON_CONFIG_SSH_HOST and LAGOON_CONFIG_SSH_PORT +# +# These, for instance, are the amazeeio defaults +# ssh: ssh.lagoon.amazeeio.cloud:32222 +# api: https://api.lagoon.amazeeio.cloud/graphql + +lagoon-sync: + mariadb: + type: mariadb + config: + hostname: "${MARIADB_HOST:-mariadb}" + username: "${MARIADB_USERNAME:-lagoon}" + password: "${MARIADB_PASSWORD:-lagoon}" + port: "${MARIADB_PORT:-3306}" + database: "${MARIADB_DATABASE:-lagoon}" + cli: + type: files + config: + sync-directory: "/app/storage/" diff --git a/examples/tutorial/docker-compose.yml b/examples/tutorial/docker-compose.yml new file mode 100644 index 0000000..3162ad0 --- /dev/null +++ b/examples/tutorial/docker-compose.yml @@ -0,0 +1,95 @@ +version: '2.3' + +x-volumes: + &default-volumes + # Define all volumes you would like to have real-time mounted into the docker containers + volumes: + - ./:/app:delegated ### Local overrides to mount host filesystem. Automatically removed in CI and PROD. + +x-environment: + &default-environment + # Route that should be used locally, if you are using pygmy, this route *must* end with .docker.amazee.io + LAGOON_ROUTE: &default-url http://${COMPOSE_PROJECT_NAME:-laravel10}.docker.amazee.io + # Uncomment if you like to have the system behave like in production + #LAGOON_ENVIRONMENT_TYPE: production + # Uncomment to enable xdebug and then restart via `docker-compose up -d` + #XDEBUG_ENABLE: "true" + +x-user: + &default-user + # The default user under which the containers should run. Change this if you are on linux and run with another user than id `1000` + user: '1000' + +volumes: + files: + {} + +services: + + cli: # cli container, will be used for executing composer and any local commands (drush, drupal, etc.) + build: + context: ./ + dockerfile: .lagoon/cli.dockerfile + image: &cli-image ${COMPOSE_PROJECT_NAME:-laravel10}-cli # this image will be reused as `CLI_IMAGE` in subsequent Docker builds + labels: + # Lagoon Labels + lagoon.type: cli-persistent + lagoon.persistent.name: nginx # mount the persistent storage of nginx into this container + lagoon.persistent: /app/storage/ # location where the persistent storage should be mounted + # lando.type: php-cli-drupal + << : *default-volumes # loads the defined volumes from the top + user: root + volumes_from: ### mount the ssh-agent from the pygmy or cachalot ssh-agent. Automatically removed in CI. + - container:amazeeio-ssh-agent ### Local overrides to mount host SSH keys. Automatically removed in CI. + environment: + << : *default-environment # loads the defined environment variables from the top + + nginx: + build: + context: ./ + dockerfile: .lagoon/nginx.dockerfile + args: + CLI_IMAGE: *cli-image # Inject the name of the cli image + labels: + lagoon.type: nginx-php-persistent + lagoon.persistent: /app/storage/ # define where the persistent storage should be mounted too + << : *default-volumes # loads the defined volumes from the top + user: '1000' + depends_on: + - cli # basically just tells docker-compose to build the cli first + environment: + << : *default-environment # loads the defined environment variables from the top + LAGOON_LOCALDEV_URL: *default-url + networks: + - amazeeio-network + - default + php: + build: + context: ./ + dockerfile: .lagoon/php.dockerfile + args: + CLI_IMAGE: *cli-image + labels: + lagoon.type: nginx-php-persistent + lagoon.name: nginx # we want this service be part of the nginx pod in Lagoon + lagoon.persistent: /app/storage/ # define where the persistent storage should be mounted too + << : *default-volumes # loads the defined volumes from the top + user: '1000' + depends_on: + - cli # basically just tells docker-compose to build the cli first + environment: + << : *default-environment # loads the defined environment variables from the top + + mariadb: + image: uselagoon/mariadb-10.5:latest + labels: + lagoon.type: mariadb + ports: + - "3306" # exposes the port 3306 with a random local port, find it with `docker-compose port mariadb 3306` + user: '1000' + environment: + << : *default-environment + +networks: + amazeeio-network: + external: true \ No newline at end of file diff --git a/generator/docker-compose.go b/generator/docker-compose.go new file mode 100644 index 0000000..4b4eca9 --- /dev/null +++ b/generator/docker-compose.go @@ -0,0 +1,69 @@ +package generator + +import ( + "github.com/compose-spec/compose-go/cli" + "github.com/compose-spec/compose-go/loader" + "github.com/compose-spec/compose-go/types" + "strings" +) + +// docker-compose.go contains all the functionality needed to parse docker compose files for lagoon labels +// and generate sync definitions + +type LagoonServiceDefinition struct { + ServiceName string + ServiceType string + image string + Labels map[string]string +} + +func LoadComposeFile(composeFile string) (*types.Project, error) { + // Load the Compose file + + // NOTE: importantly - we're using a very permissive parsing scheme here + // including the upstream library used in the lagoon build-deploy tool + // we're only interested in pulling the service definitions here - so it's not so much of a problem. + projectOptions, err := cli.NewProjectOptions([]string{composeFile}, + cli.WithResolvedPaths(false), + cli.WithLoadOptions( + loader.WithSkipValidation, + loader.WithDiscardEnvFiles, + func(o *loader.Options) { + o.IgnoreNonStringKeyErrors = true + o.IgnoreMissingEnvFileCheck = true + }, + ), + ) + if err != nil { + return nil, err + } + + project, err := cli.ProjectFromOptions(projectOptions) + if err != nil { + return nil, err + } + + return project, nil +} + +func ProcessServicesFromCompose(project *types.Project) []LagoonServiceDefinition { + var serviceDefinitions []LagoonServiceDefinition + for _, service := range project.Services { + sd := LagoonServiceDefinition{ + ServiceName: service.Name, + image: service.Image, + Labels: map[string]string{}, + } + // we only process this if this _has_ a lagoon.type, and that is not "none" + lt, exists := service.Labels["lagoon.type"] + lt = strings.ToLower(lt) + if exists && lt != "none" { + sd.ServiceType = lt + for k, v := range service.Labels { + sd.Labels[k] = v + } + serviceDefinitions = append(serviceDefinitions, sd) + } + } + return serviceDefinitions +} diff --git a/generator/docker-compose_test.go b/generator/docker-compose_test.go new file mode 100644 index 0000000..56894c4 --- /dev/null +++ b/generator/docker-compose_test.go @@ -0,0 +1,84 @@ +package generator + +import ( + "testing" +) + +func TestProcessServicesFromCompose(t *testing.T) { + type args struct { + ComposeFile string + } + tests := []struct { + name string + args args + want []LagoonServiceDefinition + wantErr bool + }{ + { + name: "Read service defs", + args: args{ComposeFile: "./test-assets/drupal-docker-compose.yml"}, + want: []LagoonServiceDefinition{ + { + ServiceName: "cli", + ServiceType: "cli-persistent", + }, + { + ServiceName: "nginx", + ServiceType: "nginx-php-persistent", + image: "drupal-base-nginx:latest", + }, + { + ServiceName: "php", + ServiceType: "nginx-php-persistent", + image: "drupal-base-php:latest", + }, + { + ServiceName: "mariadb", + ServiceType: "mariadb", + image: "uselagoon/mariadb-10.11-drupal:latest", + }, + }, + wantErr: false, + }, + { + name: "Broken file - should pass because we're using old spec", + args: args{ComposeFile: "./test-assets/docker-compose-broken.yml"}, + want: []LagoonServiceDefinition{ + { + ServiceName: "mariadb", + ServiceType: "mariadb", + image: "uselagoon/mariadb-10.5-drupal:latest", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + project, err := LoadComposeFile(tt.args.ComposeFile) + if err != nil && tt.wantErr == false { + t.Errorf("Unexpected error loading file: %v \n", tt.args.ComposeFile) + return + } + if err != nil && tt.wantErr == true { + return + } + services := ProcessServicesFromCompose(project) + for _, v := range tt.want { + if !testDockerComposeServiceHasService(services, v) { + t.Errorf("Could not find service %v in file", v.ServiceName) + } + } + }) + } +} + +func testDockerComposeServiceHasService(serviceDefinitions []LagoonServiceDefinition, serviceDef LagoonServiceDefinition) bool { + // here we match the incoming services to the name + for _, v := range serviceDefinitions { + if v.ServiceName == serviceDef.ServiceName && v.ServiceType == serviceDef.ServiceType && serviceDef.image == v.image { + return true + } + } + return false +} diff --git a/generator/syncdefGenerator.go b/generator/syncdefGenerator.go new file mode 100644 index 0000000..bed1d23 --- /dev/null +++ b/generator/syncdefGenerator.go @@ -0,0 +1,249 @@ +package generator + +import ( + "bytes" + "errors" + "fmt" + "github.com/uselagoon/lagoon-sync/synchers" + "log" + "strings" + "text/template" +) + +type configTemplateData struct { + Mariadb []synchers.MariadbSyncRoot + Filesystem []synchers.FilesSyncRoot + Postgres []synchers.PostgresSyncRoot + Ssh string + Api string +} + +func BuildConfigStanzaFromServices(services []LagoonServiceDefinition) (string, error) { + + mariadbServices := []synchers.MariadbSyncRoot{} + filesystemServices := []synchers.FilesSyncRoot{} + postgresServices := []synchers.PostgresSyncRoot{} + serviceCount := 0 + // we go through the service definitions and try to generate text for them + for _, v := range services { + switch v.ServiceType { + case "cli-persistent": //cli and cli-persisten + sr, err := GenerateFilesSyncRootFromPersistentService(v) + if err != nil { + return "", err + } + serviceCount += 1 + filesystemServices = append(filesystemServices, sr) + fallthrough // in case there are multiple volumes also defined in this cli + case "cli": + // now we generate from multivolume, if they're there + srs, err := GenerateFilesSyncRootsFromServiceDefinition(v) + if err != nil { + return "", err + } + serviceCount += len(srs) + filesystemServices = append(filesystemServices, srs...) + break + case "mariadb", "mariadb-single", "mariadb-dbaas": + sr, err := GenerateMariadbSyncRootFromService(v) + if err != nil { + return "", err + } + serviceCount += 1 + mariadbServices = append(mariadbServices, sr) + break + case "postgres", "postgres-single", "postgres-dbaas": + sr, err := GeneratePgqlSyncRootFromService(v) + if err != nil { + return "", err + } + serviceCount += 1 + postgresServices = append(postgresServices, sr) + } + } + + if serviceCount == 0 { + return "", errors.New("No sync definitions were able to be extracted from service list") + } + + templateData := configTemplateData{ + Mariadb: mariadbServices, + Filesystem: filesystemServices, + Postgres: postgresServices, + } + + retString, err := generateSyncStanza(templateData) + if err != nil { + log.Fatal(err) + } + + return retString, nil +} + +func generateSyncStanza(templateData configTemplateData) (string, error) { + const yamlTemplate = ` +# Below is your configuration for lagoon-sync. +# These data can live in either a separate .lagoon-sync.yml file +# or your .lagoon.yml file. + +# If your project is on anything except the amazeeio cluster, which are the defaults +# and you're running lagoon-sync from a local container, you may have to set these variables +# you can grab this information from running the lagoon cli's "lagoon config list" +# this will output the ssh endpoints and ports you need. +# Typically, though, this information is also available in the environment variables +# LAGOON_CONFIG_SSH_HOST and LAGOON_CONFIG_SSH_PORT +# +# These, for instance, are the amazeeio defaults +# ssh: ssh.lagoon.amazeeio.cloud:32222 +# api: https://api.lagoon.amazeeio.cloud/graphql +{{- if ne .Ssh ""}} +ssh: {{ .Ssh }} +{{- end}} +{{- if ne .Api ""}} +api: {{ .Api }} +{{- end}} + +lagoon-sync: +{{- range .Mariadb }} + {{ .ServiceName }}: + type: {{ .Type }} + config: + hostname: "{{ .Config.DbHostname }}" + username: "{{ .Config.DbUsername }}" + password: "{{ .Config.DbPassword }}" + port: "{{ .Config.DbPort }}" + database: "{{ .Config.DbDatabase }}" +{{- end }} +{{- range .Postgres }} + {{ .ServiceName }}: + type: {{ .Type }} + config: + hostname: "{{ .Config.DbHostname }}" + username: "{{ .Config.DbUsername }}" + password: "{{ .Config.DbPassword }}" + port: "{{ .Config.DbPort }}" + database: "{{ .Config.DbDatabase }}" +{{- end }} +{{- range .Filesystem }} + {{ .ServiceName }}: + type: {{ .Type }} + config: + sync-directory: "{{ .Config.SyncPath }}" +{{- end }} +` + // Parse and execute the template + tmpl, err := template.New("yaml").Parse(yamlTemplate) + if err != nil { + return "", err + } + + var output bytes.Buffer + err = tmpl.Execute(&output, templateData) + if err != nil { + return "", err + } + + retString := output.String() + return retString, nil +} + +func GenerateMariadbSyncRootFromService(definition LagoonServiceDefinition) (synchers.MariadbSyncRoot, error) { + + // the main configuration detail we're interested in here is really the defaults for the host etc. + serviceNameUppercase := strings.ToUpper(definition.ServiceName) + + syncRoot := synchers.MariadbSyncRoot{ + Type: synchers.MariadbSyncPlugin{}.GetPluginId(), + ServiceName: definition.ServiceName, + Config: synchers.BaseMariaDbSync{}, + } + syncRoot.Config.SetDefaults() + + // now we try to infer the defaults for password and username + defaultUser := "" + defaultHost := ":-" + definition.ServiceName + defaultPassword := "" + defaultDatabase := "" + if definition.image != "" && strings.Contains(definition.image, "uselagoon/mariadb") { + + if strings.Contains(definition.image, "drupal") { + defaultUser = ":-drupal" + defaultDatabase = ":-drupal" + defaultPassword = ":-drupal" + } else { + defaultUser = ":-lagoon" + defaultDatabase = ":-lagoon" + defaultPassword = ":-lagoon" + } + + } + + syncRoot.Config.DbHostname = fmt.Sprintf("${%v_HOST%v}", serviceNameUppercase, defaultHost) + syncRoot.Config.DbUsername = fmt.Sprintf("${%v_USERNAME%v}", serviceNameUppercase, defaultUser) + syncRoot.Config.DbPassword = fmt.Sprintf("${%v_PASSWORD%v}", serviceNameUppercase, defaultPassword) + syncRoot.Config.DbPort = fmt.Sprintf("${%v_PORT:-3306}", serviceNameUppercase) + syncRoot.Config.DbDatabase = fmt.Sprintf("${%v_DATABASE%v}", serviceNameUppercase, defaultDatabase) + + return syncRoot, nil +} + +func GeneratePgqlSyncRootFromService(definition LagoonServiceDefinition) (synchers.PostgresSyncRoot, error) { + + // the main configuration detail we're interested in here is really the defaults for the host etc. + serviceNameUppercase := strings.ToUpper(definition.ServiceName) + + syncRoot := synchers.PostgresSyncRoot{ + Type: synchers.PostgresSyncPlugin{}.GetPluginId(), + ServiceName: definition.ServiceName, + Config: synchers.BasePostgresSync{}, + } + + syncRoot.Config.SetDefaults() + + if serviceNameUppercase != strings.ToUpper(synchers.PostgresSyncPlugin{}.GetPluginId()) { + syncRoot.Config.DbHostname = fmt.Sprintf("${%v_HOST:-postgres}", serviceNameUppercase) + syncRoot.Config.DbUsername = fmt.Sprintf("${%v_USERNAME:-drupal}", serviceNameUppercase) + syncRoot.Config.DbPassword = fmt.Sprintf("${%v_PASSWORD:-drupal}", serviceNameUppercase) + syncRoot.Config.DbPort = fmt.Sprintf("${%v_PORT:-5432}", serviceNameUppercase) + syncRoot.Config.DbDatabase = fmt.Sprintf("${%v_DATABASE:-drupal}", serviceNameUppercase) + } + + return syncRoot, nil +} + +func GenerateFilesSyncRootFromPersistentService(definition LagoonServiceDefinition) (synchers.FilesSyncRoot, error) { + syncRoot := synchers.FilesSyncRoot{ + ServiceName: definition.ServiceName, + Type: synchers.FilesSyncPlugin{}.GetPluginId(), + Config: synchers.BaseFilesSync{}, + } + v, exists := definition.Labels["lagoon.persistent"] + + if !exists { + return syncRoot, errors.New("Could not find the 'lagoon.persistent' label in service: " + definition.ServiceName) + } + + syncRoot.Config.SyncPath = v + + return syncRoot, nil +} + +func GenerateFilesSyncRootsFromServiceDefinition(definition LagoonServiceDefinition) ([]synchers.FilesSyncRoot, error) { + syncRoots := []synchers.FilesSyncRoot{} + for k, v := range definition.Labels { + labelParts := strings.Split(k, ".") + if len(labelParts) == 4 && labelParts[0] == "lagoon" && labelParts[1] == "volumes" && labelParts[3] == "path" { + syncRoot := synchers.FilesSyncRoot{ + ServiceName: fmt.Sprintf("%v-%v", definition.ServiceName, labelParts[2]), + Type: "files", + Config: synchers.BaseFilesSync{ + SyncPath: v, + }, + } + + syncRoots = append(syncRoots, syncRoot) + + } + } + return syncRoots, nil +} diff --git a/generator/syncdefGenerator_test.go b/generator/syncdefGenerator_test.go new file mode 100644 index 0000000..d0085d0 --- /dev/null +++ b/generator/syncdefGenerator_test.go @@ -0,0 +1,339 @@ +package generator + +import ( + "github.com/uselagoon/lagoon-sync/synchers" + "reflect" + "strings" + "testing" +) + +func TestGenerateMariadbSyncRootFromService(t *testing.T) { + type args struct { + definition LagoonServiceDefinition + } + tests := []struct { + name string + args args + want synchers.MariadbSyncRoot + wantErr bool + }{ + { + name: "Default case - mariadb service name", + args: args{definition: LagoonServiceDefinition{ + ServiceName: "mariadb", + ServiceType: "mariadb", + Labels: nil, + }}, + want: synchers.MariadbSyncRoot{ + Type: synchers.MariadbSyncPlugin{}.GetPluginId(), + ServiceName: "mariadb", + Config: synchers.BaseMariaDbSync{ + DbHostname: "${MARIADB_HOST:-mariadb}", + DbUsername: "${MARIADB_USERNAME:-drupal}", + DbPassword: "${MARIADB_PASSWORD:-drupal}", + DbPort: "${MARIADB_PORT:-3306}", + DbDatabase: "${MARIADB_DATABASE:-drupal}", + }, + }, + }, + { + name: "Special case - custom service name", + args: args{definition: LagoonServiceDefinition{ + ServiceName: "db", + ServiceType: "mariadb", + Labels: nil, + }}, + want: synchers.MariadbSyncRoot{ + Type: synchers.MariadbSyncPlugin{}.GetPluginId(), + ServiceName: "db", + Config: synchers.BaseMariaDbSync{ + DbHostname: "${DB_HOST:-mariadb}", + DbUsername: "${DB_USERNAME:-drupal}", + DbPassword: "${DB_PASSWORD:-drupal}", + DbPort: "${DB_PORT:-3306}", + DbDatabase: "${DB_DATABASE:-drupal}", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateMariadbSyncRootFromService(tt.args.definition) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateMariadbSyncRootFromService() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GenerateMariadbSyncRootFromService() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGenerateFilesSyncRootFromPersistentService(t *testing.T) { + type args struct { + definition LagoonServiceDefinition + } + tests := []struct { + name string + args args + want synchers.FilesSyncRoot + wantErr bool + }{ + { + name: "Standard service", + args: args{definition: LagoonServiceDefinition{ + ServiceName: "cli", + ServiceType: "cli-persistent", + Labels: map[string]string{ + "lagoon.persistent": "/app/web/sites/default/files", + }, + }}, + want: synchers.FilesSyncRoot{ + ServiceName: "cli", + Type: synchers.FilesSyncPlugin{}.GetPluginId(), + Config: synchers.BaseFilesSync{ + SyncPath: "/app/web/sites/default/files", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateFilesSyncRootFromPersistentService(tt.args.definition) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateFilesSyncRootFromPersistentService() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GenerateFilesSyncRootFromPersistentService() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBuildConfigStanzaFromServices(t *testing.T) { + type args struct { + services []LagoonServiceDefinition + } + tests := []struct { + name string + args args + shouldContain []string + wantErr bool + }{ + { + name: "Single instance", + shouldContain: []string{ + "db", + "files1-testmount", + "files1-nogamount", + }, + args: args{services: []LagoonServiceDefinition{ + { + ServiceName: "db", + ServiceType: "mariadb", + Labels: nil, + }, + { + ServiceName: "files1", + ServiceType: "cli-persistent", + Labels: map[string]string{ + "lagoon.persistent": "/mainstorage", + "lagoon.volumes.testmount.path": "/testmount/", + "lagoon.volumes.nogamount.path": "/testmount2/", + }, + }, + }, + }, + }, + { + name: "Postgres instance", + shouldContain: []string{ + synchers.PostgresSyncPlugin{}.GetPluginId(), + }, + args: args{services: []LagoonServiceDefinition{ + { + ServiceName: "postgresdb", + ServiceType: "postgres", + Labels: nil, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := BuildConfigStanzaFromServices(tt.args.services) + if (err != nil) != tt.wantErr { + t.Errorf("BuildConfigStanzaFromServices() error = %v, wantErr %v", err, tt.wantErr) + return + } + + for _, v := range tt.shouldContain { + if !strings.Contains(got, v) { + t.Errorf("The output string should contain the substring '%v'", v) + } + } + }) + } +} + +func TestGenerateFilesSyncRootsFromServiceDefinition(t *testing.T) { + type args struct { + definition LagoonServiceDefinition + } + tests := []struct { + name string + args args + want []synchers.FilesSyncRoot + wantErr bool + }{ + { + name: "Empty - only standard definition", + args: args{definition: LagoonServiceDefinition{ + ServiceName: "cli", + ServiceType: "cli-persistent", + Labels: map[string]string{ + "lagoon.persistent": "/app/web/sites/default/files", + }, + }}, + want: []synchers.FilesSyncRoot{}, + }, + { + name: "Cli with single instance of multiple vol", + args: args{definition: LagoonServiceDefinition{ + ServiceName: "cli", + ServiceType: "cli-persistent", + Labels: map[string]string{ + "lagoon.persistent": "/app/web/sites/default/files", + "lagoon.volumes.vol1.path": "/apath/", + }, + }}, + want: []synchers.FilesSyncRoot{ + { + Type: "files", + ServiceName: "cli-vol1", + Config: synchers.BaseFilesSync{ + SyncPath: "/apath/", + }, + }, + }, + }, + { + name: "Cli with single multiple vols", + args: args{definition: LagoonServiceDefinition{ + ServiceName: "cli", + ServiceType: "cli-persistent", + Labels: map[string]string{ + "lagoon.persistent": "/app/web/sites/default/files", + "lagoon.volumes.vol1.path": "/apath/", + "lagoon.volumes.vol2.path": "/apath2/", + }, + }}, + want: []synchers.FilesSyncRoot{ + { + Type: "files", + ServiceName: "cli-vol1", + Config: synchers.BaseFilesSync{ + SyncPath: "/apath/", + }, + }, + { + Type: "files", + ServiceName: "cli-vol2", + Config: synchers.BaseFilesSync{ + SyncPath: "/apath2/", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateFilesSyncRootsFromServiceDefinition(tt.args.definition) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateFilesSyncRootsFromServiceDefinition() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if len(tt.want) != len(got) { + t.Errorf("GenerateFilesSyncRootsFromServiceDefinition() got = %v, want %v", got, tt.want) + } + + if len(tt.want) != 0 { + appears := false + for _, v := range tt.want { + for _, i := range got { + if reflect.DeepEqual(v, i) { + appears = true + } + } + } + if !appears { + t.Errorf("GenerateFilesSyncRootsFromServiceDefinition() got = %v, want %v", got, tt.want) + } + } + }) + } +} + +func Test_generateSyncStanza(t *testing.T) { + type args struct { + templateData configTemplateData + } + tests := []struct { + name string + args args + contains []string + wantErr bool + }{ + { + name: "Checking basic templating works", + args: args{templateData: configTemplateData{ + Filesystem: []synchers.FilesSyncRoot{ + { + Config: synchers.BaseFilesSync{ + SyncPath: "/path/to/files", + }, + }, + }, + }, + }, + contains: []string{"path/to/files"}, + wantErr: false, + }, + { + name: "Api and ssh templating", + args: args{templateData: configTemplateData{ + Filesystem: []synchers.FilesSyncRoot{ + { + Config: synchers.BaseFilesSync{ + SyncPath: "/path/to/files", + }, + }, + }, + Ssh: "sshtoappearhere", + Api: "apitoappearhere", + }, + }, + contains: []string{"sshtoappearhere", "apitoappearhere"}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := generateSyncStanza(tt.args.templateData) + if (err != nil) != tt.wantErr { + t.Errorf("generateSyncStanza() error = %v, wantErr %v", err, tt.wantErr) + return + } + + for _, contString := range tt.contains { + if !strings.Contains(got, contString) { + t.Errorf("generateSyncStanza() should, but doesn't, contain '%v'", tt.contains) + } + } + }) + } +} diff --git a/generator/test-assets/docker-compose-broken.yml b/generator/test-assets/docker-compose-broken.yml new file mode 100644 index 0000000..b6018b8 --- /dev/null +++ b/generator/test-assets/docker-compose-broken.yml @@ -0,0 +1,100 @@ +version: '2.3' + +x-volumes: + &default-volumes + # Define all volumes you would like to have real-time mounted into the docker containers + volumes: + - .:/app:delegated ### Local overrides to mount host filesystem. Automatically removed in CI and PROD. + - files:/app/web/sites/default/files + +x-environment: + &default-environment + # Route that should be used locally, if you are using pygmy, this route *must* end with .docker.amazee.io + LAGOON_ROUTE: &default-url http://${COMPOSE_PROJECT_NAME:-drupal9-example-simple}.docker.amazee.io + # Uncomment if you like to have the system behave like in production + #LAGOON_ENVIRONMENT_TYPE: production + # Uncomment to enable xdebug and then restart via `docker-compose up -d` + #XDEBUG_ENABLE: "true" + +x-user: + &default-user + # The default user under which the containers should run. Change this if you are on linux and run with another user than id `1000` + user: '1000' + +volumes: + files: + {} + +services: + + cli: # cli container, will be used for executing composer and any local commands (drush, drupal, etc.) + build: + context: . + dockerfile: lagoon/cli.dockerfile + image: &cli-image ${COMPOSE_PROJECT_NAME:-drupal9-example-simple}-cli # this image will be reused as `CLI_IMAGE` in subsequent Docker builds + labels: + # Lagoon Labels + lagoon.type: cli-persistent + lagoon.persistent.name: nginx # mount the persistent storage of nginx into this container + lagoon.persistent: /app/web/sites/default/files/ # location where the persistent storage should be mounted + lando.type: php-cli-drupal + << : *default-volumes # loads the defined volumes from the top + user: root + volumes_from: ### mount the ssh-agent from the pygmy or cachalot ssh-agent. Automatically removed in CI. + - container:amazeeio-ssh-agent ### Local overrides to mount host SSH keys. Automatically removed in CI. + environment: + << : *default-environment # loads the defined environment variables from the top + + nginx: + build: + context: . + dockerfile: lagoon/nginx.dockerfile + args: + CLI_IMAGE: *cli-image # Inject the name of the cli image + labels: + lagoon.type: nginx-php-persistent + lagoon.persistent: /app/web/sites/default/files/ # define where the persistent storage should be mounted too + lando.type: nginx-drupal + << : *default-volumes # loads the defined volumes from the top + << : *default-user # uses the defined user from top + depends_on: + - cli # basically just tells docker-compose to build the cli first + environment: + << : *default-environment # loads the defined environment variables from the top + LAGOON_LOCALDEV_URL: *default-url + networks: + - amazeeio-network + - default + + php: + build: + context: . + dockerfile: lagoon/php.dockerfile + args: + CLI_IMAGE: *cli-image + labels: + lagoon.type: nginx-php-persistent + lagoon.name: nginx # we want this service be part of the nginx pod in Lagoon + lagoon.persistent: /app/web/sites/default/files/ # define where the persistent storage should be mounted too + lando.type: php-fpm + << : *default-volumes # loads the defined volumes from the top + << : *default-user # uses the defined user from top + depends_on: + - cli # basically just tells docker-compose to build the cli first + environment: + << : *default-environment # loads the defined environment variables from the top + + mariadb: + image: uselagoon/mariadb-10.5-drupal:latest + labels: + lagoon.type: mariadb + lando.type: mariadb-drupal + ports: + - "3306" # exposes the port 3306 with a random local port, find it with `docker-compose port mariadb 3306` + << : *default-user # uses the defined user from top + environment: + << : *default-environment + +networks: + amazeeio-network: + external: true diff --git a/generator/test-assets/drupal-docker-compose.yml b/generator/test-assets/drupal-docker-compose.yml new file mode 100644 index 0000000..901a7b8 --- /dev/null +++ b/generator/test-assets/drupal-docker-compose.yml @@ -0,0 +1,107 @@ +x-volumes: + &default-volumes + # Define all volumes you would like to have real-time mounted into the docker containers + volumes: + - .:/app:delegated ### Local overrides to mount host filesystem. Automatically removed in CI and PROD. + - files:/app/web/sites/default/files + +x-environment: + &default-environment + # Route that should be used locally, if you are using pygmy, this route *must* end with .docker.amazee.io + LAGOON_ROUTE: &default-url http://${COMPOSE_PROJECT_NAME:-drupal-base}.docker.amazee.io + # Uncomment if you like to have the system behave like in production + #LAGOON_ENVIRONMENT_TYPE: production + # Uncomment to enable xdebug and then restart via `docker-compose up -d` + #XDEBUG_ENABLE: "true" + +x-user: + &default-user + # The default user under which the containers should run. Change this if you are on linux and run with another user than id `1000` + user: '1000' + +volumes: + files: + {} + db: + {} + +services: + + cli: # cli container, will be used for executing composer and any local commands (drush, drupal, etc.) + hostname: cli + build: + context: . + target: cli + dockerfile: lagoon/Dockerfile + image: drupal-base-cli:latest + labels: + # Lagoon Labels + lagoon.type: cli-persistent + lagoon.persistent.name: nginx # mount the persistent storage of nginx into this container + lagoon.persistent: /app/web/sites/default/files/ # location where the persistent storage should be mounted + lando.type: php-cli-drupal + << : *default-volumes # loads the defined volumes from the top + user: root + volumes_from: ### mount the ssh-agent from the pygmy or cachalot ssh-agent. Automatically removed in CI. + - container:amazeeio-ssh-agent ### Local overrides to mount host SSH keys. Automatically removed in CI. + environment: + << : *default-environment # loads the defined environment variables from the top + + nginx: + hostname: nginx + build: + context: . + target: nginx + dockerfile: lagoon/Dockerfile + image: drupal-base-nginx:latest + labels: + lagoon.type: nginx-php-persistent + lagoon.persistent: /app/web/sites/default/files/ # define where the persistent storage should be mounted too + lando.type: nginx-drupal + ports: + - "8080" # exposes the port 8080 with a random local port, find it with `docker-compose port nginx 8080` + << : [*default-volumes, *default-user] + depends_on: + - cli # basically just tells docker-compose to build the cli first + environment: + << : *default-environment # loads the defined environment variables from the top + LAGOON_LOCALDEV_URL: *default-url + networks: + - amazeeio-network + - default + + php: + hostname: php + build: + context: . + target: php + dockerfile: lagoon/Dockerfile + image: drupal-base-php:latest + labels: + lagoon.type: nginx-php-persistent + lagoon.name: nginx # we want this service be part of the nginx pod in Lagoon + lagoon.persistent: /app/web/sites/default/files/ # define where the persistent storage should be mounted too + lando.type: php-fpm + << : [*default-volumes, *default-user] + depends_on: + - cli # basically just tells docker-compose to build the cli first + environment: + << : *default-environment # loads the defined environment variables from the top + + mariadb: + image: uselagoon/mariadb-10.11-drupal:latest + labels: + lagoon.type: mariadb + lando.type: mariadb-drupal + ports: + - "3306" # exposes the port 3306 with a random local port, find it with `docker-compose port mariadb 3306` + << : *default-user # uses the defined user from top + environment: + << : *default-environment + volumes: + - db:/var/lib/mysql + +networks: + amazeeio-network: + external: true + default: \ No newline at end of file diff --git a/generator/wizard.go b/generator/wizard.go new file mode 100644 index 0000000..4c0327a --- /dev/null +++ b/generator/wizard.go @@ -0,0 +1,226 @@ +package generator + +import ( + "fmt" + "github.com/AlecAivazis/survey/v2" + "github.com/uselagoon/lagoon-sync/synchers" +) + +func displayConfigTemplateData(d configTemplateData) { + + fmt.Println("\n\n--- Current Service List ---") + if len(d.Mariadb) > 0 { + fmt.Println("\tMariadb services:") + for _, s := range d.Mariadb { + fmt.Println("\t\t" + s.ServiceName) + } + } + if len(d.Postgres) > 0 { + fmt.Println("\tPostgres services:") + for _, s := range d.Postgres { + fmt.Println("\t\t" + s.ServiceName) + } + } + if len(d.Filesystem) > 0 { + fmt.Println("\tFilesystems to sync:") + for _, s := range d.Filesystem { + fmt.Println("\t\t" + s.ServiceName + ":" + s.Config.SyncPath) + } + } + if d.Api != "" || d.Ssh != "" { + fmt.Println("\t Cluster details") + } + if d.Api != "" { + fmt.Println("\t\tApi:%v", d.Api) + } + if d.Ssh != "" { + fmt.Println("\t\tSsh:%v", d.Ssh) + } +} + +func RunWizard() (string, error) { + + template := configTemplateData{} + + done := false + const setClusterDetailsString = "Set cluster details" + const addSshDetails = "Add cluster details (if running dedicated cluster)" + const addMariadbString = "Add Mariadb" + const addPostgressString = "Add Postgres" + const addFSString = "Add filesystem" + const editService = "Edit service" + const exitString = "Done" + + for !done { + displayConfigTemplateData(template) + prompt := &survey.Select{ + Message: "Select:", + Options: []string{setClusterDetailsString, addMariadbString, addPostgressString, addFSString, exitString}, + } + var opt string + + survey.AskOne(prompt, &opt, survey.WithValidator(survey.Required)) + + switch opt { + case setClusterDetailsString: + addClusterDetails(&template) + case addMariadbString: + addMariadbService(&template) + case addPostgressString: + addPostgresqlService(&template) + case addFSString: + addFilesystemSyncer(&template) + case exitString: + done = true + + } + + if opt == exitString { + done = true + } + } + + return generateSyncStanza(template) +} + +func addClusterDetails(c *configTemplateData) { + + fmt.Println("Adding cluster details:") + fmt.Println("Note: you're able to get these details from your administrator or the lagoon cli (run 'lagoon config list')") + + var qs = []*survey.Question{ + { + Name: "SshEndpoint", + Prompt: &survey.Input{ + Renderer: survey.Renderer{}, + Message: "Enter the SSH Hostname for your lagoon instance", + Default: "ssh.lagoon.amazeeio.cloud", + Help: "", + Suggest: nil, + }, + Validate: survey.Required, + }, + { + Name: "SshPort", + Prompt: &survey.Input{ + Renderer: survey.Renderer{}, + Message: "Enter the SSH port for your lagoon instance", + Default: "32222", + Help: "", + Suggest: nil, + }, + Validate: survey.Required, + }, + { + Name: "GraphqlEndpoint", + Prompt: &survey.Input{ + Renderer: survey.Renderer{}, + Message: "Enter the graphql endpoint for your lagoon instance", + Default: "https://api.lagoon.amazeeio.cloud/graphql", + Help: "", + Suggest: nil, + }, + }, + } + answers := struct { + SshEndpoint string + SshPort string + GraphqlEndpoint string + }{} + err := survey.Ask(qs, &answers) + if err != nil { + fmt.Println(err) + return + } + c.Ssh = fmt.Sprintf("%v:%v", answers.SshEndpoint, answers.SshPort) + c.Api = answers.GraphqlEndpoint +} + +func addMariadbService(c *configTemplateData) { + fmt.Println("\nAdding a mariadb service:") + var qs = []*survey.Question{ + { + Name: "Servicename", + Prompt: &survey.Input{Message: "What is the name of the service (typically the service name in your docker file)?"}, + Validate: survey.Required, + Transform: survey.ToLower, + }, + } + answers := struct { + Servicename string + }{} + err := survey.Ask(qs, &answers) + if err != nil { + fmt.Println(err) + return + } + + service, err := GenerateMariadbSyncRootFromService(LagoonServiceDefinition{ + ServiceName: answers.Servicename, + ServiceType: synchers.MariadbSyncPlugin{}.GetPluginId(), + }) + c.Mariadb = append(c.Mariadb, service) + +} + +func addPostgresqlService(c *configTemplateData) { + fmt.Println("\nAdding a postgresql service:") + var qs = []*survey.Question{ + { + Name: "Servicename", + Prompt: &survey.Input{Message: "What is the name of the service (typically the service name in your docker file)?"}, + Validate: survey.Required, + Transform: survey.ToLower, + }, + } + answers := struct { + Servicename string + }{} + err := survey.Ask(qs, &answers) + if err != nil { + fmt.Println(err) + return + } + + service, err := GeneratePgqlSyncRootFromService(LagoonServiceDefinition{ + ServiceName: answers.Servicename, + ServiceType: synchers.PostgresSyncPlugin{}.GetPluginId(), + }) + c.Postgres = append(c.Postgres, service) +} + +func addFilesystemSyncer(c *configTemplateData) { + fmt.Println("\nAdding a File sync:") + var qs = []*survey.Question{ + { + Name: "Servicename", + Prompt: &survey.Input{Message: "What is the name of the sync you'd like to setup (a useful name to refer to the sync, such as 'publicfiles')?"}, + Validate: survey.Required, + Transform: survey.ToLower, + }, + { + Name: "Path", + Prompt: &survey.Input{Message: "What is the path you'd like to sync (eg. '/app/web/sites/default/files')?"}, + Validate: survey.Required, + Transform: survey.ToLower, + }, + } + answers := struct { + Servicename string + Path string + }{} + err := survey.Ask(qs, &answers) + if err != nil { + fmt.Println(err) + return + } + + service, err := GenerateFilesSyncRootsFromServiceDefinition(LagoonServiceDefinition{ + ServiceName: answers.Servicename, + ServiceType: synchers.FilesSyncPlugin{}.GetPluginId(), + Labels: map[string]string{ + "lagoon.volumes." + answers.Servicename + ".path": answers.Path, + }, + }) + c.Filesystem = append(c.Filesystem, service...) +} diff --git a/go.mod b/go.mod index 99443c5..bdf383b 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,12 @@ module github.com/uselagoon/lagoon-sync +toolchain go1.23.3 + go 1.23 require ( + github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/compose-spec/compose-go v1.2.7 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf github.com/manifoldco/promptui v0.9.0 github.com/mitchellh/go-homedir v1.1.0 @@ -18,30 +22,48 @@ require ( require ( github.com/chzyer/readline v1.5.1 // indirect + github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/guregu/null v4.0.0+incompatible // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/machinebox/graphql v0.2.2 // indirect github.com/magiconair/properties v1.8.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/compose-spec/compose-go v1.2.7 => github.com/shreddedbacon/compose-go v0.0.0-20220616064547-4e908a2865c1 diff --git a/go.sum b/go.sum index bb66951..a952d26 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,23 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -8,35 +28,83 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU= +github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo= @@ -45,18 +113,54 @@ github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -64,6 +168,13 @@ github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3 github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shreddedbacon/compose-go v0.0.0-20220616064547-4e908a2865c1 h1:/OYATO27ywKZ506enKV8Fd8gjLYFU9uQqCMmcVOUpW0= +github.com/shreddedbacon/compose-go v0.0.0-20220616064547-4e908a2865c1/go.mod h1:Jl9L8zJrt4aGY1XAz03DvHAu8V3/f00TK+uJL4BayDU= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= @@ -74,12 +185,21 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -88,28 +208,94 @@ github.com/uselagoon/machinery v0.0.31 h1:SkJ+muPBb9Q5vNI0bgXxZai6jN103iSj3e3d3D github.com/uselagoon/machinery v0.0.31/go.mod h1:RsHzIMOam3hiA4CKR12yANgzdTGy6tz4D19umjMzZyw= github.com/withmandala/go-log v0.1.0 h1:wINmTEe7BQ6zEA8sE7lSsYeaxCLluK6RFjF/IB5tzkA= github.com/withmandala/go-log v0.1.0/go.mod h1:/V9xQUTW74VjYm3u2Liv/bIUGLWoL9z2GlHwtscp4vg= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= diff --git a/synchers/custom.go b/synchers/custom.go index a8a151e..e7fb9a6 100644 --- a/synchers/custom.go +++ b/synchers/custom.go @@ -20,6 +20,7 @@ func (customConfig *BaseCustomSync) setDefaults() { } type CustomSyncRoot struct { + Type string `yaml:"type" json:"type"` TransferResource string `yaml:"transfer-resource"` Source BaseCustomSyncCommands `yaml:"source"` Target BaseCustomSyncCommands `yaml:"target"` @@ -54,7 +55,7 @@ func GetCustomSync(configRoot SyncherConfigRoot, syncerName string) (Syncer, err CustomRoot: syncerName, } - ret, err := m.UnmarshallYaml(configRoot) + ret, err := m.UnmarshallYaml(configRoot, syncerName) if err != nil { return CustomSyncRoot{}, err } @@ -62,9 +63,9 @@ func GetCustomSync(configRoot SyncherConfigRoot, syncerName string) (Syncer, err return ret, nil } -func (m CustomSyncPlugin) UnmarshallYaml(root SyncherConfigRoot) (Syncer, error) { +func (m CustomSyncPlugin) UnmarshallYaml(root SyncherConfigRoot, targetService string) (Syncer, error) { custom := CustomSyncRoot{} - + custom.Type = m.GetPluginId() // Use 'environment-defaults' if present envVars := root.Prerequisites var configMap interface{} diff --git a/synchers/custom_test.go b/synchers/custom_test.go index 6f824d2..ee664a5 100644 --- a/synchers/custom_test.go +++ b/synchers/custom_test.go @@ -47,7 +47,7 @@ func TestCustomSyncPlugin_UnmarshallYaml(t *testing.T) { m := CustomSyncPlugin{ isConfigEmpty: tt.fields.isConfigEmpty, } - got, err := m.UnmarshallYaml(tt.args.root) + got, err := m.UnmarshallYaml(tt.args.root, "custom") if (err != nil) != tt.wantErr { t.Errorf("UnmarshallYaml() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/synchers/drupalconfig.go b/synchers/drupalconfig.go index cded40c..86af819 100644 --- a/synchers/drupalconfig.go +++ b/synchers/drupalconfig.go @@ -32,11 +32,11 @@ func (m DrupalConfigSyncPlugin) GetPluginId() string { return "drupalconfig" } -func (m DrupalConfigSyncPlugin) UnmarshallYaml(syncerConfigRoot SyncherConfigRoot) (Syncer, error) { +func (m DrupalConfigSyncPlugin) UnmarshallYaml(syncerConfigRoot SyncherConfigRoot, targetService string) (Syncer, error) { drupalconfig := DrupalconfigSyncRoot{} drupalconfig.Config.OutputDirectory = drupalconfig.GetOutputDirectory() - configMap := syncerConfigRoot.LagoonSync[m.GetPluginId()] + configMap := syncerConfigRoot.LagoonSync[targetService] _ = UnmarshalIntoStruct(configMap, &drupalconfig) // If yaml config is there then unmarshall into struct and override default values if there are any diff --git a/synchers/files.go b/synchers/files.go index f36aaac..3d03899 100644 --- a/synchers/files.go +++ b/synchers/files.go @@ -23,6 +23,8 @@ func (filesConfig *BaseFilesSync) setDefaults() { } type FilesSyncRoot struct { + Type string `yaml:"type" json:"type"` + ServiceName string `yaml:"serviceName"` Config BaseFilesSync LocalOverrides FilesSyncLocal `yaml:"local"` TransferId string @@ -46,12 +48,13 @@ func (m FilesSyncPlugin) GetPluginId() string { return "files" } -func (m FilesSyncPlugin) UnmarshallYaml(root SyncherConfigRoot) (Syncer, error) { +func (m FilesSyncPlugin) UnmarshallYaml(root SyncherConfigRoot, targetService string) (Syncer, error) { filesroot := FilesSyncRoot{} + filesroot.Type = m.GetPluginId() filesroot.Config.setDefaults() // Use 'lagoon-sync' yaml as override if env vars are not available - configMap := root.LagoonSync[m.GetPluginId()] + configMap := root.LagoonSync[targetService] // If yaml config is there then unmarshall into struct and override default values if there are any if len(root.LagoonSync) != 0 { diff --git a/synchers/mariadb.go b/synchers/mariadb.go index 107ea72..b5dd1ff 100644 --- a/synchers/mariadb.go +++ b/synchers/mariadb.go @@ -24,18 +24,27 @@ type BaseMariaDbSync struct { OutputDirectory string } +const mariadbDefaultServiceName = "mariadb" + type MariadbSyncLocal struct { Config BaseMariaDbSync } type MariadbSyncRoot struct { + Type string `yaml:"type" json:"type"` + ServiceName string `yaml:"serviceName"` Config BaseMariaDbSync LocalOverrides MariadbSyncLocal `yaml:"local"` TransferId string TransferResourceOverride string } -func (mariadbConfig *BaseMariaDbSync) setDefaults() { +func (m *MariadbSyncRoot) setDefaults() { + m.Type = MariadbSyncPlugin{}.GetPluginId() + m.ServiceName = mariadbDefaultServiceName +} + +func (mariadbConfig *BaseMariaDbSync) SetDefaults() { if mariadbConfig.DbHostname == "" { mariadbConfig.DbHostname = "${MARIADB_HOST:-mariadb}" } @@ -67,12 +76,13 @@ func (m MariadbSyncPlugin) GetPluginId() string { return "mariadb" } -func (m MariadbSyncPlugin) UnmarshallYaml(root SyncherConfigRoot) (Syncer, error) { +func (m MariadbSyncPlugin) UnmarshallYaml(root SyncherConfigRoot, targetService string) (Syncer, error) { mariadb := MariadbSyncRoot{} - mariadb.Config.setDefaults() - mariadb.LocalOverrides.Config.setDefaults() + mariadb.setDefaults() + mariadb.Config.SetDefaults() + mariadb.LocalOverrides.Config.SetDefaults() - syncherConfig := root.LagoonSync[m.GetPluginId()] + syncherConfig := root.LagoonSync[targetService] // If yaml config is there then unmarshall into struct and override default values if there are any if syncherConfig != nil { diff --git a/synchers/mongodb.go b/synchers/mongodb.go index 026f3e1..73a810d 100644 --- a/synchers/mongodb.go +++ b/synchers/mongodb.go @@ -35,6 +35,7 @@ type MongoDbSyncLocal struct { } type MongoDbSyncRoot struct { + Type string `yaml:"type" json:"type"` Config BaseMongoDbSync LocalOverrides MongoDbSyncLocal `yaml:"local"` TransferId string @@ -55,8 +56,9 @@ func (m MongoDbSyncPlugin) GetPluginId() string { return "mongodb" } -func (m MongoDbSyncPlugin) UnmarshallYaml(root SyncherConfigRoot) (Syncer, error) { +func (m MongoDbSyncPlugin) UnmarshallYaml(root SyncherConfigRoot, targetService string) (Syncer, error) { mongodb := MongoDbSyncRoot{} + mongodb.Type = m.GetPluginId() mongodb.Config.setDefaults() mongodb.LocalOverrides.Config.setDefaults() @@ -66,7 +68,7 @@ func (m MongoDbSyncPlugin) UnmarshallYaml(root SyncherConfigRoot) (Syncer, error if envVars == nil { // Use 'lagoon-sync' yaml as override if env vars are not available - configMap = root.LagoonSync[m.GetPluginId()] + configMap = root.LagoonSync[targetService] } // If config from active config file is empty, then use defaults diff --git a/synchers/postgres.go b/synchers/postgres.go index 5faaef2..0725068 100644 --- a/synchers/postgres.go +++ b/synchers/postgres.go @@ -21,6 +21,8 @@ type BasePostgresSync struct { OutputDirectory string } type PostgresSyncRoot struct { + Type string `yaml:"type" json:"type"` + ServiceName string `yaml:"serviceName"` Config BasePostgresSync LocalOverrides PostgresSyncLocal `yaml:"local"` TransferId string @@ -31,6 +33,11 @@ type PostgresSyncLocal struct { Config BasePostgresSync } +// SetDefaults is a public function that is used to set all defaults for this struct +func (postgresConfig *BasePostgresSync) SetDefaults() { + postgresConfig.setDefaults() +} + func (postgresConfig *BasePostgresSync) setDefaults() { if postgresConfig.DbHostname == "" { postgresConfig.DbHostname = "${POSTGRES_HOST:-postgres}" @@ -63,11 +70,12 @@ func (m PostgresSyncPlugin) GetPluginId() string { return "postgres" } -func (m PostgresSyncPlugin) UnmarshallYaml(syncerConfigRoot SyncherConfigRoot) (Syncer, error) { +func (m PostgresSyncPlugin) UnmarshallYaml(syncerConfigRoot SyncherConfigRoot, targetService string) (Syncer, error) { postgres := PostgresSyncRoot{} + postgres.Type = m.GetPluginId() postgres.Config.setDefaults() - configMap := syncerConfigRoot.LagoonSync[m.GetPluginId()] + configMap := syncerConfigRoot.LagoonSync[targetService] // If yaml config is there then unmarshall into struct and override default values if there are any if configMap != nil { diff --git a/synchers/syncerPluginSystem.go b/synchers/syncerPluginSystem.go index 700b582..c31d989 100644 --- a/synchers/syncerPluginSystem.go +++ b/synchers/syncerPluginSystem.go @@ -18,7 +18,7 @@ var syncerMap = map[string]SyncerPlugin{} type SyncerPlugin interface { GetPluginId() string - UnmarshallYaml(root SyncherConfigRoot) (Syncer, error) + UnmarshallYaml(root SyncherConfigRoot, targetService string) (Syncer, error) } func RegisterSyncer(plugin SyncerPlugin) { @@ -26,8 +26,25 @@ func RegisterSyncer(plugin SyncerPlugin) { } func GetSyncerForTypeFromConfigRoot(syncerId string, root SyncherConfigRoot) (Syncer, error) { + + // we may want to first check if there's an explicit type attached to this syncerId + SyncerConfig, exists := root.LagoonSync[syncerId] + if exists { + configTypeStruct := struct { + Type string `yaml:"type" json:"type"` + }{Type: ""} + _ = UnmarshalIntoStruct(SyncerConfig, &configTypeStruct) + + // We've found an alias in the config that implements a "type" + if configTypeStruct.Type != "" { + return syncerMap[configTypeStruct.Type].UnmarshallYaml(root, syncerId) + } + } + if syncerMap[syncerId] == nil { return nil, errors.New(fmt.Sprintf("Syncer of type '%s' not registered", syncerId)) } - return syncerMap[syncerId].UnmarshallYaml(root) + + return syncerMap[syncerId].UnmarshallYaml(root, syncerId) + } diff --git a/synchers/syncerPluginSystem_test.go b/synchers/syncerPluginSystem_test.go new file mode 100644 index 0000000..29642ad --- /dev/null +++ b/synchers/syncerPluginSystem_test.go @@ -0,0 +1,60 @@ +package synchers + +import ( + "reflect" + "testing" +) + +func TestGetSyncerForTypeFromConfigRoot(t *testing.T) { + type args struct { + syncerId string + root SyncherConfigRoot + } + type syncerDef struct { + Type string + } + tests := []struct { + name string + args args + wantSyncerType reflect.Type + wantErr bool + }{ + { + name: "Basic loading of mariadb", + args: args{ + syncerId: "mariadb", + root: SyncherConfigRoot{}, + }, + wantSyncerType: reflect.TypeOf(MariadbSyncPlugin{}), + wantErr: false, + }, + { + name: "Basic loading of aliased filesystem", + args: args{ + syncerId: "logs", + root: SyncherConfigRoot{ + Api: "", + Project: "", + LagoonSync: map[string]interface{}{ + "logs": syncerDef{Type: FilesSyncPlugin{}.GetPluginId()}, + }, + Prerequisites: nil, + }, + }, + wantSyncerType: reflect.TypeOf(FilesSyncPlugin{}), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetSyncerForTypeFromConfigRoot(tt.args.syncerId, tt.args.root) + if (err != nil) != tt.wantErr { + t.Errorf("GetSyncerForTypeFromConfigRoot() error = %v, wantErr %v", err, tt.wantErr) + return + } + if reflect.TypeOf(got) == tt.wantSyncerType { + t.Errorf("GetSyncerForTypeFromConfigRoot() got = %v, want %v", got, tt.wantSyncerType) + } + }) + } +}