This is the Cloudify Ecosystem CICD toolkit. These are testing tools for Cloudify. However, there is really more here. Also, you will find tools for building and packaging and publishing Cloudify Ecosystem assets.
- Blueprint testing tools.
- Plugin testing tools.
- Plugin Bundle Packaging.
- Plugin release to Github and S3.
- Integration with an IDE.
You can use all of these tools on your laptop to develop, test, and release blueprints and plugins.
Finally, this toolkit is designed to be super easy to use! So you can probably get no sleep for days, and still be able to test your plugins!
- Cloudify Ecosystem Test.
pip install https://github.com/cloudify-incubator/cloudify-ecosystem-test/archive/latest.zip
- Start a Cloudify Manager on your laptop with Docker.
- A Cloudify License, if you are not using Cloudify Community.
- Any credentials that you need to authenticate with your Cloud provider API.
- Any Cloudify plugins that are not part of your build steps.
Advanced users of the toolkit can automate Cloudify Manager setup using this toolkit. But I suggest for simple use cases that you use an already configured manager.
- Upload your Cloudify license.
cfy license upload /home/user/path/to/license.yaml
- Upload your plugins.
cfy plugins bundle-upload
- Create your secrets.
cfy secrets create .... -s ######
orcfy secrets create ..... -f /home/user/path/to/secret-file
Let's start with the blueprint print testing tools. Let's say that you have a blueprint. For example, our Hello World Example.
Say you download that example into a new folder.
:: wget https://github.com/cloudify-community/blueprint-examples/releases/download/latest/hello-world-example.zip
--2020-12-10 09:39:31-- https://github.com/cloudify-community/blueprint-examples/releases/download/latest/hello-world-example.zip
Resolving github.com (github.com)... 140.82.121.4
Connecting to github.com (github.com)|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://github-production-release-asset-2e65be.s3.amazonaws.com/169957344/13935200-37f1-11eb-9bdd-7d00f4af162f?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201210%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201210T073931Z&X-Amz-Expires=300&X-Amz-Signature=e5a14bf9a51b1388e9bc82a0515437d192b317190ccf491132683943486218f9&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=169957344&response-content-disposition=attachment%3B%20filename%3Dhello-world-example.zip&response-content-type=application%2Foctet-stream [following]
--2020-12-10 09:39:32-- https://github-production-release-asset-2e65be.s3.amazonaws.com/169957344/13935200-37f1-11eb-9bdd-7d00f4af162f?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201210%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201210T073931Z&X-Amz-Expires=300&X-Amz-Signature=e5a14bf9a51b1388e9bc82a0515437d192b317190ccf491132683943486218f9&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=169957344&response-content-disposition=attachment%3B%20filename%3Dhello-world-example.zip&response-content-type=application%2Foctet-stream
Resolving github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)... 52.217.90.28
Connecting to github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.217.90.28|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 84983 (83K) [application/octet-stream]
Saving to: ‘hello-world-example.zip’
hello-world-example.zip 100%[===================================================================================================================================================================================================>] 82.99K 105KB/s in 0.8s
2020-12-10 09:39:33 (105 KB/s) - ‘hello-world-example.zip’ saved [84983/84983]
Now unzip the downloaded archive.
:: unzip hello-world-example.zip
Archive: hello-world-example.zip
extracting: hello-world-example/openstack.yaml
extracting: hello-world-example/README.md
extracting: hello-world-example/gcp.yaml
extracting: hello-world-example/azure.yaml
extracting: hello-world-example/aws.yaml
extracting: hello-world-example/aws-terraform.yaml
extracting: hello-world-example/aws-cloudformation.yaml
extracting: hello-world-example/scripts/gcp/key.py
extracting: hello-world-example/scripts/terraform/inject_ip.py
extracting: hello-world-example/resources/terraform/template.zip
extracting: hello-world-example/resources/terraform/template/variables.tf
extracting: hello-world-example/resources/terraform/template/main.tf
extracting: hello-world-example/resources/cloudformation/template.yaml
extracting: hello-world-example/apache2/index2.html
extracting: hello-world-example/apache2/index.html
extracting: hello-world-example/apache2/playbook2.yaml
extracting: hello-world-example/apache2/cloudify-logo.png
extracting: hello-world-example/apache2/playbook.yaml
extracting: hello-world-example/apache2/vhost
extracting: hello-world-example/includes/ansible.yaml
In order to test one of these files, create a test.
:: vi test.py
In the new test file, add our simplest test which is preconfigured for Hello World:
import pytest
from datetime import datetime
from ecosystem_tests.dorkl import basic_blueprint_test
blueprint_list = ['hello-world-example/aws.yaml']
TEST_ID = 'hello-world-{0}'.format(datetime.now().strftime("%Y%m%d%H%M"))
@pytest.fixture(scope='function', params=blueprint_list)
def blueprint_examples(request, test_id=TEST_ID):
basic_blueprint_test(
request.param,
test_id,
inputs='',
timeout=3000,
endpoint_name='application_endpoint',
endpoint_value=200)
def test_blueprints(blueprint_examples):
assert blueprint_examples is None
This test will install and uninstall all of the blueprints in the blueprint_list
variable in sequence. After install and before uninstall, it will take the application_endpoint
deployment output value. It will make a request to the URL. If the URL does returns a 200
return code, then the test will not pass. Finally, the test will execute uninstall. If the test fails, the cleanup still executes uninstall.
The test parameter endpoint_name
is the output name, for example application_endpoint
in the Hello World Example. The test parameter endpoint_value
is the expected return code.
You can now run this test.
=================================================================================================================================================== test session starts ===================================================================================================================================================
platform darwin -- Python 3.6.5, pytest-4.6.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/macos/Desktop/test
plugins: requests-mock-1.8.0
collected 1 item
....
You will see in the test output all of the same output that you would see if you installed the blueprint manually from the CLI.
You can also build new tests with this toolkit.
For example, the Getting Started test.
This test is designed to upload component blueprints to your manager, before executing the main install.
Plugin testing tools are very similar to the blueprint testing tools in that, we are going to test a plugin by running a blueprint that uses that plugin.
Let's use the AWS plugin as an example. Clone the AWS plugin:
:: git clone https://github.com/cloudify-cosmo/cloudify-aws-plugin.git
For the first test, you should build a wagon of the plugin code.
Let's also clone the wagon builders repo:
:: git clone https://github.com/cloudify-cosmo/cloudify-wagon-build-containers.git
Build the wagon builder image:
:: docker build -t cloudify-centos-7-wagon-builder-py3 cloudify-wagon-build-containers/centos_7_py3/
Let's now build the AWS plugin wagon:
:: docker run -v /home/user/path/to/cloudify-aws-plugin/:/packaging cloudify-centos-7-wagon-builder-py3:latest
Upload the plugin:
:: cfy plugins upload cloudify-aws-plugin/cloudify_aws_plugin-2.5.1-py36-none-linux_x86_64.wgn -y cloudify-aws-plugin/plugin.yaml
You can use the same test from the blueprint test. However, some notes:
-
Again, the list of paths in the
blueprint_list
variable are the blueprints that will be tested. -
If your test blueprint does not have outputs that can be cURLed, then simple remove these two values:
endpoint_name
andendpoint_value
:
basic_blueprint_test(
request.param,
test_id,
inputs='',
timeout=3000)
Run the test:
=================================================================================================================================================== test session starts ===================================================================================================================================================
platform darwin -- Python 3.6.5, pytest-4.6.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/macos/Desktop/test
plugins: requests-mock-1.8.0
collected 1 item
....
Now that the test has passed (or failed!), let's say that you want to change some code in your plugin.
So, for example you could add some logging to the VM create function in /home/user/path/to/cloudify-aws-plugin/cloudify_aws/ec2/resources/instances.py
.
I'll add something to line 103
in AWS Plugin 2.5.1:
def create(self, params):
'''
Create AWS EC2 Instances.
'''
self.logger.info('I like to test my plugins during development!')
return self.make_client_call('run_instances', params)
Now, how do I get this code on to my manager? Our toolkit offers a solution. Add this to the test imports section:
import os
from ecosystem_tests.dorkl import (
basic_blueprint_test,
update_plugin_on_manager)
plugin_path = os.path.join(
os.path.abspath(
os.path.join(
os.path.dirname(__file__),
os.pardir)
),
'test',
'cloudify-aws-plugin'
)
update_plugin_on_manager(
plugin_path, 'cloudify-aws-plugin', ['cloudify-aws-plugin/cloudify_aws'])
When I run the test, this will replace the AWS plugin code on our Cloudify Manager with the new code, before the blueprint is tested.
Naturally, you should run all unit tests before you test your new plugin code.
Now I can re-run my Hello World test:
:: pytest -s test.py
=================================================================================================================================================== test session starts ===================================================================================================================================================
platform darwin -- Python 3.6.5, pytest-4.6.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/macos/Desktop/test
plugins: requests-mock-1.8.0
collecting ... DEBUG:root:Checking plugin YAML version with /Users/macos/Desktop/test/cloudify-aws-plugin/plugin.yaml
DEBUG:root:Package version 2.5.1
DEBUG:root:Package source https://github.com/cloudify-cosmo/cloudify-aws-plugin/archive/2.5.1.zip
INFO:root:Version 2.5.1 is in CHANGELOG.
INFO:root:Version 2.5.1 matches 2.5.1
.
INFO:logger:Replacing cloudify-aws-plugin/cloudify_aws on manager /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws
INFO:logger:Executing command docker cp cloudify-aws-plugin/cloudify_aws cfy_manager:/tmp/cloudify_aws...
INFO:logger:Command docker cp cloudify-aws-plugin/cloudify_aws cfy_manager:/tmp/cloudify_aws still executing...
INFO:logger:Command finished docker cp cloudify-aws-plugin/cloudify_aws cfy_manager:/tmp/cloudify_aws...
INFO:logger:Command succeeded docker cp cloudify-aws-plugin/cloudify_aws cfy_manager:/tmp/cloudify_aws...
INFO:logger:Executing command docker exec cfy_manager rm -rf /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Command docker exec cfy_manager rm -rf /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws still executing...
INFO:logger:Command finished docker exec cfy_manager rm -rf /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Command succeeded docker exec cfy_manager rm -rf /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Executing command docker exec cfy_manager mv /tmp/cloudify_aws /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Command docker exec cfy_manager mv /tmp/cloudify_aws /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws still executing...
INFO:logger:Command finished docker exec cfy_manager mv /tmp/cloudify_aws /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Command succeeded docker exec cfy_manager mv /tmp/cloudify_aws /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Executing command docker exec cfy_manager chown -R cfyuser:cfyuser /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Command docker exec cfy_manager chown -R cfyuser:cfyuser /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws still executing...
INFO:logger:Command finished docker exec cfy_manager chown -R cfyuser:cfyuser /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
INFO:logger:Command succeeded docker exec cfy_manager chown -R cfyuser:cfyuser /opt/mgmtworker/env/plugins/default_tenant/cloudify-aws-plugin/2.5.1/lib/python3.6/site-packages/cloudify_aws...
You'll notice that the output now details the code swap, that we've asked the test to perform.
If we pay careful attention to our test logs, we will notice that our new log message has been added to the plugin code executed on the manager.
INFO:logger:Command docker exec cfy_manager cfy executions start --timeout 3000 -d hello-world-202012101056 install still executing...
INFO:logger:Execution output: 2020-12-10 09:01:15.788 CFY <hello-world-202012101056> [vm_vr4usn.configure] Sending task 'cloudify_aws.ec2.resources.instances.create'
INFO:logger:Execution output: 2020-12-10 09:01:18.771 LOG <hello-world-202012101056> [vm_vr4usn.configure] INFO: I like to test my plugins during development!
INFO:logger:Command docker exec cfy_manager cfy executions start --timeout 3000 -d hello-world-202012101056 install still executing...
- pytest
- pytest-logger, add this to your test runner configuration:
--log-cli-level debug -s -v
.
Ecosystem tests CLI introduced in order to improve blueprint testing and continuous development of blueprints. Moreover, it makes testing blueprints and plugins via CI tools very intuitive.
The CLI has three commands:
- prepare-test-manager
- local-blueprint-test
- validate-blueprint
prepare-test-manager
command responsible for uploading license, plugins and create secrets on the manager before test invocation.
If the manager has all the assets needed for the test, you can skip this command.
l, --license TEXT
- Licence for the manager, should be either path
to licence file or base64 encoded licence string. Default: license.yaml
-s, --secret TEXT
- A secret to update on the manager, should be provided
as secret_key=secret_value. This argument can be used multiple times.
-fs, --file-secret TEXT
- A secret to update on the manager, should be
provided as secret_key=file_path. This argument can be used multiple times.
-es, --encoded-secret TEXT
- Base 64 encoded secret to update on the manager,
should be provided as secret_key=secret_value_base_64_encoded.
This argument can be used multiple times.
-p, --plugin TEXT
- Plugin to upload before test invocation, should be
provided as --plugin plugin_wagon_URL plugin_yaml_URL. This argument can be used
multiple times.
--bundle-path PATH
- Plugins bundle tgz file path.
--skip-bundle-upload
- Specify --skip-bundle-upload for not uploading
plugins bundle before the test. Default: False.
-c, --container-name TEXT
- Manager docker container name. Default: cfy_manager.
--yum-package TEXT
- Yum package to install on the manager container.
This argument can be used multiple times.
Notes:
- On Linux, use
-n
when creating base64 encoded values like:
echo -n secret_value | base64
- In addition, this command will create base64 encoded string of file content:
base64 /path/to/file/with/secret/content -w0
--skip-bundle-upload
is in higher priority than--bundle-path
, means if both provided than--skip-bundle-upload
will take place.
ecosystem-test prepare-test-manager -l $TEST_LICENSE -s aws_access_key_id=<your aws access key id> -s aws_secret_access_key=<your aws secret access key> --yum-package git
This command will:
- Upload license which its content resides in
$TEST_LICENSE
environment variable(base64 encoded!). - Create two secrets on the manager -
aws_access_key_id
andaws_secret_access_key
. - Upload plugins bundle(default value).
- Install
git
package on the manager container usingyum
.
This command invokes blueprint tests, multiple blueprints tests can be invoked in a single command.
-b, --blueprint-path PATH
- Blueprint path, This option can be used multiple times.
Default: blueprint.yaml
.
--test-id TEXT
- Test id, the name of the test deployment. CLI will randomize test id if not provided.
-i, --inputs TEXT
- Test inputs (Can be provided as path to YAML file,
or as 'key1=value1;key2=value2'). This argument can be used multiple times.
-t, --timeout INTEGER
- Test timeout (seconds). Default: 1800.
--on-failure
- Which action to perform on test failure.
Should be one of: donothing
(do nothing), cancel
(cancel install/update workflows if test fails),
rollback-full
, rollback-partial
, uninstall-force
. Default: rollback-partial
.
--uninstall-on-success BOOLEAN
- Whether to perform uninstall if the test
succeeded,and delete the test blueprint. Default: True
.
--on-subsequent-invoke
- Which action to perform on subsequent invocation of
the test (same test id). Should be one of: resume
, rerun
, update
. Default: rerun
.
-c, --container-name TEXT
- Manager docker container name. Default: cfy_manager
.
--nested-test TEXT
- Nested tests, will run by pytest, should be specified in
the pytest notation like: path/to/module.py::TestClass::test_method
.
--dry-run
- Perform dry run means process the inputs and settings for the test
and print this information.
Notes:
-
If multiple blueprints provided in a single test command, do not provide
--test-id
. -
--on-failure
default value isrollback-partial
, although currently Rollback workflow is part of the Utilities plugin and not built in workflow, so it's recommended to use different value for this option while invoking tests. -
When providing
rerun
,resume
for--on-subsequent-invoke
and the tool recognize the test exists, inputs like-b
,-i
will be ignored because an install workflow will be executed/resumed for the existing deployment. it's recommended to provide only--test-id
in such cases.
ecosystem-test local-blueprint-test -b examples/blueprint-examples/virtual-machine/aws-cloudformation.yaml --test-id=virtual-machine -i aws_region_name=us-east-1 -i resource_suffix=$CIRCLE_BUILD_NUM --on-failure=uninstall-force --timeout=3000
This command will:
-
Upload aws-cloudformation.yaml blueprint. The name of the blueprint on the manager is
virtual-machine
. -
Deploy the blueprint with specified inputs. The name of the deployment on the manager is
virtual-machine
. -
If the blueprint upload/install workflow fails(timeout/error), uninstall workflow will be performed, and the blueprint will be deleted from the manager.
This command perform blueprint validation on the given blueprints. The validation done by upload each blueprint to the manager.
-b, --blueprint-path PATH
- Blueprint path, This option can be used multiple
times. Default: blueprint.yaml
-c, --container-name TEXT
- Manager docker container name. Default: cfy_manager
.
ecosystem-test validate-blueprint -b /path/to/bp1.yaml -b /path/to/bp2.yaml
This command will:
- Upload
bp1.yaml
,bp2.yaml
to the manager and if succeed then delete the blueprint. The upload of the blueprints done sequentially.