From 51bc0e06460bec0c159de43bcf746b5d3b0aa66b Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 23 Sep 2016 11:45:41 +0200 Subject: [PATCH] Add test suite (closes #63) --- .gitignore | 2 + .travis.yml | 8 ++- README.md | 5 +- integration/setup_aws.sh | 37 +++++++++++ integration/setup_local.sh | 1 + integration/setup_travis.sh | 1 + sql_runner/aws_utils_test.go | 68 ++++++++++++++++++++ sql_runner/consul_utils.go | 22 ++----- sql_runner/consul_utils_test.go | 47 ++++++++++++++ sql_runner/file_utils_test.go | 32 ++++++++++ sql_runner/lock_file_test.go | 89 +++++++++++++++++++++++--- sql_runner/main_test.go | 110 ++++++++++++++++++++++++++++++++ sql_runner/yaml_utils_test.go | 53 +++++++++++++++ 13 files changed, 447 insertions(+), 28 deletions(-) create mode 100755 integration/setup_aws.sh create mode 100644 sql_runner/aws_utils_test.go create mode 100644 sql_runner/consul_utils_test.go create mode 100644 sql_runner/file_utils_test.go create mode 100644 sql_runner/main_test.go create mode 100644 sql_runner/yaml_utils_test.go diff --git a/.gitignore b/.gitignore index dc1c461..2bab5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ _testmain.go # This project's binaries sql-runner *.tmp +coverage.out +coverage.html # Vagrant .vagrant/ diff --git a/.travis.yml b/.travis.yml index a08369f..3c41363 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,19 @@ go: - 1.7 - tip +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover + before_script: - ./integration/setup_travis.sh script: - - go test -i ./... - - go test ./... - test -z "$(go fmt ./...)" - go build -o sql-runner ./sql_runner/ - ./integration/run_tests.sh + - go test ./sql_runner/ -v -covermode=count -coverprofile=coverage.out + - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci before_deploy: - go get github.com/mitchellh/gox diff --git a/README.md b/README.md index d5b3fdf..4197f16 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQL Runner -[ ![Build Status] [travis-image] ] [travis] [ ![Release] [release-image] ] [releases] [ ![License] [license-image] ] [license] +[![Build Status] [travis-image]][travis] [![Coveralls][coveralls-image]][coveralls] [![Release][release-image]][releases] [![License][license-image]][license] ## Overview @@ -44,6 +44,9 @@ limitations under the License. [license-image]: http://img.shields.io/badge/license-Apache--2-blue.svg?style=flat [license]: http://www.apache.org/licenses/LICENSE-2.0 +[coveralls-image]: https://coveralls.io/repos/github/snowplow/sql-runner/badge.svg?branch=master +[coveralls]: https://coveralls.io/github/snowplow/sql-runner?branch=master + [snowplow]: https://github.com/snowplow/snowplow [analysts-guide]: https://github.com/snowplow/sql-runner/wiki/Guide-for-analysts diff --git a/integration/setup_aws.sh b/integration/setup_aws.sh new file mode 100755 index 0000000..d63bc95 --- /dev/null +++ b/integration/setup_aws.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Copyright (c) 2015-2016 Snowplow Analytics Ltd. All rights reserved. +# +# This program is licensed to you under the Apache License Version 2.0, +# and you may not use this file except in compliance with the Apache License Version 2.0. +# You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the Apache License Version 2.0 is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + +set -e + + + +# ----------------------------------------------------------------------------- +# CONSTANTS +# ----------------------------------------------------------------------------- + +home=${HOME} +aws_dir=${home}/.aws +creds_file=${aws_dir}/credentials + + + +# ----------------------------------------------------------------------------- +# EXECUTION +# ----------------------------------------------------------------------------- + +mkdir -p ${aws_dir} +touch ${creds_file} + +echo "[default]" >> ${creds_file} +echo "aws_access_key_id=some-aws-key" >> ${creds_file} +echo "aws_secret_access_key=some-aws-secret" >> ${creds_file} diff --git a/integration/setup_local.sh b/integration/setup_local.sh index 0ab9714..a0f8db0 100755 --- a/integration/setup_local.sh +++ b/integration/setup_local.sh @@ -40,5 +40,6 @@ psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'sql_runner_test psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'sql_runner_tests_2'" | grep -q 1 || psql -U postgres -c "CREATE DATABASE sql_runner_tests_2" ${root}/integration/setup_consul.sh +${root}/integration/setup_aws.sh printf "Ready for integration tests!\n" diff --git a/integration/setup_travis.sh b/integration/setup_travis.sh index 32c7c53..0b2cb52 100755 --- a/integration/setup_travis.sh +++ b/integration/setup_travis.sh @@ -35,5 +35,6 @@ psql -c 'create database sql_runner_tests_1' -U postgres psql -c 'create database sql_runner_tests_2' -U postgres ./integration/setup_consul.sh +./integration/setup_aws.sh printf "Ready for integration tests!\n" diff --git a/sql_runner/aws_utils_test.go b/sql_runner/aws_utils_test.go new file mode 100644 index 0000000..5cc0cf7 --- /dev/null +++ b/sql_runner/aws_utils_test.go @@ -0,0 +1,68 @@ +// +// Copyright (c) 2015-2016 Snowplow Analytics Ltd. All rights reserved. +// +// This program is licensed to you under the Apache License Version 2.0, +// and you may not use this file except in compliance with the Apache License Version 2.0. +// You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the Apache License Version 2.0 is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. +// +package main + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestAwsEnvCredentials(t *testing.T) { + assert := assert.New(t) + + str, err := awsEnvCredentials() + assert.NotNil(err) + assert.NotNil(str) + assert.Equal("EnvAccessKeyNotFound: AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment", err.Error()) + assert.Equal("CREDENTIALS 'aws_access_key_id=;aws_secret_access_key='", str) + + os.Setenv("AWS_ACCESS_KEY_ID", "some-aws-key") + os.Setenv("AWS_SECRET_ACCESS_KEY", "some-aws-secret") + + str, err = awsEnvCredentials() + assert.NotNil(str) + assert.Nil(err) + assert.Equal("CREDENTIALS 'aws_access_key_id=some-aws-key;aws_secret_access_key=some-aws-secret'", str) +} + +func TestAwsProfileCredentials(t *testing.T) { + assert := assert.New(t) + + str, err := awsProfileCredentials("fake-profile") + assert.NotNil(err) + assert.NotNil(str) + assert.Equal("CREDENTIALS 'aws_access_key_id=;aws_secret_access_key='", str) + + str, err = awsProfileCredentials("default") + assert.NotNil(str) + assert.Nil(err) + assert.Equal("CREDENTIALS 'aws_access_key_id=some-aws-key;aws_secret_access_key=some-aws-secret'", str) +} + +func TestAwsChainCredentials(t *testing.T) { + assert := assert.New(t) + + os.Setenv("AWS_ACCESS_KEY_ID", "some-aws-key") + os.Setenv("AWS_SECRET_ACCESS_KEY", "some-aws-secret") + + str, err := awsChainCredentials("fake-profile") + assert.NotNil(str) + assert.Nil(err) + assert.Equal("CREDENTIALS 'aws_access_key_id=some-aws-key;aws_secret_access_key=some-aws-secret'", str) + + str, err = awsChainCredentials("default") + assert.NotNil(str) + assert.Nil(err) + assert.Equal("CREDENTIALS 'aws_access_key_id=some-aws-key;aws_secret_access_key=some-aws-secret'", str) +} diff --git a/sql_runner/consul_utils.go b/sql_runner/consul_utils.go index bbb33f3..46c8ac4 100644 --- a/sql_runner/consul_utils.go +++ b/sql_runner/consul_utils.go @@ -29,11 +29,7 @@ func GetConsulClient(address string) (*api.Client, error) { // GetBytesFromConsul attempts to return the bytes // of a key stored in a Consul server func GetBytesFromConsul(address string, key string) ([]byte, error) { - client, err := GetConsulClient(address) - if err != nil { - return nil, err - } - + client, _ := GetConsulClient(address) kv := client.KV() // Get the KV Pair from consul @@ -64,16 +60,12 @@ func GetStringValueFromConsul(address string, key string) (string, error) { // PutBytesToConsul attempts to push a new // KV pair to a Consul Server func PutBytesToConsul(address string, key string, value []byte) error { - client, err := GetConsulClient(address) - if err != nil { - return err - } - + client, _ := GetConsulClient(address) kv := client.KV() // Put a new KV pair to consul p := &api.KVPair{Key: key, Value: value} - _, err = kv.Put(p, nil) + _, err := kv.Put(p, nil) return err } @@ -86,14 +78,10 @@ func PutStringValueToConsul(address string, key string, value string) error { // DeleteValueFromConsul attempts to delete a // KV pair from a Consul Server func DeleteValueFromConsul(address string, key string) error { - client, err := GetConsulClient(address) - if err != nil { - return err - } - + client, _ := GetConsulClient(address) kv := client.KV() // Delete the KV pair - _, err = kv.Delete(key, nil) + _, err := kv.Delete(key, nil) return err } diff --git a/sql_runner/consul_utils_test.go b/sql_runner/consul_utils_test.go new file mode 100644 index 0000000..7dcb99b --- /dev/null +++ b/sql_runner/consul_utils_test.go @@ -0,0 +1,47 @@ +// +// Copyright (c) 2015-2016 Snowplow Analytics Ltd. All rights reserved. +// +// This program is licensed to you under the Apache License Version 2.0, +// and you may not use this file except in compliance with the Apache License Version 2.0. +// You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the Apache License Version 2.0 is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. +// +package main + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPutGetDelStringValueFromConsul_Failure(t *testing.T) { + assert := assert.New(t) + + err := PutStringValueToConsul("localhost", "somekey", "somevalue") + assert.NotNil(err) + + str, err := GetStringValueFromConsul("localhost", "somekey") + assert.Equal("", str) + assert.NotNil(err) + + err = DeleteValueFromConsul("localhost", "somekey") + assert.NotNil(err) +} + +func TestPutGetDelStringValueFromConsul_Success(t *testing.T) { + assert := assert.New(t) + + err := PutStringValueToConsul("localhost:8500", "somekey", "somevalue") + assert.Nil(err) + + str, err := GetStringValueFromConsul("localhost:8500", "somekey") + assert.Nil(err) + assert.NotNil(str) + assert.Equal("somevalue", str) + + err = DeleteValueFromConsul("localhost:8500", "somekey") + assert.Nil(err) +} diff --git a/sql_runner/file_utils_test.go b/sql_runner/file_utils_test.go new file mode 100644 index 0000000..f14d457 --- /dev/null +++ b/sql_runner/file_utils_test.go @@ -0,0 +1,32 @@ +// +// Copyright (c) 2015-2016 Snowplow Analytics Ltd. All rights reserved. +// +// This program is licensed to you under the Apache License Version 2.0, +// and you may not use this file except in compliance with the Apache License Version 2.0. +// You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the Apache License Version 2.0 is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. +// +package main + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLoadLocalFile(t *testing.T) { + assert := assert.New(t) + + bytes, err := loadLocalFile("/this/path/does/not/exist") + assert.Nil(bytes) + assert.NotNil(err) + assert.Equal("open /this/path/does/not/exist: no such file or directory", err.Error()) + + bytes, err = loadLocalFile("../VERSION") + assert.NotNil(bytes) + assert.Nil(err) + assert.Equal(CLI_VERSION, string(bytes)) +} diff --git a/sql_runner/lock_file_test.go b/sql_runner/lock_file_test.go index b1746be..e31db4e 100644 --- a/sql_runner/lock_file_test.go +++ b/sql_runner/lock_file_test.go @@ -17,9 +17,9 @@ import ( "testing" ) -// TestInitLockFile tests setting up a lockfile +// TestInitLockFile_Local tests setting up a lockfile // on the local file system -func TestInitLockFileLocal(t *testing.T) { +func TestInitLockFile_Local(t *testing.T) { assert := assert.New(t) lockFile, err := InitLockFile("../dist/lock.lockfile", false, "") @@ -31,9 +31,9 @@ func TestInitLockFileLocal(t *testing.T) { assert.False(lockFile.LockExists()) } -// TestLockUnlockFileConsul asserts that we can +// TestLockUnlockFile_Local asserts that we can // lock and unlock using a local file server -func TestLockUnlockFileLocal(t *testing.T) { +func TestLockUnlockFile_Local(t *testing.T) { assert := assert.New(t) lockFile, err := InitLockFile("../dist/lock.lockfile", false, "") @@ -63,9 +63,38 @@ func TestLockUnlockFileLocal(t *testing.T) { assert.False(lockFile.LockExists()) } -// TestInitLockFile tests setting up a lockfile +// TestInitLockFile_LocalFailure tests setting up a lockfile +// on the local file system that does not exist +func TestInitLockFile_LocalFailure(t *testing.T) { + assert := assert.New(t) + + lockFile, err := InitLockFile("dist/lock.lockfile", false, "") + + assert.Nil(err) + assert.Equal("dist/lock.lockfile", lockFile.Path) + assert.False(lockFile.SoftLock) + assert.Equal("", lockFile.ConsulAddress) + assert.False(lockFile.LockExists()) +} + +// TestLockUnlockFile_LocalFailure asserts that we can +// lock and unlock using a local file server that does not exist +func TestLockUnlockFile_LocalFailure(t *testing.T) { + assert := assert.New(t) + + lockFile, err := InitLockFile("dist/lock.lockfile", false, "") + assert.Nil(err) + assert.False(lockFile.LockExists()) + + err = lockFile.Lock() + assert.NotNil(err) + assert.Equal("directory for key does not exist", err.Error()) + assert.False(lockFile.LockExists()) +} + +// TestInitLockFile_Consul tests setting up a lockfile // on a remote consul server -func TestInitLockFileConsul(t *testing.T) { +func TestInitLockFile_Consul(t *testing.T) { assert := assert.New(t) lockFile, err := InitLockFile("dist/lock.lockfile", false, "localhost:8500") @@ -77,9 +106,9 @@ func TestInitLockFileConsul(t *testing.T) { assert.False(lockFile.LockExists()) } -// TestLockUnlockFileConsul asserts that we can +// TestLockUnlockFile_Consul asserts that we can // lock and unlock using a consul server -func TestLockUnlockFileConsul(t *testing.T) { +func TestLockUnlockFile_Consul(t *testing.T) { assert := assert.New(t) lockFile, err := InitLockFile("dist/lock.lockfile", false, "localhost:8500") @@ -106,3 +135,47 @@ func TestLockUnlockFileConsul(t *testing.T) { err = lockFile.Unlock() assert.Nil(err) } + +// TestInitLockFile_ConsulFailure tests setting up a lockfile +// on a remote consul server that does not exist +func TestInitLockFile_ConsulFailure(t *testing.T) { + assert := assert.New(t) + + lockFile, err := InitLockFile("dist/lock.lockfile", false, "localhost") + + assert.Nil(err) + assert.Equal("dist/lock.lockfile", lockFile.Path) + assert.False(lockFile.SoftLock) + assert.Equal("localhost", lockFile.ConsulAddress) + assert.False(lockFile.LockExists()) +} + +// TestLockUnlockFile_ConsulFailure asserts that we can +// lock and unlock using a consul server that does not exist +func TestLockUnlockFile_ConsulFailure(t *testing.T) { + assert := assert.New(t) + + lockFile, err := InitLockFile("dist/lock.lockfile", false, "localhost") + assert.Nil(err) + assert.False(lockFile.LockExists()) + + err = lockFile.Lock() + assert.NotNil(err) + assert.False(lockFile.LockExists()) + + err = lockFile.Lock() + assert.NotNil(err) + assert.False(lockFile.LockExists()) + + _, err2 := InitLockFile("dist/lock.lockfile", false, "localhost") + assert.Nil(err2) + assert.False(lockFile.LockExists()) + + err = lockFile.Unlock() + assert.NotNil(err) + assert.False(lockFile.LockExists()) + + err = lockFile.Unlock() + assert.NotNil(err) + assert.False(lockFile.LockExists()) +} diff --git a/sql_runner/main_test.go b/sql_runner/main_test.go new file mode 100644 index 0000000..76ac61b --- /dev/null +++ b/sql_runner/main_test.go @@ -0,0 +1,110 @@ +// +// Copyright (c) 2015-2016 Snowplow Analytics Ltd. All rights reserved. +// +// This program is licensed to you under the Apache License Version 2.0, +// and you may not use this file except in compliance with the Apache License Version 2.0. +// You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the Apache License Version 2.0 is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. +// +package main + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestLockFileFromOptions(t *testing.T) { + assert := assert.New(t) + + options := Options{ + dryRun: true, + } + lockFile, err := LockFileFromOptions(options) + assert.Nil(lockFile) + assert.Nil(err) + + options = Options{ + dryRun: false, + } + lockFile, err = LockFileFromOptions(options) + assert.Nil(lockFile) + assert.Nil(err) + + options = Options{ + dryRun: false, + lock: "../dist/lock.lockfile", + } + lockFile, err = LockFileFromOptions(options) + assert.Nil(err) + assert.False(lockFile.SoftLock) + assert.Equal("../dist/lock.lockfile", lockFile.Path) + + options = Options{ + dryRun: false, + softLock: "../dist/lock.lockfile", + } + lockFile, err = LockFileFromOptions(options) + assert.Nil(err) + assert.True(lockFile.SoftLock) + assert.Equal("../dist/lock.lockfile", lockFile.Path) + + options = Options{ + dryRun: false, + checkLock: "../dist/lock.lockfile", + } + lockFile, err = LockFileFromOptions(options) + assert.Nil(err) + assert.False(lockFile.SoftLock) + assert.Equal("../dist/lock.lockfile", lockFile.Path) + + options = Options{ + dryRun: false, + deleteLock: "../dist/lock.lockfile", + } + lockFile, err = LockFileFromOptions(options) + assert.Nil(err) + assert.False(lockFile.SoftLock) + assert.Equal("../dist/lock.lockfile", lockFile.Path) +} + +func TestResolveSqlRoot(t *testing.T) { + assert := assert.New(t) + + str, err := resolveSqlRoot(SQLROOT_BINARY, "../integration/resources/good-postgres.yml", "") + assert.NotNil(str) + assert.Nil(err) + str, err = resolveSqlRoot(SQLROOT_BINARY, "../integration/resources/good-postgres.yml", "localhost:8500") + assert.NotNil(str) + assert.NotNil(err) + assert.Equal("", str) + assert.Equal("Cannot use BINARY option with -consul argument", err.Error()) + + str, err = resolveSqlRoot(SQLROOT_PLAYBOOK, "../integration/resources/good-postgres.yml", "") + assert.NotNil(str) + assert.Nil(err) + assert.True(strings.HasSuffix(str, "/integration/resources")) + str, err = resolveSqlRoot(SQLROOT_PLAYBOOK, "../integration/resources/good-postgres.yml", "localhost:8500") + assert.NotNil(str) + assert.Nil(err) + assert.True(strings.HasSuffix(str, "/integration/resources")) + + str, err = resolveSqlRoot(SQLROOT_PLAYBOOK_CHILD, "../integration/resources/good-postgres.yml", "") + assert.NotNil(str) + assert.NotNil(err) + assert.Equal("", str) + assert.Equal("Cannot use PLAYBOOK_CHILD option without -consul argument", err.Error()) + str, err = resolveSqlRoot(SQLROOT_PLAYBOOK_CHILD, "../integration/resources/good-postgres.yml", "localhost:8500") + assert.NotNil(str) + assert.Nil(err) + assert.Equal("../integration/resources/good-postgres.yml", str) + + str, err = resolveSqlRoot("random", "../integration/resources/good-postgres.yml", "localhost:8500") + assert.NotNil(str) + assert.Nil(err) + assert.Equal("random", str) +} diff --git a/sql_runner/yaml_utils_test.go b/sql_runner/yaml_utils_test.go new file mode 100644 index 0000000..b660c0a --- /dev/null +++ b/sql_runner/yaml_utils_test.go @@ -0,0 +1,53 @@ +// +// Copyright (c) 2015-2016 Snowplow Analytics Ltd. All rights reserved. +// +// This program is licensed to you under the Apache License Version 2.0, +// and you may not use this file except in compliance with the Apache License Version 2.0. +// You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the Apache License Version 2.0 is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. +// +package main + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParsePlaybookYaml(t *testing.T) { + assert := assert.New(t) + + playbook, err := parsePlaybookYaml(nil) + assert.Nil(err) + assert.NotNil(playbook) + assert.Equal(0, len(playbook.Targets)) + assert.Equal(0, len(playbook.Steps)) + + playbookBytes, err1 := loadLocalFile("../integration/resources/good-postgres.yml") + assert.Nil(err1) + assert.NotNil(playbookBytes) + + playbook, err = parsePlaybookYaml(playbookBytes) + assert.Nil(err) + assert.NotNil(playbook) + assert.Equal(2, len(playbook.Targets)) + assert.Equal(5, len(playbook.Steps)) +} + +func TestCleanYaml(t *testing.T) { + assert := assert.New(t) + + rawYaml := []byte(":hello: world\n:world: hello") + cleanYamlStr := string(cleanYaml(rawYaml)) + assert.Equal("hello: world\nworld: hello\n", cleanYamlStr) + + rawYaml = []byte(":hello:\n :world: hello") + cleanYamlStr = string(cleanYaml(rawYaml)) + assert.Equal("hello:\n world: hello\n", cleanYamlStr) + + cleanYamlStr = string(cleanYaml(nil)) + assert.Equal("\n", cleanYamlStr) +}