├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── appveyor.yml ├── cloudify_cli ├── __init__.py ├── async_commands │ ├── __init__.py │ └── audit_log.py ├── blueprint.py ├── cli │ ├── __init__.py │ ├── cfy.py │ └── helptexts.py ├── colorful_event.py ├── commands │ ├── __init__.py │ ├── agents.py │ ├── apply.py │ ├── audit_log.py │ ├── blueprints.py │ ├── certificates.py │ ├── cluster.py │ ├── community.py │ ├── config.py │ ├── deployments.py │ ├── events.py │ ├── executions.py │ ├── groups.py │ ├── idp.py │ ├── init.py │ ├── install.py │ ├── ldap.py │ ├── license.py │ ├── log_bundles.py │ ├── maintenance_mode.py │ ├── node_instances.py │ ├── nodes.py │ ├── permissions.py │ ├── plugins.py │ ├── profiles.py │ ├── secrets.py │ ├── sites.py │ ├── snapshots.py │ ├── status.py │ ├── summary.py │ ├── tenants.py │ ├── tokens.py │ ├── uninstall.py │ ├── user_groups.py │ ├── users.py │ └── workflows.py ├── config │ ├── __init__.py │ └── config.py ├── constants.py ├── env.py ├── exceptions.py ├── execution_events_fetcher.py ├── filters_utils.py ├── inputs.py ├── labels_utils.py ├── local.py ├── logger.py ├── main.py ├── prettytable.py ├── replace_certificates_config.py ├── table.py ├── tests │ ├── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── mocks.py │ │ ├── test_agents.py │ │ ├── test_apply.py │ │ ├── test_base.py │ │ ├── test_blueprints.py │ │ ├── test_cluster.py │ │ ├── test_deployments.py │ │ ├── test_events.py │ │ ├── test_executions.py │ │ ├── test_filters.py │ │ ├── test_groups.py │ │ ├── test_init.py │ │ ├── test_install.py │ │ ├── test_labels.py │ │ ├── test_ldap.py │ │ ├── test_list_sort.py │ │ ├── test_maintenance_mode.py │ │ ├── test_node_instances.py │ │ ├── test_nodes.py │ │ ├── test_options.py │ │ ├── test_plugins.py │ │ ├── test_profiles.py │ │ ├── test_secrets.py │ │ ├── test_sites.py │ │ ├── test_snapshots.py │ │ ├── test_status.py │ │ ├── test_tenants.py │ │ ├── test_tokens.py │ │ ├── test_uninstall.py │ │ ├── test_use.py │ │ ├── test_user_groups.py │ │ ├── test_users.py │ │ ├── test_version.py │ │ └── test_workflows.py │ ├── conftest.py │ ├── resources │ │ ├── __init__.py │ │ ├── blueprints │ │ │ ├── bad_blueprint │ │ │ │ ├── blueprint.yaml │ │ │ │ ├── blueprint_catalog.yaml │ │ │ │ └── plugin_repo.yaml │ │ │ ├── cloudify-hello-world-example-master.zip │ │ │ ├── helloworld.zip │ │ │ ├── helloworld │ │ │ │ ├── blueprint.yaml │ │ │ │ ├── inputs.yaml │ │ │ │ ├── sample_app.yaml │ │ │ │ └── simple_blueprint.yaml │ │ │ ├── helloworld_custom_name.zip │ │ │ ├── local │ │ │ │ ├── blueprint.yaml │ │ │ │ ├── blueprint_validate_definitions_version.yaml │ │ │ │ ├── blueprint_with_plugins.yaml │ │ │ │ ├── blueprint_without_plugins.yaml │ │ │ │ ├── install-agent-blueprint.yaml │ │ │ │ └── windows_installers_blueprint.yaml │ │ │ └── logging │ │ │ │ └── blueprint.yaml │ │ ├── inputs │ │ │ ├── bad_format.yaml │ │ │ └── not_dict.yaml │ │ ├── mocks │ │ │ ├── __init__.py │ │ │ ├── mock_list_response.py │ │ │ └── mock_tar.tar │ │ ├── old_local_profile │ │ │ └── bp1 │ │ │ │ ├── data │ │ │ │ ├── executions │ │ │ │ ├── node-instances │ │ │ │ └── x_0nsqzw │ │ │ │ ├── payload │ │ │ │ ├── resources │ │ │ │ ├── bp.yaml │ │ │ │ └── scripts │ │ │ │ │ └── increase.py │ │ │ │ └── work │ │ │ │ └── .gitkeep │ │ ├── plugins │ │ │ ├── Cute-Rain-Cloud-with-Rainbow.png │ │ │ ├── plugin.tar.gz │ │ │ └── plugin.yaml │ │ ├── profile.yaml │ │ ├── snapshots │ │ │ └── snapshot.zip │ │ └── storage │ │ │ └── manager1 │ │ │ ├── dir1 │ │ │ ├── file1 │ │ │ └── file2 │ │ │ ├── dir2 │ │ │ ├── file1 │ │ │ └── file2 │ │ │ ├── dotgit │ │ │ └── gitfile │ │ │ ├── file1 │ │ │ └── file2 │ ├── test_blueprint.py │ ├── test_env.py │ ├── test_inputs.py │ ├── test_multiple_local_profiles.py │ └── test_utils.py └── utils.py ├── jenkins ├── Jenkinsfile └── build-pod.yaml ├── packaging ├── Vagrantfile ├── cloudify-cli.spec ├── debian │ └── build.sh ├── icon.png ├── linux │ ├── build.sh │ ├── provision.sh │ └── source │ │ └── get-pip.py ├── source_branch ├── version_info └── windows │ ├── main.tf │ ├── packaging │ ├── create_install_wizard.iss │ ├── source │ │ ├── icons │ │ │ └── Cloudify.ico │ │ └── import_resolver.yaml │ └── update_wheel.py │ └── win_cli_builder.ps1 ├── requirements.in ├── requirements.txt ├── setup.py └── test-requirements.txt /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | checkout: 4 | post: 5 | - > 6 | if [ -n "$CI_PULL_REQUEST" ]; then 7 | PR_ID=${CI_PULL_REQUEST##*/} 8 | git fetch origin +refs/pull/$PR_ID/merge: 9 | git checkout -qf FETCH_HEAD 10 | fi 11 | 12 | executors: 13 | py27: 14 | docker: 15 | - image: circleci/python:2.7 16 | py36: 17 | docker: 18 | - image: circleci/python:3.6 19 | 20 | commands: 21 | install_test_dependencies: 22 | parameters: 23 | cache_prefix: 24 | type: string 25 | default: py27 26 | steps: 27 | - restore_cache: 28 | keys: 29 | - << parameters.cache_prefix >>-venv-{{ checksum "requirements.txt" }}-{{ checksum "test-requirements.txt" }}-{{ checksum "setup.py" }} 30 | - run: ~/venv/bin/pip install -r requirements.txt 31 | - run: ~/venv/bin/pip install -r test-requirements.txt 32 | - run: ~/venv/bin/pip install -e . 33 | - save_cache: 34 | paths: 35 | - /home/circleci/venv 36 | key: << parameters.cache_prefix >>-venv-{{ checksum "requirements.txt" }}-{{ checksum "test-requirements.txt" }}-{{ checksum "setup.py" }} 37 | pytest: 38 | steps: 39 | - run: 40 | name: pytest 41 | command: | 42 | ~/venv/bin/pytest \ 43 | --cov-report term-missing \ 44 | --cov=cloudify_cli \ 45 | cloudify_cli/tests \ 46 | --junitxml=test-results/cloudify_cli.xml 47 | 48 | jobs: 49 | flake8_py27: 50 | executor: py27 51 | steps: 52 | - checkout 53 | - run: pip install flake8 --user 54 | - run: 55 | name: Run flake8 56 | command: flake8 cloudify_cli 57 | 58 | flake8_py36: 59 | executor: py36 60 | steps: 61 | - checkout 62 | - run: pip install flake8 --user 63 | - run: 64 | name: Run flake8 65 | command: flake8 cloudify_cli 66 | 67 | test_py27: 68 | executor: py27 69 | steps: 70 | - checkout 71 | - run: virtualenv ~/venv 72 | - install_test_dependencies 73 | - pytest 74 | - store_test_results: 75 | path: test-results 76 | 77 | test_py36: 78 | executor: py36 79 | steps: 80 | - checkout 81 | - run: python -m venv ~/venv 82 | - install_test_dependencies: 83 | cache_prefix: py36 84 | - pytest 85 | - store_test_results: 86 | path: test-results 87 | build-deb: 88 | docker: 89 | - image: ubuntu:18.04 90 | steps: 91 | - checkout 92 | - run: 93 | name: Set envvars 94 | command: | 95 | echo "export PROJECT_DIR=~/project" >>$BASH_ENV 96 | echo "export BUILD_DIR=/tmp/build" >>$BASH_ENV 97 | echo "export RESULT_DIR=/tmp/result" >>$BASH_ENV 98 | echo "export CLOUDIFY_PACKAGE_RELEASE=1" >>$BASH_ENV 99 | mkdir /tmp/result 100 | - run: 101 | name: Set CLOUDIFY_VERSION 102 | command: | 103 | set -ex 104 | apt-get update 105 | apt-get install python3 python3-setuptools -y 106 | echo "export CLOUDIFY_VERSION=$(python3 ~/project/setup.py --version)" >>$BASH_ENV 107 | - run: 108 | name: build the deb package 109 | command: /bin/bash ~/project/packaging/debian/build.sh 110 | - run: 111 | name: sanity-test the deb package 112 | command: | 113 | set -ex 114 | rm -rf /opt/cfy 115 | dpkg -i /tmp/result/*.deb 116 | cfy --version 117 | - store_artifacts: 118 | path: /tmp/result 119 | destination: / 120 | 121 | build-rpm: 122 | docker: 123 | - image: rpmbuild/centos7 124 | working_directory: ~/rpm 125 | steps: 126 | - checkout 127 | - run: sudo yum install python-setuptools -y 128 | - run: sudo chmod a+wx /opt 129 | - run: 130 | name: Installing build dependencies 131 | command: sudo yum-builddep -y packaging/cloudify-cli.spec 132 | - run: 133 | name: Building rpm 134 | command: | 135 | rpmbuild \ 136 | -D "CLOUDIFY_VERSION $(python setup.py --version | cut -d- -f1)" \ 137 | -D "CLOUDIFY_PACKAGE_RELEASE 1" \ 138 | -bb packaging/cloudify-cli.spec 139 | - run: | 140 | mkdir result 141 | mv x86_64/*.rpm result 142 | - store_artifacts: 143 | path: result 144 | destination: result 145 | 146 | workflows: 147 | version: 2 148 | 149 | build_and_test: 150 | jobs: &build_jobs 151 | - build-rpm 152 | - flake8_py27 153 | - test_py27: 154 | requires: 155 | - flake8_py27 156 | - flake8_py36 157 | - test_py36: 158 | requires: 159 | - flake8_py36 160 | - build-deb 161 | nightly: 162 | triggers: 163 | - schedule: 164 | cron: "0 0 * * *" 165 | filters: 166 | branches: 167 | only: 168 | - master 169 | jobs: *build_jobs 170 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage* 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | .pytest_cache/ 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | *.iml 57 | 58 | *COMMIT_MSG 59 | 60 | # QuickBuild 61 | .qbcache/ 62 | 63 | .noseids 64 | 65 | # OS X 66 | .DS_Store 67 | 68 | .idea/ 69 | .cloudify/ 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudify CLI 2 | 3 | [![Circle CI](https://circleci.com/gh/cloudify-cosmo/cloudify-cli.svg?style=shield)](https://circleci.com/gh/cloudify-cosmo/cloudify-cli) 4 | [![Build Status](https://travis-ci.org/cloudify-cosmo/cloudify-cli.svg?branch=master)](https://travis-ci.org/cloudify-cosmo/cloudify-cli) 5 | [![PyPI](http://img.shields.io/pypi/dm/cloudify.svg)](http://img.shields.io/pypi/dm/cloudify.svg) 6 | [![PypI](http://img.shields.io/pypi/v/cloudify.svg)](http://img.shields.io/pypi/v/cloudify.svg) 7 | 8 | 9 | Cloudify's Command Line Interface. 10 | 11 | ## Usage 12 | 13 | See [Cloudify CLI](https://docs.cloudify.co/latest/cli/) 14 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | 3 | TOX_ENV: pywin 4 | 5 | matrix: 6 | - PYTHON: C:\Python27 7 | PYTHON_VERSION: 2.7.8 8 | PYTHON_ARCH: 32 9 | 10 | install: 11 | 12 | ################################# 13 | # Change Python Registry 14 | ################################# 15 | 16 | - reg ADD HKCU\Software\Python\PythonCore\2.7\InstallPath /ve /d "C:\Python27" /t REG_SZ /f 17 | - reg ADD HKLM\Software\Python\PythonCore\2.7\InstallPath /ve /d "C:\Python27" /t REG_SZ /f 18 | 19 | ################################# 20 | # Installing Inno Setup 21 | ################################# 22 | 23 | - choco install -y InnoSetup 24 | - set PATH="C:\\Program Files (x86)\\Inno Setup 5";%PATH% 25 | 26 | - SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% 27 | 28 | - echo Upgrading pip... 29 | - ps: (new-object System.Net.WebClient).Downloadfile('https://bootstrap.pypa.io/get-pip.py', 'C:\Users\appveyor\get-pip.py') 30 | - ps: Start-Process -FilePath "C:\Python27\python.exe" -ArgumentList "C:\Users\appveyor\get-pip.py" -Wait -Passthru 31 | - pip --version 32 | 33 | build: false # Not a C# project, build stuff at the test step instead. 34 | 35 | before_test: 36 | - echo Installing tox (2.0.0) 37 | - pip install tox>=2.0.0 38 | 39 | test_script: 40 | - tox -e %TOX_ENV% -------------------------------------------------------------------------------- /cloudify_cli/__init__.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | -------------------------------------------------------------------------------- /cloudify_cli/async_commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/async_commands/__init__.py -------------------------------------------------------------------------------- /cloudify_cli/async_commands/audit_log.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | 4 | import aiohttp.client_exceptions 5 | 6 | from cloudify_cli.exceptions import CloudifyCliError 7 | from cloudify_cli.logger import get_global_json_output 8 | 9 | 10 | def stream_logs(creator_name, 11 | execution_id, 12 | since, 13 | timeout, 14 | logger, 15 | client): 16 | loop = asyncio.get_event_loop() 17 | loop.run_until_complete(_stream_logs(creator_name, 18 | execution_id, 19 | since, 20 | timeout, 21 | logger, 22 | client)) 23 | loop.close() 24 | 25 | 26 | async def _stream_logs(creator_name, 27 | execution_id, 28 | since, 29 | timeout, 30 | logger, 31 | client): 32 | if not hasattr(client.auditlog, 'stream'): 33 | raise CloudifyCliError('Streaming requires Python>=3.6.') 34 | logger.info('Streaming audit log entries...') 35 | response = await client.auditlog.stream(timeout=timeout, 36 | creator_name=creator_name, 37 | execution_id=execution_id, 38 | since=since) 39 | try: 40 | async for data in response.content: 41 | for audit_log in _streamed_audit_log(data): 42 | if get_global_json_output(): 43 | print(audit_log) 44 | else: 45 | print(_format_audit_log(audit_log)) 46 | except aiohttp.client_exceptions.ClientError as e: 47 | raise CloudifyCliError(f'Error getting audit log stream: {e}') from e 48 | 49 | 50 | def _streamed_audit_log(data): 51 | line = data.strip().decode(errors='ignore') 52 | if line: 53 | yield json.loads(line) 54 | 55 | 56 | def _format_audit_log(data): 57 | result = f"[{data['created_at']}]" 58 | if 'creator_name' in data and data['creator_name']: 59 | result = f"{result} user {data['creator_name']}" 60 | if 'execution_id' in data and data['execution_id']: 61 | result = f"{result} execution {data['execution_id']}" 62 | result = f"{result} {data['operation'].upper()}D" 63 | result = f"{result} {data['ref_table']} {data['ref_id']}" 64 | return result 65 | -------------------------------------------------------------------------------- /cloudify_cli/cli/__init__.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | -------------------------------------------------------------------------------- /cloudify_cli/commands/__init__.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | -------------------------------------------------------------------------------- /cloudify_cli/commands/audit_log.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime, timedelta 3 | 4 | import click 5 | 6 | from cloudify_cli.cli import cfy, helptexts 7 | from cloudify_cli.exceptions import CloudifyCliError 8 | from cloudify_cli.logger import get_global_json_output 9 | from cloudify_cli.table import print_data 10 | 11 | REF_OBJECT = 'ref_object' 12 | LIST_COLUMNS = ['operation', REF_OBJECT, 'creator_name', 'execution_id', 13 | 'created_at'] 14 | JSON_COLUMNS = ['id', 'ref_table', 'ref_id', 'ref_identifier', 'operation', 15 | 'creator_name', 'execution_id', 'created_at'] 16 | 17 | 18 | def _parse_before(ctx, _, spec): 19 | """Parse the --before/--since parameter""" 20 | if not spec: 21 | return spec 22 | if spec == "now": 23 | return datetime.utcnow() 24 | r = re.match(r'^([.\d]+)([hdw])$', spec, re.IGNORECASE) 25 | if r: 26 | # timestamp specification e.g. 10.5h, 15d, 7w 27 | count, unit = float(r.groups()[0]), r.groups()[1].lower() 28 | if unit == 'h': 29 | delta = timedelta(hours=count) 30 | elif unit == 'd': 31 | delta = timedelta(days=count) 32 | else: # 'w' 33 | delta = timedelta(weeks=count) 34 | return datetime.utcnow() - delta 35 | elif spec.startswith('@'): 36 | try: 37 | return datetime.utcfromtimestamp(int(spec[1:])) 38 | except ValueError: 39 | raise CloudifyCliError('Failed to parse timestamp: {0}' 40 | .format(spec)) 41 | else: 42 | return spec 43 | 44 | 45 | @cfy.group(name='auditlog') 46 | @cfy.assert_manager_active() 47 | def auditlog(): 48 | """Manage the audit log""" 49 | 50 | 51 | @auditlog.command(name='list', 52 | short_help='List audit log entries') 53 | @click.option('-c', '--creator-name', 54 | help=helptexts.AUDIT_CREATOR_NAME) 55 | @click.option('-e', '--execution-id', 56 | help=helptexts.AUDIT_EXECUTION_ID) 57 | @click.option('-i', '--since', 58 | help=helptexts.AUDIT_SINCE, 59 | callback=_parse_before) 60 | @click.option('-f', '--follow', 61 | help=helptexts.AUDIT_FOLLOW, 62 | is_flag=True) 63 | @cfy.options.timeout(default=300) 64 | @cfy.options.sort_by() 65 | @cfy.options.descending 66 | @cfy.options.pagination_offset 67 | @cfy.options.pagination_size 68 | @cfy.options.common_options 69 | @cfy.pass_logger 70 | @cfy.pass_client() 71 | def list_logs(creator_name, 72 | execution_id, 73 | since, 74 | follow, 75 | timeout, 76 | sort_by, 77 | descending, 78 | pagination_offset, 79 | pagination_size, 80 | logger, 81 | client, 82 | ): 83 | if follow: 84 | from cloudify_cli.async_commands.audit_log import stream_logs 85 | stream_logs(creator_name, 86 | execution_id, 87 | since, 88 | timeout, 89 | logger, 90 | client) 91 | else: 92 | _list_logs(creator_name, 93 | execution_id, 94 | since, 95 | sort_by, 96 | descending, 97 | pagination_offset, 98 | pagination_size, 99 | logger, 100 | client) 101 | 102 | 103 | def _list_logs(creator_name, 104 | execution_id, 105 | since, 106 | sort_by, 107 | descending, 108 | pagination_offset, 109 | pagination_size, 110 | logger, 111 | client): 112 | """List audit_log entries""" 113 | logger.info('Listing audit log entries...') 114 | logs = client.auditlog.list( 115 | creator_name=creator_name, 116 | execution_id=execution_id, 117 | since=since, 118 | order_by=sort_by, 119 | desc=descending, 120 | offset=pagination_offset, 121 | size=pagination_size, 122 | ) 123 | columns = JSON_COLUMNS if get_global_json_output() else LIST_COLUMNS 124 | print_data(columns, _update_refs(logs), 'AuditLogs:') 125 | logger.info('Showing %d of %d audit log entries', 126 | len(logs), logs.metadata.pagination.total) 127 | 128 | 129 | def _update_refs(logs_response): 130 | for log in logs_response: 131 | if ref_identifier := log.get('ref_identifier'): 132 | if tenant_name := ref_identifier.get("tenant_name"): 133 | prefix = f'{tenant_name}:' 134 | else: 135 | prefix = '' 136 | for col in ['id', 'manager_id', 'username', 'storage_id']: 137 | if val := ref_identifier.get(col): 138 | log[REF_OBJECT] = f'{prefix}{log.get("ref_table")}:{val}' 139 | if not log.get(REF_OBJECT): 140 | log[REF_OBJECT] = f'{log.get("ref_table")}:{log.get("ref_id")}' 141 | yield log 142 | 143 | 144 | @auditlog.command(name='truncate', 145 | short_help='Truncate audit log') 146 | @click.option('-b', '--before', 147 | required=True, 148 | help=helptexts.AUDIT_TRUNCATE_BEFORE, 149 | callback=_parse_before) 150 | @click.option('-c', '--creator-name', 151 | help=helptexts.AUDIT_CREATOR_NAME) 152 | @click.option('-e', '--execution-id', 153 | help=helptexts.AUDIT_EXECUTION_ID) 154 | @cfy.pass_logger 155 | @cfy.pass_client() 156 | def truncate_logs(before, 157 | creator_name, 158 | execution_id, 159 | logger, 160 | client 161 | ): 162 | """Truncate audit_log entries""" 163 | logger.info("Truncating audit log entries...") 164 | params = {'before': before} 165 | if creator_name: 166 | params.update({'creator_name': creator_name}) 167 | if execution_id: 168 | params.update({'execution_id': execution_id}) 169 | result = client.auditlog.delete(**params) 170 | logger.info('%d audit log entries have been truncated', result.deleted) 171 | -------------------------------------------------------------------------------- /cloudify_cli/commands/community.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from cloudify_cli.cli import cfy 4 | 5 | 6 | @cfy.group(name='community') 7 | @cfy.options.common_options 8 | def community(): 9 | """Commands specific for the Cloudify Community edition""" 10 | 11 | 12 | @community.command(name='register', 13 | short_help='Register a new Cloudify Community contact') 14 | @click.option('-f', '--first-name', 15 | help='The contact\'s first name', required=True) 16 | @click.option('-l', '--last-name', 17 | help='The contact\'s last name', required=True) 18 | @click.option('-e', '--email', 19 | help='The contact\'s Email address', required=True) 20 | @click.option('-p', '--phone', 21 | help='The contact\'s phone number', required=True) 22 | @click.option('-a', '--accept-eula', is_flag=True, 23 | help='By using this flag you agree to the terms of the ' 24 | 'End User License Agreement ' 25 | '(https://cloudify.co/license-community)') 26 | @cfy.pass_client() 27 | @cfy.pass_logger 28 | def register(first_name, last_name, email, phone, accept_eula, logger, client): 29 | """Register a new Cloudify Community contact. 30 | """ 31 | customer_id = client.community_contacts.create( 32 | first_name, 33 | last_name, 34 | email, 35 | phone, 36 | accept_eula, 37 | ) 38 | logger.info('Cloudify Community registered successfully. Customer ID:' 39 | ' "%s"', customer_id) 40 | -------------------------------------------------------------------------------- /cloudify_cli/commands/config.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2019 Cloudify.co Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | 18 | from cloudify_cli.cli import cfy 19 | from cloudify_cli.table import print_data 20 | 21 | CONFIG_COLUMNS = [ 22 | 'name', 'value', 'scope', 'updated_at', 'is_editable', 'admin_only', 23 | ] 24 | 25 | 26 | @cfy.group(name='config') 27 | @cfy.options.common_options 28 | def config(): 29 | """Handle manager configuration""" 30 | 31 | 32 | @config.command(name='list', 33 | short_help='List configuration') 34 | @cfy.pass_client() 35 | @cfy.options.common_options 36 | def list_config(client): 37 | configs = client.manager.get_config() 38 | print_data(CONFIG_COLUMNS, configs, 'Config:') 39 | 40 | 41 | @config.command(name='update', 42 | short_help='Update configuration') 43 | @cfy.pass_client() 44 | @cfy.pass_logger 45 | @cfy.argument('inputs', callback=cfy.inputs_callback, nargs=-1) 46 | @cfy.options.common_options 47 | def update_config(client, inputs, logger): 48 | """Update the manager configuration. 49 | 50 | Pass INPUTS as a yaml-formatted dict with {"config name": "new value"}, 51 | or as a path to a file containing yaml. 52 | 53 | Note: strings passed as input must be surrounded by '...' or "..." 54 | 55 | To resolve ambiguous names, config name can be prefixed with scope, 56 | e.g.: 57 | cfy config update '{"rest.ldap_username": "adminuser", 58 | "rest.ldap_password": "adminpassword"}' 59 | 60 | """ 61 | for name, value in inputs.items(): 62 | updated = client.manager.put_config(name, value) 63 | logger.info('Updated %s to %s', name, value) 64 | if updated.scope == 'mgmtworker': 65 | logger.info('Updating mgmtworker config will only take effect ' 66 | 'after the service has been restarted') 67 | elif updated.scope == 'agent': 68 | logger.info('Updating agent config will only take effect for ' 69 | 'agents installed from now on. It will NOT update ' 70 | 'existing agents.') 71 | -------------------------------------------------------------------------------- /cloudify_cli/commands/groups.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | import json 18 | 19 | from cloudify_rest_client.exceptions import CloudifyClientError 20 | 21 | from cloudify_cli import utils 22 | from cloudify_cli.cli import cfy 23 | from cloudify_cli.exceptions import CloudifyCliError 24 | 25 | 26 | @cfy.group(name='groups') 27 | @cfy.options.common_options 28 | @cfy.assert_manager_active() 29 | def groups(): 30 | """Handle deployment groups 31 | """ 32 | pass 33 | 34 | 35 | @groups.command(name='list', 36 | short_help='List groups for a deployment [manager only]') 37 | @cfy.options.deployment_id(required=True) 38 | @cfy.options.common_options 39 | @cfy.options.tenant_name(required=False, resource_name_for_help='deployment') 40 | @cfy.pass_client() 41 | @cfy.pass_logger 42 | def list(deployment_id, logger, client, tenant_name): 43 | """List all groups for a deployment 44 | """ 45 | utils.explicit_tenant_name_message(tenant_name, logger) 46 | logger.info("Listing groups for deployment {0}...".format( 47 | deployment_id)) 48 | try: 49 | deployment = client.deployments.get(deployment_id) 50 | except CloudifyClientError as e: 51 | if e.status_code != 404: 52 | raise 53 | raise CloudifyCliError('Deployment {0} not found'.format( 54 | deployment_id)) 55 | 56 | groups = deployment.get('groups', {}) 57 | scaling_groups = deployment.get('scaling_groups', {}) 58 | 59 | if not groups: 60 | logger.info('No groups defined for deployment {0}'.format( 61 | deployment.id)) 62 | else: 63 | logger.info("Groups: {0}".format(deployment.id)) 64 | for group_name, group in sorted(groups.items()): 65 | logger.info(' - Name: {0}'.format(group_name)) 66 | logger.info(' Members: {0}'.format( 67 | json.dumps(group['members']))) 68 | group_policies = group.get('policies') 69 | scaling_group = scaling_groups.get(group_name) 70 | if group_policies or scaling_group: 71 | logger.info(' Policies:') 72 | if scaling_group: 73 | logger.info(' - cloudify.policies.scaling') 74 | if group_policies: 75 | for group_policy in group_policies.values(): 76 | logger.info(' - {0}'.format(group_policy['type'])) 77 | logger.info('') 78 | -------------------------------------------------------------------------------- /cloudify_cli/commands/idp.py: -------------------------------------------------------------------------------- 1 | from cloudify_cli.cli import cfy 2 | 3 | 4 | @cfy.group(name='identity') 5 | @cfy.options.common_options 6 | @cfy.assert_manager_active() 7 | def idp(): 8 | """Identity provider commands. 9 | """ 10 | pass 11 | 12 | 13 | @idp.command(name='get', 14 | short_help='Get the current identity provider for the manager.') 15 | @cfy.pass_client() 16 | @cfy.pass_logger 17 | def get(client, logger): 18 | logger.info(client.idp.get()) 19 | -------------------------------------------------------------------------------- /cloudify_cli/commands/ldap.py: -------------------------------------------------------------------------------- 1 | from cloudify_cli.cli import cfy 2 | from cloudify_cli.exceptions import CloudifyCliError 3 | 4 | 5 | @cfy.group(name='ldap') 6 | @cfy.options.common_options 7 | @cfy.assert_manager_active() 8 | def ldap(): 9 | """Set LDAP authenticator. 10 | """ 11 | pass 12 | 13 | 14 | @ldap.command(name='set', 15 | short_help='Set the manager to use the LDAP authenticator.') 16 | @cfy.options.ldap_server 17 | @cfy.options.ldap_username 18 | @cfy.options.ldap_password 19 | @cfy.options.ldap_domain 20 | @cfy.options.ldap_is_active_directory 21 | @cfy.options.ldap_dn_extra 22 | @cfy.options.ldap_ca_path 23 | @cfy.options.ldap_base_dn 24 | @cfy.options.ldap_group_dn 25 | @cfy.options.ldap_bind_format 26 | @cfy.options.ldap_user_filter 27 | @cfy.options.ldap_group_member_filter 28 | @cfy.options.ldap_attribute_email 29 | @cfy.options.ldap_attribute_first_name 30 | @cfy.options.ldap_attribute_last_name 31 | @cfy.options.ldap_attribute_uid 32 | @cfy.options.ldap_attribute_group_membership 33 | @cfy.options.ldap_nested_levels 34 | @cfy.pass_client() 35 | @cfy.pass_logger 36 | def set(ldap_server, 37 | ldap_username, 38 | ldap_password, 39 | ldap_domain, 40 | ldap_is_active_directory, 41 | ldap_dn_extra, 42 | ldap_base_dn, 43 | ldap_group_dn, 44 | ldap_bind_format, 45 | ldap_user_filter, 46 | ldap_group_member_filter, 47 | ldap_attribute_email, 48 | ldap_attribute_first_name, 49 | ldap_attribute_last_name, 50 | ldap_attribute_uid, 51 | ldap_attribute_group_membership, 52 | ldap_nested_levels, 53 | ldap_ca_path, 54 | client, 55 | logger): 56 | if (ldap_username and not ldap_password) \ 57 | or (ldap_password and not ldap_username): 58 | raise CloudifyCliError( 59 | 'Must either set both username and password, or neither. ' 60 | 'Note that an empty username or password is invalid') 61 | logger.info('Setting the Cloudify manager authenticator to use LDAP..') 62 | client.ldap.set(ldap_server=ldap_server, 63 | ldap_username=ldap_username, 64 | ldap_password=ldap_password, 65 | ldap_is_active_directory=ldap_is_active_directory, 66 | ldap_domain=ldap_domain, 67 | ldap_dn_extra=ldap_dn_extra, 68 | ldap_base_dn=ldap_base_dn, 69 | ldap_group_dn=ldap_group_dn, 70 | ldap_bind_format=ldap_bind_format, 71 | ldap_user_filter=ldap_user_filter, 72 | ldap_group_member_filter=ldap_group_member_filter, 73 | ldap_attribute_email=ldap_attribute_email, 74 | ldap_attribute_first_name=ldap_attribute_first_name, 75 | ldap_attribute_last_name=ldap_attribute_last_name, 76 | ldap_attribute_uid=ldap_attribute_uid, 77 | ldap_attribute_group_membership=( 78 | ldap_attribute_group_membership 79 | ), 80 | ldap_nested_levels=ldap_nested_levels, 81 | ldap_ca_path=ldap_ca_path) 82 | logger.info('LDAP authentication set successfully') 83 | 84 | 85 | @ldap.command(name='status', 86 | short_help='Get the manager LDAP status (enabled/disabled).') 87 | @cfy.pass_client() 88 | @cfy.pass_logger 89 | def status(client, logger): 90 | logger.info(client.ldap.get_status()) 91 | -------------------------------------------------------------------------------- /cloudify_cli/commands/license.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from cloudify_cli.cli import cfy 4 | from cloudify_cli.logger import output 5 | from cloudify_cli.table import print_data 6 | from cloudify_cli.exceptions import CloudifyValidationError 7 | 8 | LICENSE_COLUMN = ['customer_id', 'expiration_date', 'license_edition', 'trial', 9 | 'cloudify_version', 'capabilities', 'expired'] 10 | ENVIRONMENT_COLUMNS = ['id', 'display_name', 'tenant_name'] 11 | ENVIRONMENT_LABELS = {'id': 'deployment_id', 'display_name': 'deployment_name'} 12 | 13 | 14 | @cfy.group(name='license') 15 | @cfy.options.common_options 16 | @cfy.assert_manager_active() 17 | def license(): 18 | """ Handle Cloudify licenses 19 | """ 20 | pass 21 | 22 | 23 | @license.command( 24 | name='check', 25 | short_help='Checks the manager license state is healthy.') 26 | @cfy.assert_manager_active() 27 | @cfy.options.common_options 28 | @cfy.pass_client() 29 | @cfy.pass_logger 30 | def check(logger, client): 31 | logger.info('Checking manager license state.') 32 | client.license.check() 33 | logger.info('Manager is licensed.') 34 | 35 | 36 | @license.command( 37 | name='list', 38 | short_help='Get the Cloudify license that was uploaded to this Manager') 39 | @cfy.assert_manager_active() 40 | @cfy.options.common_options 41 | @cfy.pass_client() 42 | @cfy.pass_logger 43 | @cfy.options.extended_view 44 | def list(logger, client): 45 | """Returns the Cloudify license from the Manager. 46 | """ 47 | logger.info('Retrieving Cloudify License') 48 | license = client.license.list() 49 | print_data(LICENSE_COLUMN, license, 'Cloudify License') 50 | 51 | 52 | @license.command( 53 | name='upload', 54 | short_help='Upload a new Cloudify license to the Manager') 55 | @cfy.argument('license-path') 56 | @cfy.assert_manager_active() 57 | @cfy.options.common_options 58 | @cfy.pass_client() 59 | @cfy.pass_logger 60 | def upload(logger, client, license_path): 61 | logger.info('Uploading Cloudify License `{0}` to the Manager...'. 62 | format(license_path)) 63 | if not os.path.isfile(license_path): 64 | raise CloudifyValidationError('License file was not found in the ' 65 | 'following path: `{0}`'. 66 | format(license_path)) 67 | client.license.upload(license_path) 68 | logger.info('Cloudify license successfully uploaded.') 69 | 70 | 71 | @license.command( 72 | name='remove', 73 | short_help='Remove a Cloudify license from the Manager') 74 | @cfy.assert_manager_active() 75 | @cfy.options.common_options 76 | @cfy.pass_client() 77 | @cfy.pass_logger 78 | def remove(logger, client): 79 | logger.info('Removing Cloudify License from the Manager...') 80 | client.license.delete() 81 | logger.info('Cloudify license successfully removed.') 82 | 83 | 84 | @license.group(name='environments') 85 | @cfy.options.common_options 86 | @cfy.assert_manager_active() 87 | def environments(): 88 | """Handle licensed environments on the manager 89 | """ 90 | pass 91 | 92 | 93 | @environments.command(name='list', 94 | short_help='List all licensed environments') 95 | @cfy.options.sort_by() 96 | @cfy.options.descending 97 | @cfy.options.pagination_offset 98 | @cfy.options.pagination_size 99 | @cfy.options.common_options 100 | @cfy.pass_client() 101 | @cfy.pass_logger 102 | @cfy.options.extended_view 103 | def environments_list(sort_by, 104 | descending, 105 | pagination_offset, 106 | pagination_size, 107 | logger, 108 | client): 109 | """List all licensed environments on the manager. 110 | """ 111 | logger.info('Listing all licensed environments...') 112 | 113 | environments = client.deployments.list(sort=sort_by, 114 | is_descending=descending, 115 | _offset=pagination_offset, 116 | _size=pagination_size, 117 | _all_tenants=True, 118 | _environments_only=True) 119 | print_data(ENVIRONMENT_COLUMNS, environments, 'Environments:', 120 | labels=ENVIRONMENT_LABELS) 121 | logger.info('Showing %d of %d environments', 122 | len(environments), environments.metadata.pagination.total) 123 | 124 | 125 | @environments.command(name='count', 126 | short_help='Print the count of licensed environments') 127 | @cfy.options.common_options 128 | @cfy.pass_logger 129 | @cfy.pass_client() 130 | def environments_count(logger, client): 131 | """Print the count of licensed environments on the manager. 132 | """ 133 | environments = client.deployments.list(_all_tenants=True, 134 | _get_all_results=True, 135 | _environments_only=True) 136 | output('Licensed environments count: {}'.format(len(environments))) 137 | -------------------------------------------------------------------------------- /cloudify_cli/commands/log_bundles.py: -------------------------------------------------------------------------------- 1 | from .. import utils 2 | from ..table import print_data 3 | from ..cli import cfy 4 | 5 | LOG_BUNDLE_COLUMNS = ['id', 'created_at', 'status', 'error', 6 | 'visibility', 'tenant_name', 'created_by'] 7 | 8 | 9 | @cfy.group(name='log_bundles') 10 | @cfy.options.common_options 11 | @cfy.assert_manager_active() 12 | def log_bundles(): 13 | """Handle manager log bundles.""" 14 | 15 | 16 | @log_bundles.command(name='create', 17 | short_help='Create a log bundle [manager only]') 18 | @cfy.argument('log-bundle-id', required=False, callback=cfy.validate_name) 19 | @cfy.options.common_options 20 | @cfy.options.queue_log_bundle 21 | @cfy.pass_client() 22 | @cfy.pass_logger 23 | def create(log_bundle_id, 24 | queue, 25 | logger, 26 | client): 27 | """Create a log bundle on the manager 28 | 29 | The log bundle will contain all cloudify logs it was able to retrieve from 30 | all managers, brokers, and database nodes it was able to reach. 31 | 32 | `LOG_BUNDLE_ID` is the id to attach to the log bundle. 33 | """ 34 | log_bundle_id = log_bundle_id or utils.generate_suffixed_id('log_bundle') 35 | logger.info('Creating log_bundle {0}...'.format(log_bundle_id)) 36 | 37 | execution = client.log_bundles.create(log_bundle_id, queue) 38 | started_log_msg = "Started workflow execution. The execution's id is" \ 39 | " {0}.".format(execution.id) 40 | queued_log_msg = '`queue` flag was passed, log bundle creation will' \ 41 | ' start automatically when possible. Execution id:' \ 42 | ' {0}'.format(execution.id) 43 | queued = True if execution.status == 'queued' else False 44 | logger.info(queued_log_msg) if queued else logger.info(started_log_msg) 45 | 46 | 47 | @log_bundles.command(name='delete', 48 | short_help='Delete a log bundle [manager only]') 49 | @cfy.argument('log-bundle-id') 50 | @cfy.options.common_options 51 | @cfy.pass_client() 52 | @cfy.pass_logger 53 | def delete(log_bundle_id, logger, client): 54 | """Delete a log_bundle from the manager 55 | 56 | `LOG_BUNDLE_ID` is the id of the log bundle to delete. 57 | """ 58 | logger.info('Deleting log_bundle {0}...'.format(log_bundle_id)) 59 | client.log_bundles.delete(log_bundle_id) 60 | logger.info('Log bundle deleted successfully') 61 | 62 | 63 | @log_bundles.command(name='download', 64 | short_help='Download a log bundle [manager only]') 65 | @cfy.argument('log-bundle-id') 66 | @cfy.options.output_path 67 | @cfy.options.common_options 68 | @cfy.pass_client() 69 | @cfy.pass_logger 70 | def download(log_bundle_id, output_path, logger, client): 71 | """Download a log bundle from the manager 72 | 73 | `LOG_BUNDLE_ID` is the id of the log bundle to download. 74 | """ 75 | logger.info('Downloading log_bundle {0}...'.format(log_bundle_id)) 76 | log_bundle_name = output_path if output_path else log_bundle_id 77 | progress_handler = utils.generate_progress_handler(log_bundle_name, '') 78 | target_file = client.log_bundles.download(log_bundle_id, 79 | output_path, 80 | progress_handler) 81 | logger.info('Log bundle downloaded as {0}'.format(target_file)) 82 | 83 | 84 | @log_bundles.command(name='list', 85 | short_help='List log bundles [manager only]') 86 | @cfy.options.sort_by() 87 | @cfy.options.descending 88 | @cfy.options.search 89 | @cfy.options.pagination_offset 90 | @cfy.options.pagination_size 91 | @cfy.options.common_options 92 | @cfy.pass_client() 93 | @cfy.pass_logger 94 | @cfy.options.extended_view 95 | def list(sort_by, 96 | descending, 97 | search, 98 | pagination_offset, 99 | pagination_size, 100 | logger, 101 | client): 102 | """List all log bundles on the manager""" 103 | logger.info('Listing log_bundles...') 104 | log_bundles = client.log_bundles.list( 105 | sort=sort_by, 106 | is_descending=descending, 107 | _search=search, 108 | _offset=pagination_offset, 109 | _size=pagination_size) 110 | 111 | print_data(LOG_BUNDLE_COLUMNS, log_bundles, 'Log bundles:') 112 | total = log_bundles.metadata.pagination.total 113 | logger.info( 114 | 'Showing {0} of {1} log_bundles'.format(len(log_bundles), total)) 115 | -------------------------------------------------------------------------------- /cloudify_cli/commands/maintenance_mode.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | import time 18 | 19 | from cloudify_cli import exceptions 20 | from cloudify_cli.cli import cfy 21 | from cloudify_cli.logger import NO_VERBOSE, get_global_verbosity 22 | from cloudify_cli.table import print_data 23 | 24 | 25 | DEFAULT_TIMEOUT_INTERVAL = 5 26 | MAINTENANCE_MODE_ACTIVE = 'activated' 27 | EXECUTION_COLUMNS = ['id', 'deployment_id', 'workflow_id', 'status'] 28 | 29 | 30 | @cfy.group(name='maintenance-mode') 31 | @cfy.options.common_options 32 | @cfy.assert_manager_active() 33 | def maintenance_mode(): 34 | """Handle the manager's maintenance-mode 35 | """ 36 | pass 37 | 38 | 39 | @maintenance_mode.command(name='status', 40 | short_help='Show maintenance-mode status ' 41 | '[manager only]') 42 | @cfy.options.common_options 43 | @cfy.pass_client() 44 | @cfy.options.extended_view 45 | def status(client): 46 | """Retrieve the current maintenance-mode status. 47 | """ 48 | _print_maintenance_mode_status(client) 49 | 50 | 51 | @cfy.pass_logger 52 | def _print_maintenance_mode_status(client, logger): 53 | status_response = client.maintenance_mode.status() 54 | 55 | logger.info('\nMaintenance Mode Status:') 56 | for param_name, param_value in status_response.items(): 57 | if param_value and param_name != 'remaining_executions': 58 | logger.info('\t{0}:\t{1}'.format( 59 | param_name.title().replace("_", " "), 60 | param_value)) 61 | logger.info('') 62 | 63 | remaining_executions = status_response.remaining_executions 64 | if remaining_executions: 65 | if len(remaining_executions) == 1: 66 | logger.info( 67 | 'Cloudify Manager currently has one ' 68 | 'running or pending execution. Waiting for it' 69 | ' to finish before entering maintenance mode.') 70 | else: 71 | logger.info( 72 | 'Cloudify Manager currently has {0} ' 73 | 'running or pending executions. Waiting for all ' 74 | 'executions to finish before entering maintenance ' 75 | 'mode.'.format( 76 | len(remaining_executions))) 77 | 78 | if get_global_verbosity() != NO_VERBOSE: 79 | print_data( 80 | columns=EXECUTION_COLUMNS, 81 | items=remaining_executions, 82 | header_text='Remaining executions:', 83 | max_width=50 84 | ) 85 | 86 | if status_response.status == MAINTENANCE_MODE_ACTIVE: 87 | logger.info('INFO - Cloudify Manager is currently in maintenance ' 88 | 'mode. Most requests will be blocked.\n') 89 | 90 | 91 | @maintenance_mode.command(name='activate', 92 | short_help='Activate maintenance-mode ' 93 | '[manager only]') 94 | @cfy.options.wait 95 | @cfy.options.timeout(default=0) 96 | @cfy.options.common_options 97 | @cfy.pass_client() 98 | @cfy.pass_logger 99 | def activate(wait, timeout, logger, client): 100 | """Enter maintenance-mode on the manager rejecting further REST requests. 101 | """ 102 | 103 | if timeout and not wait: 104 | msg = "'--timeout' was used without '--wait'." 105 | error = exceptions.CloudifyCliError(msg) 106 | error.possible_solutions = [ 107 | "Add the '--wait' flag to the command in order to wait." 108 | ] 109 | raise error 110 | 111 | logger.info('Entering maintenance mode...') 112 | client.maintenance_mode.activate() 113 | 114 | if wait: 115 | logger.info("Cloudify manager will enter Maintenance mode once " 116 | "there are no running or pending executions...\n") 117 | deadline = time.time() + timeout 118 | 119 | while True: 120 | if _is_timeout(timeout, deadline): 121 | raise exceptions.CloudifyCliError( 122 | "Timed out while entering maintenance mode. " 123 | "Note that the manager is still entering maintenance mode" 124 | " in the background. You can run " 125 | "'cfy maintenance-mode status' to check the status.") 126 | 127 | status_response = client.maintenance_mode.status() 128 | if status_response.status == MAINTENANCE_MODE_ACTIVE: 129 | logger.info('Manager is in maintenance mode.') 130 | logger.info('While in maintenance mode most requests will ' 131 | 'be blocked.') 132 | return 133 | time.sleep(DEFAULT_TIMEOUT_INTERVAL) 134 | logger.info("Run 'cfy maintenance-mode status' to check the " 135 | "maintenance mode's status.\n") 136 | 137 | 138 | @maintenance_mode.command(name='deactivate', 139 | short_help='Deactivate maintenance-mode ' 140 | '[manager only]') 141 | @cfy.options.common_options 142 | @cfy.pass_client() 143 | @cfy.pass_logger 144 | def deactivate(logger, client): 145 | """Deactivate maintenance-mode on the manager to accept REST requests. 146 | """ 147 | logger.info('Turning off maintenance mode...') 148 | client.maintenance_mode.deactivate() 149 | logger.info('Maintenance mode is off.') 150 | 151 | 152 | def _is_timeout(timeout, deadline): 153 | return timeout > 0 and time.time() > deadline 154 | -------------------------------------------------------------------------------- /cloudify_cli/commands/permissions.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from cloudify_cli.cli import cfy 4 | from cloudify_cli.table import print_data 5 | 6 | 7 | @cfy.group(name='permissions') 8 | @cfy.options.common_options 9 | @cfy.assert_manager_active() 10 | def permissions(): 11 | pass 12 | 13 | 14 | @permissions.command(name='list') 15 | @click.option('--role', help='List permissions for this role') 16 | @cfy.options.common_options 17 | @cfy.pass_logger 18 | @cfy.pass_client() 19 | def list(role, logger, client): 20 | """List defined permissions.""" 21 | permissions = {} 22 | for permission in client.permissions.list(role=role): 23 | permissions.setdefault( 24 | permission['permission'], [] 25 | ).append(permission['role']) 26 | permissions = [{'permission': k, 'roles': v} 27 | for k, v in permissions.items()] 28 | print_data(['permission', 'roles'], permissions, 'Permissions:') 29 | 30 | 31 | @permissions.command(name='allow') 32 | @click.option('--role', help='Allow permission for this role') 33 | @click.option('--permission', help='Allow this permission') 34 | @cfy.options.common_options 35 | @cfy.pass_logger 36 | @cfy.pass_client() 37 | def allow(role, permission, logger, client): 38 | """Define a new permission.""" 39 | client.permissions.add(permission, role) 40 | logger.info('Allowed role %s the permission %s', role, permission) 41 | logger.warning('Updating manager permissions only takes effect after ' 42 | 'restarting the REST Service') 43 | 44 | 45 | @permissions.command(name='disallow') 46 | @click.option('--role', help='Disallow permission for this role') 47 | @click.option('--permission', help='Disallow this permission') 48 | @cfy.options.common_options 49 | @cfy.pass_logger 50 | @cfy.pass_client() 51 | def disallow(role, permission, logger, client): 52 | """Remove a defined permission.""" 53 | client.permissions.delete(permission, role) 54 | logger.info('Disallowed role %s the permission %s', role, permission) 55 | logger.warning('Updating manager permissions only takes effect after ' 56 | 'restarting the REST Service') 57 | -------------------------------------------------------------------------------- /cloudify_cli/commands/sites.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2013-2019 Cloudify Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | 18 | from cloudify_cli import env, utils 19 | from cloudify_cli.cli import cfy 20 | from cloudify_cli.table import print_data, print_single 21 | from cloudify_cli.utils import handle_client_error, validate_visibility 22 | 23 | SITES_COLUMNS = ['name', 'location', 'visibility', 'tenant_name', 24 | 'created_at', 'created_by'] 25 | 26 | 27 | @cfy.group(name='sites') 28 | @cfy.options.common_options 29 | def sites(): 30 | """ 31 | Handle Cloudify sites 32 | """ 33 | if not env.is_initialized(): 34 | env.raise_uninitialized() 35 | 36 | 37 | @sites.command(name='create', short_help='Create a new site') 38 | @cfy.argument('name', callback=cfy.validate_name) 39 | @cfy.options.location 40 | @cfy.options.visibility(mutually_exclusive_required=False) 41 | @cfy.options.tenant_name(required=False, resource_name_for_help='site') 42 | @cfy.options.common_options 43 | @cfy.assert_manager_active() 44 | @cfy.pass_client(use_tenant_in_header=True) 45 | @cfy.pass_logger 46 | def create(name, location, visibility, tenant_name, client, logger): 47 | """Create a new site 48 | 49 | `NAME` is the new site's name 50 | """ 51 | utils.explicit_tenant_name_message(tenant_name, logger) 52 | validate_visibility(visibility) 53 | client.sites.create(name, location, visibility) 54 | logger.info('Site `{0}` created'.format(name)) 55 | 56 | 57 | @sites.command(name='get', short_help='Get details for a single site') 58 | @cfy.argument('name', callback=cfy.validate_name) 59 | @cfy.options.tenant_name(required=False, resource_name_for_help='site') 60 | @cfy.options.common_options 61 | @cfy.assert_manager_active() 62 | @cfy.pass_client(use_tenant_in_header=True) 63 | @cfy.pass_logger 64 | @cfy.options.extended_view 65 | def get(name, tenant_name, client, logger): 66 | """Get details for a single site 67 | 68 | `NAME` is the site's name 69 | """ 70 | utils.explicit_tenant_name_message(tenant_name, logger) 71 | graceful_msg = 'Requested site with name `{0}` was not found in this ' \ 72 | 'tenant'.format(name) 73 | with handle_client_error(404, graceful_msg, logger): 74 | logger.info('Getting info for site `{0}`...'.format(name)) 75 | site_details = client.sites.get(name) 76 | print_single(SITES_COLUMNS, site_details, 'Requested site info:') 77 | 78 | 79 | @sites.command(name='update', short_help='Update an existing site') 80 | @cfy.argument('name', callback=cfy.validate_name) 81 | @cfy.options.location 82 | @cfy.options.update_visibility 83 | @cfy.options.new_name('site') 84 | @cfy.options.tenant_name(required=False, resource_name_for_help='site') 85 | @cfy.options.common_options 86 | @cfy.assert_manager_active() 87 | @cfy.pass_client(use_tenant_in_header=True) 88 | @cfy.pass_logger 89 | def update(name, location, visibility, new_name, tenant_name, client, logger): 90 | """Update an existing site 91 | 92 | `NAME` is the site's name 93 | """ 94 | utils.explicit_tenant_name_message(tenant_name, logger) 95 | validate_visibility(visibility) 96 | graceful_msg = 'Requested site with name `{0}` was not found'.format(name) 97 | with handle_client_error(404, graceful_msg, logger): 98 | client.sites.update(name, location, visibility, new_name) 99 | logger.info('Site `{0}` updated'.format(name)) 100 | 101 | 102 | @sites.command(name='list', short_help='List all sites') 103 | @cfy.options.sort_by('name') 104 | @cfy.options.descending 105 | @cfy.options.common_options 106 | @cfy.options.tenant_name_for_list(required=False, 107 | resource_name_for_help='site') 108 | @cfy.options.all_tenants 109 | @cfy.options.search 110 | @cfy.options.pagination_offset 111 | @cfy.options.pagination_size 112 | @cfy.assert_manager_active() 113 | @cfy.pass_client() 114 | @cfy.pass_logger 115 | @cfy.options.extended_view 116 | def list(sort_by, 117 | descending, 118 | tenant_name, 119 | all_tenants, 120 | search, 121 | pagination_offset, 122 | pagination_size, 123 | client, 124 | logger): 125 | """ 126 | List all sites 127 | """ 128 | utils.explicit_tenant_name_message(tenant_name, logger) 129 | logger.info('Listing all sites...') 130 | sites_list = client.sites.list( 131 | sort=sort_by, 132 | is_descending=descending, 133 | _all_tenants=all_tenants, 134 | _search=search, 135 | _offset=pagination_offset, 136 | _size=pagination_size 137 | ) 138 | print_data(SITES_COLUMNS, sites_list, 'Sites:') 139 | total = sites_list.metadata.pagination.total 140 | logger.info('Showing {0} of {1} sites'.format(len(sites_list), total)) 141 | 142 | 143 | @sites.command(name='delete', short_help='Delete a site') 144 | @cfy.argument('name', callback=cfy.validate_name) 145 | @cfy.options.tenant_name(required=False, resource_name_for_help='secret') 146 | @cfy.options.common_options 147 | @cfy.assert_manager_active() 148 | @cfy.pass_client() 149 | @cfy.pass_logger 150 | def delete(name, tenant_name, client, logger): 151 | """Delete a site 152 | 153 | `NAME` is the site's name 154 | """ 155 | utils.explicit_tenant_name_message(tenant_name, logger) 156 | graceful_msg = 'Requested site with name `{0}` was not found'.format(name) 157 | with handle_client_error(404, graceful_msg, logger): 158 | logger.info('Deleting site `{0}`...'.format(name)) 159 | client.sites.delete(name) 160 | logger.info('Site deleted') 161 | -------------------------------------------------------------------------------- /cloudify_cli/commands/status.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014-2019 Cloudify Platform Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | import json 18 | 19 | from cloudify_rest_client.exceptions import CloudifyClientError, \ 20 | UserUnauthorizedError 21 | 22 | from cloudify_cli.cli import cfy 23 | from cloudify_cli.env import profile 24 | from cloudify_cli.logger import ( 25 | CloudifyJSONEncoder, 26 | output, 27 | get_global_json_output) 28 | from cloudify_cli.table import print_data 29 | 30 | STATUS_COLUMNS = ['service', 'status'] 31 | 32 | 33 | @cfy.command(name='status', short_help="Show manager status [manager only]") 34 | @cfy.options.common_options 35 | @cfy.assert_manager_active() 36 | @cfy.pass_client() 37 | @cfy.pass_logger 38 | def status(logger, client): 39 | """Show the status of the manager""" 40 | rest_host = profile.manager_ip 41 | logger.info('Retrieving manager services status... [ip={0}]'.format( 42 | rest_host)) 43 | try: 44 | status_result = client.manager.get_status() 45 | maintenance_response = client.maintenance_mode.status() 46 | except UserUnauthorizedError: 47 | logger.info( 48 | 'Failed to query manager service status: User is unauthorized') 49 | return False 50 | except CloudifyClientError: 51 | logger.info('REST service at manager {0} is not responding!'.format( 52 | rest_host)) 53 | return False 54 | 55 | if get_global_json_output(): 56 | output(json.dumps(status_result, cls=CloudifyJSONEncoder)) 57 | else: 58 | services = [] 59 | for display_name, service in status_result['services'].items(): 60 | services.append({ 61 | 'service': display_name.ljust(30), 62 | 'status': service.get('status') 63 | }) 64 | print_data(STATUS_COLUMNS, services, 'Services:') 65 | 66 | maintenance_status = maintenance_response.status 67 | if maintenance_status != 'deactivated': 68 | logger.info('Maintenance mode is {0}.\n'.format( 69 | maintenance_response.status)) 70 | return True 71 | -------------------------------------------------------------------------------- /cloudify_cli/commands/summary.py: -------------------------------------------------------------------------------- 1 | from cloudify_cli.logger import get_global_json_output 2 | 3 | 4 | BASE_SUMMARY_FIELDS = [ 5 | 'tenant_name', 6 | 'visibility', 7 | ] 8 | 9 | 10 | def structure_summary_results(results, target_field, sub_field, 11 | summary_type): 12 | """Restructure the results returned from the rest client. 13 | 14 | This is needed in case sub-fields are provided, as sub-fields will result 15 | in output that looks like: 16 | [ 17 | { 18 | "": "", 19 | "": , 20 | "by ": [ 21 | { 22 | "": "", 23 | "": , 24 | }, 25 | ... more sub-field results for this value of target_field ... 26 | ], 27 | }, 28 | ... more results ... 29 | ] 30 | 31 | For compatibility with the CLI output tools, we want to turn this into: 32 | [ 33 | { 34 | "": "", 35 | "": "", 36 | "": , 37 | }, 38 | ... more sub-field results for this value of target_field ... 39 | { 40 | "": "", 41 | "": "", 42 | "": , 43 | }, 44 | ... sub-fields followed by totals for other target_field values ... 45 | ] 46 | """ 47 | if sub_field: 48 | columns = [target_field, sub_field, summary_type] 49 | structured_result = [] 50 | for result in results: 51 | for sub_result in result['by ' + sub_field]: 52 | structured_result.append( 53 | { 54 | target_field: result[target_field], 55 | sub_field: sub_result[sub_field], 56 | summary_type: sub_result[summary_type], 57 | } 58 | ) 59 | structured_result.append( 60 | { 61 | target_field: result[target_field], 62 | sub_field: None if get_global_json_output() else 'TOTAL', 63 | summary_type: result[summary_type], 64 | } 65 | ) 66 | else: 67 | columns = [target_field, summary_type] 68 | structured_result = results 69 | return columns, structured_result 70 | -------------------------------------------------------------------------------- /cloudify_cli/commands/tokens.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from cloudify_cli.cli import cfy 4 | from cloudify_cli.exceptions import CloudifyCliError 5 | from cloudify_cli.table import print_single, print_data 6 | 7 | REST_TOKEN_COLUMNS = ['id', 'role', 'description', 8 | 'expiration_date', 'last_used'] 9 | 10 | 11 | @cfy.group(name='tokens') 12 | @cfy.assert_manager_active() 13 | def tokens(): 14 | """Handle tokens on the manager""" 15 | 16 | 17 | @tokens.command( 18 | name='create', 19 | short_help='Create a token for this user on the Cloudify Manager') 20 | @cfy.assert_manager_active() 21 | @cfy.options.common_options 22 | @click.option('-e', '--expiry', 23 | help="Token expiration, e.g. +10h or 2121-03-09 14:52. " 24 | "Absolute times are considered to be in UTC.") 25 | @click.option('-d', '--description', help="Token description") 26 | @cfy.pass_client() 27 | @cfy.pass_logger 28 | def create(logger, client, description, expiry): 29 | logger.info('Listing REST tokens') 30 | token = client.tokens.create(expiration=expiry, description=description) 31 | columns = REST_TOKEN_COLUMNS + ['value'] 32 | print_single(columns, token, 'REST token') 33 | 34 | 35 | @tokens.command( 36 | name='list', 37 | short_help='Lists tokens from the Cloudify Manager') 38 | @cfy.assert_manager_active() 39 | @cfy.options.common_options 40 | @cfy.pass_client() 41 | @cfy.pass_logger 42 | def list(logger, client): 43 | logger.info('Listing REST tokens') 44 | tokens = client.tokens.list() 45 | columns = REST_TOKEN_COLUMNS 46 | # An admin listing tokens will see tokens for other users 47 | if any(token.username != tokens[0].username 48 | for token in tokens): 49 | columns = columns + ['username'] 50 | print_data( 51 | columns, 52 | tokens, 53 | 'Token listing', 54 | ) 55 | total = tokens.metadata.pagination.total 56 | logger.info('Showing %s of %s tokens', len(tokens), total) 57 | 58 | 59 | @tokens.command( 60 | name='get', 61 | short_help='Get details of a REST token from the Cloudify Manager.') 62 | @cfy.assert_manager_active() 63 | @cfy.options.common_options 64 | @cfy.argument('token_id', type=str, default=None, required=False) 65 | @cfy.pass_client() 66 | @cfy.pass_logger 67 | def get(logger, client, token_id): 68 | if token_id is None: 69 | # Give a helpful error because of the back compat break 70 | raise CloudifyCliError( 71 | 'Token ID must now be provided to get a token.\n' 72 | 'For old `cfy tokens get` functionality, use `cfy tokens create`.' 73 | ) 74 | logger.info('Retrieving REST token') 75 | token = client.tokens.get(token_id) 76 | print_single(REST_TOKEN_COLUMNS, token, 'REST token') 77 | 78 | 79 | @tokens.command( 80 | name='delete', 81 | short_help='Delete a REST token from the Cloudify Manager, disabling it.') 82 | @cfy.assert_manager_active() 83 | @cfy.options.common_options 84 | @cfy.argument('token_id', type=str, default=None, required=True) 85 | @cfy.pass_client() 86 | @cfy.pass_logger 87 | def delete(logger, client, token_id): 88 | logger.info('Deleting REST token %s', token_id) 89 | client.tokens.delete(token_id) 90 | -------------------------------------------------------------------------------- /cloudify_cli/commands/uninstall.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | from cloudify_cli import env 18 | from cloudify_cli.cli import cfy, helptexts 19 | from cloudify_cli.constants import DEFAULT_UNINSTALL_WORKFLOW 20 | from cloudify_cli.local import remove as local_remove 21 | from cloudify_cli.commands import blueprints, executions, deployments 22 | 23 | 24 | @cfy.command(name='uninstall', 25 | short_help='Uninstall an application blueprint [manager only]') 26 | @cfy.argument('deployment-id') 27 | @cfy.options.workflow_id('uninstall') 28 | @cfy.options.force(help=helptexts.FORCE_CONCURRENT_EXECUTION) 29 | @cfy.options.recursive_delete 30 | @cfy.options.parameters 31 | @cfy.options.allow_custom_parameters 32 | @cfy.options.timeout() 33 | @cfy.options.include_logs 34 | @cfy.options.json_output 35 | @cfy.options.common_options 36 | @cfy.options.tenant_name(required=False, 37 | resource_name_for_help='blueprint and deployment') 38 | @cfy.pass_context 39 | def manager(ctx, 40 | deployment_id, 41 | workflow_id, 42 | force, 43 | recursive, 44 | parameters, 45 | allow_custom_parameters, 46 | timeout, 47 | include_logs, 48 | json_output, 49 | tenant_name): 50 | """Uninstall an application via the manager 51 | 52 | This will execute the `uninstall` workflow, delete the deployment and 53 | delete the blueprint (if there is only one deployment for that blueprint). 54 | 55 | `DEPLOYMENT_ID` is the id of the deployment to uninstall. 56 | """ 57 | env.assert_manager_active() 58 | 59 | # if no workflow was supplied, execute the `uninstall` workflow 60 | workflow_id = workflow_id or DEFAULT_UNINSTALL_WORKFLOW 61 | 62 | if recursive: 63 | parameters['recursive'] = True 64 | ctx.invoke( 65 | executions.manager_start, 66 | workflow_id=workflow_id, 67 | deployment_id=deployment_id, 68 | timeout=timeout, 69 | force=force, 70 | allow_custom_parameters=allow_custom_parameters, 71 | include_logs=include_logs, 72 | parameters=parameters, 73 | json_output=json_output, 74 | tenant_name=tenant_name) 75 | 76 | # before deleting the deployment, save its blueprint_id, so we will be able 77 | # to delete the blueprint after deleting the deployment 78 | client = env.get_rest_client(tenant_name=tenant_name) 79 | deployment = client.deployments.get( 80 | deployment_id, _include=['blueprint_id']) 81 | blueprint_id = deployment.blueprint_id 82 | ctx.invoke( 83 | deployments.manager_delete, 84 | deployment_id=deployment_id, 85 | force=force, 86 | tenant_name=tenant_name, 87 | recursive=recursive, 88 | ) 89 | ctx.invoke( 90 | blueprints.delete, 91 | blueprint_id=blueprint_id, 92 | force=force, 93 | tenant_name=tenant_name) 94 | 95 | 96 | @cfy.command(name='uninstall', 97 | short_help='Uninstall an application blueprint') 98 | @cfy.options.workflow_id('uninstall') 99 | @cfy.options.blueprint_id(required=True) 100 | @cfy.options.parameters 101 | @cfy.options.allow_custom_parameters 102 | @cfy.options.task_retries() 103 | @cfy.options.task_retry_interval() 104 | @cfy.options.task_thread_pool_size() 105 | @cfy.options.common_options 106 | @cfy.pass_context 107 | def local(ctx, 108 | workflow_id, 109 | blueprint_id, 110 | parameters, 111 | allow_custom_parameters, 112 | task_retries, 113 | task_retry_interval, 114 | task_thread_pool_size): 115 | """Uninstall an application 116 | """ 117 | workflow_id = workflow_id or DEFAULT_UNINSTALL_WORKFLOW 118 | 119 | ctx.invoke( 120 | executions.local_start, 121 | blueprint_id=blueprint_id, 122 | workflow_id=workflow_id, 123 | parameters=parameters, 124 | allow_custom_parameters=allow_custom_parameters, 125 | task_retries=task_retries, 126 | task_retry_interval=task_retry_interval, 127 | task_thread_pool_size=task_thread_pool_size) 128 | 129 | local_remove(blueprint_id) 130 | -------------------------------------------------------------------------------- /cloudify_cli/commands/workflows.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | import click 18 | from cloudify_rest_client.exceptions import CloudifyClientError 19 | 20 | from cloudify_cli import utils 21 | from cloudify_cli.cli import cfy 22 | from cloudify_cli.exceptions import CloudifyCliError 23 | from cloudify_cli.logger import get_global_json_output 24 | from cloudify_cli.table import print_data, print_single 25 | 26 | WORKFLOW_COLUMNS = ['blueprint_id', 'deployment_id', 'name', 27 | 'availability_rules'] 28 | 29 | 30 | @cfy.group(name='workflows') 31 | @cfy.assert_manager_active() 32 | def workflows(): 33 | """Handle deployment workflows 34 | """ 35 | pass 36 | 37 | 38 | @workflows.command(name='get', 39 | short_help='Retrieve workflow information [manager only]') 40 | @cfy.argument('workflow-id') 41 | @cfy.options.deployment_id(required=True) 42 | @cfy.options.common_options 43 | @cfy.options.tenant_name(required=False, resource_name_for_help='deployment') 44 | @cfy.pass_logger 45 | @cfy.pass_client() 46 | @cfy.options.extended_view 47 | def get(workflow_id, deployment_id, logger, client, tenant_name): 48 | """Retrieve information for a specific workflow of a specific deployment 49 | 50 | `WORKFLOW_ID` is the id of the workflow to get information on. 51 | """ 52 | utils.explicit_tenant_name_message(tenant_name, logger) 53 | try: 54 | logger.info('Retrieving workflow {0} for deployment {1}'.format( 55 | workflow_id, deployment_id)) 56 | deployment = client.deployments.get(deployment_id) 57 | workflow = next((wf for wf in deployment.workflows if 58 | wf.name == workflow_id), None) 59 | if not workflow: 60 | raise CloudifyCliError( 61 | 'Workflow {0} of deployment {1} not found' 62 | .format(workflow_id, deployment_id)) 63 | except CloudifyClientError as e: 64 | if e.status_code != 404: 65 | raise 66 | raise CloudifyCliError('Deployment {0} not found'.format( 67 | deployment_id)) 68 | 69 | defaults = { 70 | 'blueprint_id': deployment.blueprint_id, 71 | 'deployment_id': deployment.id 72 | } 73 | columns = WORKFLOW_COLUMNS 74 | 75 | if get_global_json_output(): 76 | columns += ['parameters'] 77 | print_single(columns, workflow, 'Workflows:', defaults=defaults) 78 | 79 | if not get_global_json_output(): 80 | # print workflow parameters 81 | mandatory_params = dict() 82 | optional_params = dict() 83 | for param_name, param in workflow.parameters.items(): 84 | params_group = optional_params if 'default' in param else \ 85 | mandatory_params 86 | params_group[param_name] = param 87 | 88 | logger.info('Workflow Parameters:') 89 | logger.info('\tMandatory Parameters:') 90 | for param_name, param in mandatory_params.items(): 91 | if 'description' in param: 92 | logger.info('\t\t{0}\t({1})'.format(param_name, 93 | param['description'])) 94 | else: 95 | logger.info('\t\t{0}'.format(param_name)) 96 | 97 | logger.info('\tOptional Parameters:') 98 | for param_name, param in optional_params.items(): 99 | if 'description' in param: 100 | logger.info('\t\t{0}: \t{1}\t({2})'.format( 101 | param_name, param['default'], param['description'])) 102 | else: 103 | logger.info('\t\t{0}: \t{1}'.format(param_name, 104 | param['default'])) 105 | logger.info('') 106 | 107 | 108 | def _format_workflow(wf): 109 | if wf.get('availability_rules'): 110 | wf['availability_rules'] = ', '.join(wf['availability_rules'].keys()) 111 | return wf 112 | 113 | 114 | @workflows.command(name='list', 115 | short_help='List workflows for a deployment [manager only]') 116 | @cfy.options.deployment_id(required=True) 117 | @cfy.options.common_options 118 | @click.option('--all', 'all_workflows', is_flag=True, 119 | help='Also show unavailable workflows') 120 | @cfy.options.tenant_name(required=False, resource_name_for_help='deployment') 121 | @cfy.pass_logger 122 | @cfy.pass_client() 123 | @cfy.options.extended_view 124 | def list(deployment_id, all_workflows, logger, client, tenant_name): 125 | """List all workflows on the manager for a specific deployment 126 | """ 127 | utils.explicit_tenant_name_message(tenant_name, logger) 128 | logger.info('Listing workflows for deployment %s...', deployment_id) 129 | deployment = client.deployments.get(deployment_id) 130 | 131 | workflows = sorted(deployment.workflows, key=lambda w: w.name) 132 | columns = WORKFLOW_COLUMNS 133 | hidden_count = 0 134 | if not all_workflows: 135 | total_count = len(workflows) 136 | workflows = [wf for wf in workflows if wf.is_available] 137 | hidden_count = total_count - len(workflows) 138 | else: 139 | columns = columns + ['is_available'] 140 | 141 | defaults = { 142 | 'blueprint_id': deployment.blueprint_id, 143 | 'deployment_id': deployment.id 144 | } 145 | if not get_global_json_output(): 146 | workflows = [_format_workflow(wf) for wf in workflows] 147 | print_data(columns, workflows, 'Workflows:', defaults=defaults) 148 | if hidden_count: 149 | logger.info('%d unavailable workflows hidden (use --all to show)', 150 | hidden_count) 151 | -------------------------------------------------------------------------------- /cloudify_cli/config/__init__.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | -------------------------------------------------------------------------------- /cloudify_cli/config/config.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2018 Cloudify Platform Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | 18 | import os 19 | import yaml 20 | 21 | from dsl_parser import utils as dsl_parser_utils 22 | from dsl_parser.constants import IMPORT_RESOLVER_KEY 23 | from dsl_parser.import_resolver.default_import_resolver import ( 24 | DefaultImportResolver 25 | ) 26 | 27 | from cloudify_cli import env, exceptions 28 | 29 | 30 | CLOUDIFY_CONFIG_PATH = os.path.join(env.CLOUDIFY_WORKDIR, 'config.yaml') 31 | 32 | 33 | class CloudifyConfig(object): 34 | 35 | class Logging(object): 36 | 37 | def __init__(self, logging): 38 | self._logging = logging or {} 39 | 40 | @property 41 | def filename(self): 42 | return self._logging.get('filename') 43 | 44 | @property 45 | def loggers(self): 46 | return self._logging.get('loggers', {}) 47 | 48 | def __init__(self): 49 | with open(CLOUDIFY_CONFIG_PATH) as f: 50 | self._config = yaml.safe_load(f.read()) 51 | 52 | @property 53 | def colors(self): 54 | return self._config.get('colors', False) 55 | 56 | @property 57 | def auto_generate_ids(self): 58 | return self._config.get('auto_generate_ids', False) 59 | 60 | @property 61 | def logging(self): 62 | return self.Logging(self._config.get('logging', {})) 63 | 64 | @property 65 | def local_provider_context(self): 66 | return self._config.get('local_provider_context', {}) 67 | 68 | @property 69 | def local_import_resolver(self): 70 | return self._config.get(IMPORT_RESOLVER_KEY, {}) 71 | 72 | @property 73 | def validate_definitions_version(self): 74 | return self._config.get('validate_definitions_version', True) 75 | 76 | 77 | def is_use_colors(): 78 | if not env.is_initialized(): 79 | return False 80 | 81 | config = CloudifyConfig() 82 | return config.colors 83 | 84 | 85 | def is_auto_generate_ids(): 86 | if not env.is_initialized(): 87 | return False 88 | 89 | config = CloudifyConfig() 90 | return config.auto_generate_ids 91 | 92 | 93 | def get_import_resolver(): 94 | local_import_resolver = { 95 | 'implementation': 96 | 'cloudify_cli.config.config:ResolverWithCatalogIdentification' 97 | } 98 | if env.is_initialized(): 99 | config = CloudifyConfig() 100 | # get the resolver configuration from the config file 101 | if isinstance(config.local_import_resolver, dict): 102 | local_import_resolver.update(config.local_import_resolver) 103 | return dsl_parser_utils.create_import_resolver(local_import_resolver) 104 | 105 | 106 | def is_validate_definitions_version(): 107 | if not env.is_initialized(): 108 | return True 109 | config = CloudifyConfig() 110 | return config.validate_definitions_version 111 | 112 | 113 | class ResolverWithCatalogIdentification(DefaultImportResolver): 114 | """ 115 | All catalog resources (blueprints, plugin) can only be validated 116 | in the manager not via the CLI, so this resolver only supports not 117 | catalog-style urls. 118 | """ 119 | CATALOG_RESOURCES_PREFIX = ('plugin:', 'blueprint:') 120 | 121 | def fetch_import(self, import_url, **kwargs): 122 | if self._is_cloudify_repository_url(import_url): 123 | e = exceptions.CloudifyCliError( 124 | 'Error fetching remote resource yaml: {0!r}\nBlueprints using ' 125 | 'Cloudify repository imports can not be validated locally.' 126 | .format(import_url)) 127 | e.possible_solutions = [ 128 | 'Upload the blueprint/plugin to the Cloudify Manager', 129 | 'In case of a missing plugin, use an explicit URL ' 130 | 'to the plugin YAML file instead of a plugin ' 131 | 'repository `plugin:` import' 132 | ] 133 | raise e 134 | return super(ResolverWithCatalogIdentification, self)\ 135 | .fetch_import(import_url, **kwargs) 136 | 137 | def _is_cloudify_repository_url(self, import_url): 138 | return import_url.startswith(self.CATALOG_RESOURCES_PREFIX) 139 | -------------------------------------------------------------------------------- /cloudify_cli/constants.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | CLOUDIFY_BASE_DIRECTORY_NAME = '.cloudify' 18 | CONFIG_FILE_NAME = 'cloudify-config.yaml' 19 | DEFAULTS_CONFIG_FILE_NAME = 'cloudify-config.defaults.yaml' 20 | DEFAULT_BLUEPRINT_FILE_NAME = 'blueprint.yaml' 21 | DEFAULT_BLUEPRINT_PATH = 'blueprint.yaml' 22 | DEFAULT_PARAMETERS = {} 23 | DEFAULT_TIMEOUT = 900 24 | DEFAULT_INSTALL_WORKFLOW = 'install' 25 | DEFAULT_UNINSTALL_WORKFLOW = 'uninstall' 26 | 27 | AGENT_MIN_WORKERS = 2 28 | AGENT_MAX_WORKERS = 5 29 | AGENT_KEY_PATH = '~/.ssh/cloudify-agents-kp.pem' 30 | AGENT_REMOTE_KEY_PATH = '~/.ssh/agent_key.pem' 31 | REMOTE_EXECUTION_PORT = 22 32 | 33 | POLICY_ENGINE_START_TIMEOUT = 30 34 | 35 | DEFAULT_REST_PORT = 80 36 | SECURED_REST_PORT = 443 37 | DEFAULT_REST_PROTOCOL = 'http' 38 | SECURED_REST_PROTOCOL = 'https' 39 | 40 | REST_PORT_RUNTIME_PROPERTY = 'external_rest_port' 41 | REST_PROTOCOL_RUNTIME_PROPERTY = 'external_rest_protocol' 42 | 43 | CLOUDIFY_PACKAGES_PATH = '/cloudify' 44 | CLOUDIFY_COMPONENTS_PACKAGE_PATH = '/cloudify-components' 45 | CLOUDIFY_CORE_PACKAGE_PATH = '/cloudify-core' 46 | CLOUDIFY_UI_PACKAGE_PATH = '/cloudify-ui' 47 | CLOUDIFY_AGENT_PACKAGE_PATH = '/cloudify-agents' 48 | 49 | CLOUDIFY_REST_CLIENT_LOGGER_NAME = 'cloudify.rest_client.http' 50 | 51 | IGNORED_LOCAL_WORKFLOW_MODULES = ( 52 | 'cloudify_agent.operations', 53 | 'cloudify_agent.installer.operations', 54 | 55 | # maintained for backward compatibility with < 3.3 blueprints 56 | 'worker_installer.tasks', 57 | 'plugin_installer.tasks', 58 | 'windows_agent_installer.tasks', 59 | 'windows_plugin_installer.tasks', 60 | ) 61 | 62 | CLOUDIFY_AUTHENTICATION_HEADER = 'Authorization' 63 | CLOUDIFY_TENANT_HEADER = 'Tenant' 64 | CLOUDIFY_USERNAME_ENV = 'CLOUDIFY_USERNAME' 65 | CLOUDIFY_PASSWORD_ENV = 'CLOUDIFY_PASSWORD' 66 | CLOUDIFY_TENANT_ENV = 'CLOUDIFY_TENANT' 67 | DEFAULT_TENANT_NAME = 'default_tenant' 68 | 69 | PUBLIC_REST_CERT = 'public_rest_cert.crt' 70 | LOCAL_REST_CERT_FILE = 'LOCAL_REST_CERT_FILE' 71 | CLOUDIFY_SSL_TRUST_ALL = 'CLOUDIFY_SSL_TRUST_ALL' 72 | 73 | SSL_ENABLED_PROPERTY_NAME = 'enabled' 74 | SSL_CERTIFICATE_PATH_PROPERTY_NAME = 'certificate_path' 75 | SSL_PRIVATE_KEY_PROPERTY_NAME = 'private_key_path' 76 | 77 | BASIC_AUTH_PREFIX = 'Basic' 78 | 79 | API_VERSION = 'v3.1' 80 | 81 | HELP_TEXT_COLUMN_BUFFER = 5 82 | 83 | SUPPORTED_ARCHIVE_TYPES = ('zip', 'tar', 'tar.gz', 'tar.bz2') 84 | 85 | DELETE_DEP = 'delete_deployment_environment' 86 | CREATE_DEPLOYMENT = 'create_deployment_environment' 87 | -------------------------------------------------------------------------------- /cloudify_cli/exceptions.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | 18 | class CloudifyCliError(Exception): 19 | pass 20 | 21 | 22 | class CloudifyBootstrapError(Exception): 23 | pass 24 | 25 | 26 | class CloudifyValidationError(Exception): 27 | pass 28 | 29 | 30 | class SuppressedCloudifyCliError(Exception): 31 | pass 32 | 33 | 34 | class CloudifyTimeoutError(Exception): 35 | pass 36 | 37 | 38 | class ExecutionTimeoutError(RuntimeError): 39 | 40 | def __init__(self, execution_id, message): 41 | self.execution_id = execution_id 42 | self.message = message 43 | 44 | 45 | class EventProcessingTimeoutError(RuntimeError): 46 | 47 | def __init__(self, execution_id, message): 48 | self.execution_id = execution_id 49 | self.message = message 50 | 51 | 52 | class LabelsValidationError(CloudifyValidationError): 53 | def __init__(self, err_label, err_reason): 54 | super(LabelsValidationError, self).__init__( 55 | 'ERROR: The label `{0}` is invalid. ' 56 | '{1}'.format(err_label, err_reason) 57 | ) 58 | -------------------------------------------------------------------------------- /cloudify_cli/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/__init__.py -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/__init__.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/constants.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | import os 17 | 18 | from cloudify_cli.constants import DEFAULT_BLUEPRINT_FILE_NAME 19 | 20 | 21 | STUB_TIMEOUT = 900 22 | STUB_FORCE = False 23 | STUB_INCLUDE_LOGS = False 24 | STUB_WORKFLOW = 'my_workflow' 25 | STUB_PARAMETERS = 'key=value' 26 | STUB_BLUEPRINT_ID = 'blueprint_id' 27 | STUB_DIRECTORY_NAME = 'helloworld' 28 | STUB_DEPLOYMENT_ID = 'deployment_id' 29 | STUB_ALLOW_CUSTOM_PARAMETERS = False 30 | STUB_ARCHIVE_LOCATION = 'archive.zip' 31 | STUB_BLUEPRINT_FILENAME = 'my_blueprint.yaml' 32 | SSL_PORT = '443' 33 | THIS_DIR = os.path.dirname(os.path.dirname(__file__)) 34 | RESOURCES_DIR = os.path.join(THIS_DIR, 'resources') 35 | BLUEPRINTS_DIR = os.path.join(RESOURCES_DIR, 'blueprints') 36 | SNAPSHOTS_DIR = os.path.join(RESOURCES_DIR, 'snapshots') 37 | PLUGINS_DIR = os.path.join(RESOURCES_DIR, 'plugins') 38 | OLD_CONTEXT_PATH = os.path.join(RESOURCES_DIR, 'profile.yaml') 39 | SAMPLE_INPUTS_PATH = os.path.join( 40 | BLUEPRINTS_DIR, STUB_DIRECTORY_NAME, 'inputs.yaml') 41 | SAMPLE_BLUEPRINT_PATH = os.path.join( 42 | BLUEPRINTS_DIR, STUB_DIRECTORY_NAME, DEFAULT_BLUEPRINT_FILE_NAME) 43 | SAMPLE_ARCHIVE_URL = 'https://github.com/cloudify-cosmo/' \ 44 | 'cloudify-hello-world-example/archive/master.zip' 45 | SAMPLE_ARCHIVE_PATH = os.path.join(BLUEPRINTS_DIR, 'helloworld.zip') 46 | SAMPLE_CUSTOM_NAME_ARCHIVE = os.path.join( 47 | BLUEPRINTS_DIR, 48 | 'helloworld_custom_name.zip' 49 | ) 50 | TEST_LABELS = 'label_key:label_value' 51 | UPDATED_BLUEPRINT_ID = 'updated' 52 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_groups.py: -------------------------------------------------------------------------------- 1 | from mock import MagicMock 2 | 3 | from .test_base import CliCommandTest 4 | 5 | from cloudify_rest_client import deployments 6 | from cloudify_rest_client.exceptions import CloudifyClientError 7 | 8 | 9 | class GroupsTest(CliCommandTest): 10 | 11 | def setUp(self): 12 | super(GroupsTest, self).setUp() 13 | self.use_manager() 14 | 15 | def test_groups_list(self): 16 | deployment = deployments.Deployment({ 17 | 'blueprint_id': 'mock_blueprint_id', 18 | 'groups': { 19 | 'group1': { 20 | 'members': ['node1', 'node2'], 21 | 'policies': { 22 | 'policy1': { 23 | 'type': 'cloudify.policies.threshold' 24 | } 25 | } 26 | }, 27 | 'group2': { 28 | 'members': ['node1', 'node2'], 29 | 'policies': { 30 | 'policy2': { 31 | 'type': 'cloudify.policies.host_failure' 32 | } 33 | } 34 | }, 35 | 'group3': { 36 | 'members': ['group1', 'node3'] 37 | } 38 | }, 39 | 'scaling_groups': { 40 | 'group2': { 41 | 'members': ['node1', 'node2'], 42 | 'properties': { 43 | 44 | } 45 | }, 46 | 'group3': { 47 | 'members': ['group1', 'node3'], 48 | 'properties': { 49 | 50 | } 51 | } 52 | } 53 | }) 54 | self.client.deployments.get = MagicMock(return_value=deployment) 55 | self.invoke('cfy groups list -d a-deployment-id') 56 | 57 | def test_groups_sort_list(self): 58 | deployment = deployments.Deployment({ 59 | 'blueprint_id': 'mock_blueprint_id', 60 | 'groups': { 61 | 'group2': { 62 | 'members': ['node1', 'node2'], 63 | 'policies': { 64 | 'policy1': { 65 | 'type': 'cloudify.policies.threshold' 66 | } 67 | } 68 | }, 69 | 'group3': { 70 | 'members': ['node1', 'node2'], 71 | 'policies': { 72 | 'policy2': { 73 | 'type': 'cloudify.policies.host_failure' 74 | } 75 | } 76 | }, 77 | 'group1': { 78 | 'members': ['node2', 'node3'] 79 | } 80 | } 81 | }) 82 | self.client.deployments.get = MagicMock(return_value=deployment) 83 | output = self.invoke('cfy groups list -d a-deployment-id').logs 84 | first = output.find('group1') 85 | second = output.find('group2') 86 | third = output.find('group3') 87 | self.assertTrue(0 < first < second < third) 88 | 89 | def test_groups_list_nonexistent_deployment(self): 90 | expected_message = 'Deployment nonexistent-dep not found' 91 | error = CloudifyClientError('') 92 | error.status_code = 404 93 | self.client.deployments.get = MagicMock(side_effect=error) 94 | self.invoke("cfy groups list -d nonexistent-dep", expected_message) 95 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_ldap.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | from .test_base import CliCommandTest 17 | from mock import MagicMock 18 | 19 | 20 | class LdapTest(CliCommandTest): 21 | 22 | def setUp(self): 23 | super(LdapTest, self).setUp() 24 | self.use_manager() 25 | 26 | def test_ldap_set(self): 27 | self.client.ldap.set = MagicMock(return_value='') 28 | self.invoke('ldap set -s server -u user -p pass -d name -a ' 29 | '-e extra') 30 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_list_sort.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | from .mocks import MockListResponse 4 | from .test_base import CliCommandTest 5 | 6 | 7 | class ListSortTest(CliCommandTest): 8 | _resource = namedtuple('Resource', 'name,class_type,sort_order,context') 9 | 10 | def setUp(self): 11 | super(ListSortTest, self).setUp() 12 | self.use_manager() 13 | self.resources = [ 14 | ListSortTest._resource( 15 | 'plugins', 16 | self.client.plugins, 17 | 'uploaded_at', 18 | None 19 | ), 20 | ListSortTest._resource( 21 | 'deployments', 22 | self.client.deployments, 23 | 'created_at', 24 | None 25 | ), 26 | ListSortTest._resource( 27 | 'nodes', 28 | self.client.nodes, 29 | 'deployment_id', 30 | None 31 | ), 32 | ListSortTest._resource( 33 | 'node-instances', 34 | self.client.node_instances, 35 | 'node_id', 36 | 'node_instances' 37 | ), 38 | ListSortTest._resource( 39 | 'blueprints', 40 | self.client.blueprints, 41 | 'created_at', 42 | None 43 | ), 44 | ListSortTest._resource( 45 | 'snapshots', 46 | self.client.snapshots, 47 | 'created_at', 48 | None 49 | ), 50 | ListSortTest._resource( 51 | 'executions', 52 | self.client.executions, 53 | 'created_at', 54 | None 55 | ), 56 | ListSortTest._resource( 57 | 'users', 58 | self.client.users, 59 | 'username', 60 | None 61 | ), 62 | ListSortTest._resource( 63 | 'user-groups', 64 | self.client.user_groups, 65 | 'name', 66 | None 67 | ), 68 | ] 69 | 70 | self.count_mock_calls = 0 71 | 72 | self.original_lists = {} 73 | for r in self.resources: 74 | self.original_lists[r.name] = r.class_type.list 75 | 76 | def tearDown(self): 77 | for r in self.resources: 78 | r.class_type.list = self.original_lists[r.name] 79 | super(ListSortTest, self).tearDown() 80 | 81 | def test_list_sort(self): 82 | for r in self.resources: 83 | self._set_mock_list(r, 'order') 84 | self.invoke( 85 | 'cfy {0} list --sort-by order' 86 | .format(r.name), context=r.context 87 | ) 88 | self.assertEqual(len(self.resources), self.count_mock_calls) 89 | 90 | def test_list_sort_reverse(self): 91 | for r in self.resources: 92 | self._set_mock_list(r, 'order', descending=True) 93 | self.invoke( 94 | 'cfy {0} list --sort-by order --descending' 95 | .format(r.name), context=r.context 96 | ) 97 | self.assertEqual(len(self.resources), self.count_mock_calls) 98 | 99 | def test_list_sort_default(self): 100 | for r in self.resources: 101 | self._set_mock_list(r, r.sort_order) 102 | self.invoke('cfy {0} list'.format(r.name), context=r.context) 103 | self.assertEqual(len(self.resources), self.count_mock_calls) 104 | 105 | def test_list_sort_default_reverse(self): 106 | for r in self.resources: 107 | self._set_mock_list(r, r.sort_order, descending=True) 108 | self.invoke('cfy {0} list --descending' 109 | .format(r.name), context=r.context) 110 | self.assertEqual(len(self.resources), self.count_mock_calls) 111 | 112 | def _set_mock_list(self, resource, sort, descending=False): 113 | def _mock_list(*_, **kwargs): 114 | self.count_mock_calls += 1 115 | self.assertEqual(sort, kwargs['sort']) 116 | self.assertEqual(descending, kwargs['is_descending']) 117 | return MockListResponse() 118 | 119 | resource.class_type.list = _mock_list 120 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_maintenance_mode.py: -------------------------------------------------------------------------------- 1 | from mock import MagicMock, patch, call 2 | 3 | from .test_base import CliCommandTest 4 | from .mocks import mock_activated_status, mock_is_timeout 5 | 6 | 7 | class MaintenanceModeTest(CliCommandTest): 8 | 9 | def setUp(self): 10 | super(MaintenanceModeTest, self).setUp() 11 | self.use_manager() 12 | self.client.maintenance_mode.deactivate = MagicMock() 13 | self.client.maintenance_mode.activate = MagicMock() 14 | 15 | def test_maintenance_status(self): 16 | self.client.maintenance_mode.status = MagicMock() 17 | self.invoke('cfy maintenance-mode status') 18 | 19 | def test_activate_maintenance(self): 20 | self.invoke('cfy maintenance-mode activate') 21 | 22 | def test_activate_maintenance_with_wait(self): 23 | with patch('cloudify_rest_client.maintenance.' 24 | 'MaintenanceModeClient.status', 25 | new=mock_activated_status): 26 | with patch('time.sleep') as sleep_mock: 27 | self.invoke('cfy maintenance-mode activate --wait') 28 | self.invoke('cfy maintenance-mode ' 29 | 'activate --wait --timeout 20') 30 | sleep_mock.assert_has_calls([call(5), call(5)]) 31 | 32 | def test_activate_maintenance_timeout(self): 33 | with patch('cloudify_cli.commands.maintenance_mode._is_timeout', 34 | new=mock_is_timeout): 35 | self.invoke( 36 | 'cfy maintenance-mode activate --wait', 37 | err_str_segment='Timed out while entering maintenance mode') 38 | 39 | def test_activate_maintenance_timeout_no_wait(self): 40 | self.invoke('cfy maintenance-mode activate --timeout 5', 41 | "'--timeout' was used without '--wait'.") 42 | 43 | def test_deactivate_maintenance(self): 44 | self.invoke('cfy maintenance-mode deactivate') 45 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_nodes.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2018 Cloudify Platform Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | 18 | import json 19 | 20 | from mock import MagicMock 21 | 22 | from .test_base import CliCommandTest 23 | from .mocks import node_get_mock, node_instance_get_mock, MockListResponse 24 | 25 | 26 | class NodesTest(CliCommandTest): 27 | 28 | def setUp(self): 29 | super(NodesTest, self).setUp() 30 | self.use_manager() 31 | 32 | def test_nodes_get(self): 33 | node = node_get_mock() 34 | node_instance = node_instance_get_mock() 35 | self.client.nodes.get = MagicMock(return_value=node) 36 | self.client.node_instances.list = MagicMock( 37 | return_value=[node_instance]) 38 | outcome = self.invoke('cfy nodes get mongod -d nodecellar') 39 | self.assertIn(str(node_instance.id), outcome.logs) 40 | self.assertIn(str(node.properties['port']), outcome.output) 41 | 42 | def test_nodes_get_json(self): 43 | node = node_get_mock() 44 | node_instance = node_instance_get_mock() 45 | self.client.nodes.get = MagicMock(return_value=node) 46 | self.client.node_instances.list = MagicMock( 47 | return_value=[node_instance]) 48 | outcome = self.invoke('cfy nodes get mongod -d nodecellar --json') 49 | parsed = json.loads(outcome.output) 50 | self.assertEqual(parsed['instances'], [node_instance.id.hex]) 51 | self.assertEqual(parsed['properties'], node.properties) 52 | 53 | def test_node_get_no_node_id(self): 54 | outcome = self.invoke( 55 | 'cfy nodes get -d nodecellar', 56 | err_str_segment='2', # Exit code 57 | exception=SystemExit 58 | ) 59 | self.assertIn('missing argument', outcome.output.lower()) 60 | self.assertIn('NODE_ID', outcome.output) 61 | 62 | def test_node_get_no_deployment_id(self): 63 | outcome = self.invoke( 64 | 'cfy nodes get mongod', 65 | err_str_segment='2', # Exit code 66 | exception=SystemExit, 67 | ) 68 | self.assertIn('missing option', outcome.output.lower()) 69 | self.assertIn('--deployment-id', outcome.output) 70 | 71 | def test_nodes_list(self): 72 | self.client.nodes.list = MagicMock( 73 | return_value=MockListResponse(items=[node_get_mock(), 74 | node_get_mock()]) 75 | ) 76 | self.invoke('cfy nodes list') 77 | self.invoke('cfy nodes list -d nodecellar') 78 | self.invoke('cfy nodes list -t dummy_tenant') 79 | self.invoke('cfy nodes list -a') 80 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_options.py: -------------------------------------------------------------------------------- 1 | from mock import MagicMock 2 | 3 | from cloudify.models_states import AgentState 4 | from cloudify_cli.logger import get_global_json_output 5 | 6 | from .mocks import MockListResponse 7 | from .test_base import CliCommandTest 8 | 9 | 10 | class OptionsTest(CliCommandTest): 11 | def setUp(self): 12 | super(OptionsTest, self).setUp() 13 | self.use_manager() 14 | self.client.agents.list = MagicMock(return_value=MockListResponse()) 15 | 16 | def test_json_command_sets(self): 17 | self.client.blueprints.list = MagicMock( 18 | return_value=MockListResponse() 19 | ) 20 | self.invoke('cfy blueprints list --json ') 21 | self.assertTrue(get_global_json_output()) 22 | 23 | def test_format_command_sets(self): 24 | self.client.blueprints.list = MagicMock( 25 | return_value=MockListResponse() 26 | ) 27 | self.invoke('cfy blueprints list --format json') 28 | self.assertTrue(get_global_json_output()) 29 | 30 | def test_agent_filters_all_tenants(self): 31 | self.invoke('agents list --node-id a --all-tenants') 32 | self.client.agents.list.assert_called_with( 33 | deployment_id=[], 34 | install_methods=[], 35 | node_ids=['a'], 36 | node_instance_ids=[], 37 | _all_tenants=True, 38 | state=[AgentState.STARTED], 39 | ) 40 | 41 | def test_agent_filters_multiple(self): 42 | self.invoke('agents list --node-id a --node-id b') 43 | self.client.agents.list.assert_called_with( 44 | deployment_id=[], 45 | install_methods=[], 46 | node_ids=['a', 'b'], 47 | node_instance_ids=[], 48 | _all_tenants=False, 49 | state=[AgentState.STARTED], 50 | ) 51 | 52 | def test_agent_filters_commaseparated(self): 53 | self.invoke('agents list --node-id a,b') 54 | self.client.agents.list.assert_called_with( 55 | deployment_id=[], 56 | install_methods=[], 57 | node_ids=['a', 'b'], 58 | node_instance_ids=[], 59 | _all_tenants=False, 60 | state=[AgentState.STARTED], 61 | ) 62 | 63 | def test_agent_filters_commaseparated_multiple(self): 64 | self.invoke('agents list --node-id a,b --node-id c') 65 | self.client.agents.list.assert_called_with( 66 | deployment_id=[], 67 | install_methods=[], 68 | node_ids=['a', 'b', 'c'], 69 | node_instance_ids=[], 70 | _all_tenants=False, 71 | state=[AgentState.STARTED], 72 | ) 73 | 74 | def test_agents_filters_all_states(self): 75 | self.invoke('agents list --all-states') 76 | self.client.agents.list.assert_called_with( 77 | deployment_id=[], 78 | install_methods=[], 79 | node_ids=[], 80 | node_instance_ids=[], 81 | _all_tenants=False, 82 | ) 83 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_sites.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2013-2019 Cloudify Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | from mock import MagicMock 17 | 18 | from .test_base import CliCommandTest 19 | from cloudify_cli.exceptions import CloudifyValidationError, CloudifyCliError 20 | 21 | 22 | class SitesTest(CliCommandTest): 23 | system_exit = { 24 | 'err_str_segment': '2', 25 | 'exception': SystemExit 26 | } 27 | 28 | def setUp(self): 29 | super(SitesTest, self).setUp() 30 | self.use_manager() 31 | 32 | def test_sites_get(self): 33 | self.client.sites.get = MagicMock() 34 | self.invoke('cfy sites get test_site') 35 | call_args = list(self.client.sites.get.call_args) 36 | self.assertEqual(call_args[0][0], 'test_site') 37 | 38 | def test_get_missing_name(self): 39 | outcome = self.invoke('cfy sites get', **self.system_exit) 40 | self.assertIn('missing argument', outcome.output.lower()) 41 | self.assertIn('name', outcome.output.lower()) 42 | 43 | def test_get_invalid_name(self): 44 | self.invoke( 45 | "cfy sites get ' ' ", 46 | err_str_segment='ERROR: The `name` argument contains illegal ' 47 | 'characters', 48 | exception=CloudifyValidationError 49 | ) 50 | 51 | self.invoke( 52 | "cfy sites get :bla", 53 | err_str_segment='ERROR: The `name` argument contains illegal ' 54 | 'characters', 55 | exception=CloudifyValidationError 56 | ) 57 | 58 | def test_sites_create(self): 59 | self.client.sites.create = MagicMock() 60 | self.invoke('cfy sites create test_site') 61 | call_args = list(self.client.sites.create.call_args) 62 | self.assertEqual(call_args[0][0], 'test_site') 63 | 64 | def test_sites_create_with_location(self): 65 | self.client.sites.create = MagicMock() 66 | self.invoke('cfy sites create test_site --location 1.0,2.0') 67 | call_args = list(self.client.sites.create.call_args) 68 | self.assertEqual(call_args[0][1], '1.0,2.0') 69 | 70 | def test_create_missing_name(self): 71 | outcome = self.invoke('cfy sites create ', **self.system_exit) 72 | self.assertIn('missing argument', outcome.output.lower()) 73 | self.assertIn('name', outcome.output.lower()) 74 | 75 | def test_create_invalid_visibility(self): 76 | self.invoke('cfy sites create test_site -l bla', 77 | err_str_segment='Invalid visibility: `bla`', 78 | exception=CloudifyCliError) 79 | 80 | def test_create_invalid_argument(self): 81 | outcome = self.invoke('cfy sites create test_site -g', 82 | **self.system_exit) 83 | self.assertIn('No such option: -g', outcome.output) 84 | 85 | def test_create_invalid_location(self): 86 | outcome = self.invoke('cfy sites create test_site --location', 87 | **self.system_exit) 88 | self.assertIn('Error: Option \'--location\' requires an argument', 89 | outcome.output) 90 | 91 | def test_sites_update(self): 92 | self.client.sites.update = MagicMock() 93 | self.invoke('cfy sites update test_site') 94 | call_args = list(self.client.sites.update.call_args) 95 | self.assertEqual(call_args[0][0], 'test_site') 96 | 97 | def test_sites_update_with_location(self): 98 | self.client.sites.update = MagicMock() 99 | self.invoke('cfy sites update test_site --location 1.2,1.3') 100 | call_args = list(self.client.sites.update.call_args) 101 | self.assertEqual(call_args[0][1], '1.2,1.3') 102 | 103 | def test_sites_update_with_new_name(self): 104 | self.client.sites.update = MagicMock() 105 | self.invoke('cfy sites update test_site --new-name new_name') 106 | call_args = list(self.client.sites.update.call_args) 107 | self.assertEqual(call_args[0][3], 'new_name') 108 | 109 | def test_update_invalid_visibility(self): 110 | self.invoke('cfy sites update test_site -l bla', 111 | err_str_segment='Invalid visibility: `bla`', 112 | exception=CloudifyCliError) 113 | 114 | def test_update_invalid_location(self): 115 | outcome = self.invoke('cfy sites update test_site --location', 116 | **self.system_exit) 117 | self.assertIn('Error: Option \'--location\' requires an argument', 118 | outcome.output) 119 | 120 | def test_update_invalid_new_name(self): 121 | self.invoke('cfy sites update test_site --new-name :bla', 122 | err_str_segment='The `new_name` argument contains illegal ' 123 | 'characters', 124 | exception=CloudifyValidationError) 125 | 126 | def test_sites_delete(self): 127 | self.client.sites.delete = MagicMock() 128 | self.invoke('cfy sites delete test_site') 129 | call_args = list(self.client.sites.delete.call_args) 130 | self.assertEqual(call_args[0][0], 'test_site') 131 | 132 | def test_sites_list(self): 133 | self.client.sites.list = MagicMock() 134 | self.invoke('cfy sites list') 135 | 136 | def test_sites_invalid_command(self): 137 | outcome = self.invoke('cfy sites bla', **self.system_exit) 138 | self.assertIn('no such command', outcome.output.lower()) 139 | self.assertIn('bla', outcome.output) 140 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_snapshots.py: -------------------------------------------------------------------------------- 1 | from mock import Mock, MagicMock 2 | 3 | from .mocks import MockListResponse 4 | from .constants import SNAPSHOTS_DIR 5 | from .test_base import CliCommandTest 6 | 7 | from cloudify_rest_client import snapshots, executions 8 | 9 | 10 | class SnapshotsTest(CliCommandTest): 11 | 12 | def setUp(self): 13 | super(SnapshotsTest, self).setUp() 14 | self.client.manager.get_version = Mock() 15 | self.use_manager() 16 | 17 | def test_snapshots_list(self): 18 | self.client.snapshots.list = MagicMock(return_value=MockListResponse()) 19 | self.invoke('cfy snapshots list') 20 | self.invoke('cfy snapshots list -t dummy_tenant') 21 | self.invoke('cfy snapshots list -a') 22 | 23 | def test_snapshots_delete(self): 24 | self.client.snapshots.delete = MagicMock() 25 | self.invoke('cfy snapshots delete a-snapshot-id') 26 | 27 | def test_snapshots_upload(self): 28 | self.client.snapshots.upload = MagicMock( 29 | return_value=snapshots.Snapshot({'id': 'some_id'})) 30 | self.invoke('cfy snapshots upload {0}/snapshot.zip ' 31 | '-s my_snapshot_id'.format(SNAPSHOTS_DIR)) 32 | 33 | def test_snapshots_create(self): 34 | self.client.snapshots.create = MagicMock( 35 | return_value=executions.Execution({'id': 'some_id'})) 36 | self.invoke('cfy snapshots create a-snapshot-id') 37 | 38 | def test_snapshots_restore(self): 39 | self.client.snapshots.restore = MagicMock() 40 | self.invoke('cfy snapshots restore a-snapshot-id') 41 | self.invoke('cfy snapshots restore a-snapshot-id' 42 | '--without-deployments-envs') 43 | self.invoke('cfy snapshots restore a-snapshot-id' 44 | '--ignore-plugin-failure') 45 | 46 | def test_snapshots_download(self): 47 | self.client.snapshots.download = MagicMock(return_value='some_file') 48 | self.invoke('cfy snapshots download a-snapshot-id') 49 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_status.py: -------------------------------------------------------------------------------- 1 | from mock import MagicMock, patch 2 | 3 | from .test_base import CliCommandTest 4 | 5 | from cloudify_rest_client.exceptions import UserUnauthorizedError 6 | 7 | 8 | class StatusTest(CliCommandTest): 9 | 10 | def setUp(self): 11 | super(StatusTest, self).setUp() 12 | self.client.manager.get_status = MagicMock() 13 | self.client.maintenance_mode.status = MagicMock() 14 | 15 | def test_status_command(self): 16 | self.invoke('cfy status') 17 | 18 | def test_status_no_manager_server_defined(self): 19 | self.use_local_profile() 20 | # Running a command which requires a target manager server without 21 | # first calling "cfy profiles use" or providing a target server 22 | # explicitly 23 | self.invoke( 24 | 'cfy status', 25 | 'This command is only available when using a manager' 26 | ) 27 | 28 | def test_status_by_unauthorized_user(self): 29 | with patch.object(self.client.manager, 'get_status') as mock: 30 | mock.side_effect = UserUnauthorizedError('Unauthorized user') 31 | outcome = self.invoke('cfy status') 32 | self.assertIn('User is unauthorized', outcome.logs) 33 | 34 | def test_status_result_services(self): 35 | status_result = { 36 | "status": "OK", 37 | "services": { 38 | "name1": { 39 | "status": "state1", 40 | "extra_info": { 41 | "systemd": {} 42 | }, 43 | "isExternal": False 44 | }, 45 | "name2": { 46 | "status": "state2", 47 | "isExternal": True 48 | } 49 | } 50 | } 51 | 52 | self.client.manager.get_status = MagicMock(return_value=status_result) 53 | 54 | outcome = self.invoke('cfy status') 55 | outcome = [o.strip() for o in outcome.output.split('\n')] 56 | 57 | expected_outputs = [ 58 | '| name1 | state1 |', 59 | '| name2 | state2 |', 60 | ] 61 | 62 | for output in expected_outputs: 63 | self.assertIn(output, outcome) 64 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_tenants.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | from mock import MagicMock 17 | 18 | from .test_base import CliCommandTest 19 | from cloudify_cli.exceptions import CloudifyValidationError 20 | 21 | 22 | class TenantsTest(CliCommandTest): 23 | def setUp(self): 24 | super(TenantsTest, self).setUp() 25 | self.use_manager() 26 | self.client.tenants = MagicMock() 27 | 28 | def test_empty_tenant_name(self): 29 | self.invoke( 30 | 'cfy tenants create ""', 31 | err_str_segment='ERROR: The `tenant_name` argument is empty', 32 | exception=CloudifyValidationError 33 | ) 34 | 35 | def test_illegal_characters_in_tenant_name(self): 36 | self.invoke( 37 | 'cfy tenants create "#&*"', 38 | err_str_segment='ERROR: The `tenant_name` argument contains ' 39 | 'illegal characters', 40 | exception=CloudifyValidationError 41 | ) 42 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_tokens.py: -------------------------------------------------------------------------------- 1 | from mock import Mock 2 | 3 | from cloudify_rest_client.tokens import Token 4 | 5 | from cloudify_cli.tests.commands.test_base import CliCommandTest 6 | from cloudify_cli.tests.commands.mocks import MockListResponse 7 | 8 | 9 | TOKEN1 = {'id': 'abc123', 'role': 'someroleorother', 'username': 'user1', 10 | 'description': 'areallyusefultoken', 11 | 'value': 'ctok-something-willbehiddenbyserver'} 12 | TOKEN2 = {'id': 'xyz4576', 'role': 'adifferentrole', 'username': 'user2', 13 | 'description': 'trokenboken', 'last_used': 'A Long Time Ago', 14 | 'expiration_date': 'Very very soon', 15 | 'value': 'ctok-other-alsohiddenvalue'} 16 | 17 | 18 | class TokensTest(CliCommandTest): 19 | 20 | def setUp(self): 21 | super(TokensTest, self).setUp() 22 | self.client.tokens = Mock() 23 | self.use_manager() 24 | 25 | def test_old_command_error(self): 26 | # Make sure we give a helpful error on the old style token retrieval 27 | self.invoke('cfy tokens get', err_str_segment='`cfy tokens create`') 28 | 29 | def _check_output(self, tokens, output, expect_users=False, 30 | expect_count=True, expect_value=False): 31 | for token in tokens: 32 | for key in ['id', 'role', 'description', 33 | 'expiration_date', 'last_used']: 34 | if token.get(key): 35 | assert token.get(key) in output 36 | 37 | if expect_users: 38 | assert token['username'] in output 39 | else: 40 | assert token['username'] not in output 41 | 42 | if expect_value: 43 | assert token['value'] in output 44 | else: 45 | assert token['value'] not in output 46 | if expect_count: 47 | assert '{0} of {0}'.format(len(tokens)) in output 48 | 49 | def test_get_token(self): 50 | token = Token(TOKEN1) 51 | self.client.tokens.get.return_value = token 52 | output = self.invoke('cfy tokens get {}'.format(token['id'])).output 53 | self.client.tokens.get.assert_called_once_with(token['id']) 54 | self._check_output([token], output, expect_count=False) 55 | 56 | def test_delete_token(self): 57 | token_id = 'killthistoken' 58 | self.invoke('cfy tokens delete {}'.format(token_id)) 59 | self.client.tokens.delete.assert_called_once_with(token_id) 60 | 61 | def test_list_single_user(self): 62 | tokens = MockListResponse(items=[Token(TOKEN1)]) 63 | tokens.metadata.pagination.total = len(tokens) 64 | self.client.tokens.list.return_value = tokens 65 | output = self.invoke('cfy tokens list').output 66 | self.client.tokens.list.assert_called_once_with() 67 | self._check_output(tokens.items, output) 68 | 69 | def test_list_multi_user(self): 70 | tokens = MockListResponse(items=[Token(TOKEN1), Token(TOKEN2)]) 71 | tokens.metadata.pagination.total = len(tokens) 72 | self.client.tokens.list.return_value = tokens 73 | output = self.invoke('cfy tokens list').output 74 | self.client.tokens.list.assert_called_once_with() 75 | self._check_output(tokens.items, output, expect_users=True) 76 | 77 | def test_create_token(self): 78 | token = Token(TOKEN1) 79 | self.client.tokens.create.return_value = token 80 | output = self.invoke('cfy tokens create').output 81 | self.client.tokens.create.assert_called_once_with( 82 | expiration=None, description=None, 83 | ) 84 | self._check_output([token], output, expect_value=True, 85 | expect_count=False) 86 | 87 | def test_create_token_extras(self): 88 | token = Token(TOKEN1) 89 | self.client.tokens.create.return_value = token 90 | self.invoke('cfy tokens create ' 91 | '--expiry something ' 92 | '--description descr') 93 | self.client.tokens.create.assert_called_once_with( 94 | expiration='something', description='descr', 95 | ) 96 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_use.py: -------------------------------------------------------------------------------- 1 | from mock import MagicMock, patch 2 | 3 | from ... import env 4 | from ... import constants 5 | from .constants import SSL_PORT 6 | from .test_base import CliCommandTest 7 | 8 | from cloudify_rest_client import CloudifyClient 9 | from cloudify_rest_client.exceptions import UserUnauthorizedError 10 | 11 | 12 | class UseTest(CliCommandTest): 13 | 14 | def test_use_command(self): 15 | self.client.manager.get_status = MagicMock() 16 | self.client.manager.get_context = MagicMock( 17 | return_value={ 18 | 'name': 'name', 19 | 'context': {}} 20 | ) 21 | self.invoke('cfy profiles use 127.0.0.1') 22 | context = self._read_context() 23 | self.assertEquals("127.0.0.1", context.manager_ip) 24 | 25 | def test_use_attempt_by_unauthorized_user(self): 26 | with patch.object(self.client.manager, 'get_status') as mock: 27 | mock.side_effect = UserUnauthorizedError('Unauthorized user') 28 | self.invoke('cfy profiles use 127.0.0.1', 29 | err_str_segment='Unauthorized user') 30 | 31 | def test_use_command_no_prior_init(self): 32 | self.client.manager.get_status = MagicMock() 33 | self.client.manager.get_context = MagicMock( 34 | return_value={ 35 | 'name': 'name', 'context': {} 36 | } 37 | ) 38 | self.invoke('cfy profiles use 127.0.0.1') 39 | context = self._read_context() 40 | self.assertEquals('127.0.0.1', context.manager_ip) 41 | 42 | def test_use_with_user_and_port(self): 43 | self.client.manager.get_status = MagicMock() 44 | self.client.manager.get_context = MagicMock( 45 | return_value={ 46 | 'name': 'name', 'context': {} 47 | } 48 | ) 49 | self.invoke('cfy profiles use 127.0.0.1 -s test_user --ssh-port 22222') 50 | context = self._read_context() 51 | self.assertEquals('127.0.0.1', context.manager_ip) 52 | self.assertEquals('22222', context.ssh_port) 53 | self.assertEquals('test_user', context.ssh_user) 54 | 55 | def test_use_with_authorization(self): 56 | host = '127.0.0.1' 57 | auth_header = env.get_auth_header('test_username', 'test_password') 58 | self.client = CloudifyClient(host=host, headers=auth_header) 59 | 60 | self._test_use() 61 | 62 | # assert Authorization in headers 63 | eventual_request_headers = self.client._client.headers 64 | self.assertEqual(self.do_request_headers, eventual_request_headers) 65 | 66 | def test_use_with_verify(self): 67 | host = 'localhost' 68 | self.client = CloudifyClient(host=host, protocol='https') 69 | self._test_use() 70 | self.assertEqual(self.request_url, 'https://{0}:{1}/api/{2}/status'. 71 | format(host, SSL_PORT, constants.API_VERSION)) 72 | self.assertTrue(self.verify) 73 | 74 | def test_use_trust_all(self): 75 | host = 'localhost' 76 | self.client = CloudifyClient(host=host, 77 | protocol='https', trust_all=True) 78 | self._test_use() 79 | self.assertEqual(self.request_url, 'https://{0}:{1}/api/{2}/status'. 80 | format(host, SSL_PORT, constants.API_VERSION)) 81 | self.assertFalse(self.verify) 82 | 83 | @patch('cloudify_cli.commands.profiles._get_provider_context', 84 | return_value={}) 85 | def test_use_sets_ssl_port_and_protocol(self, *_): 86 | outcome = self.invoke('profiles use 1.2.3.4 --ssl') 87 | self.assertIn('Using manager 1.2.3.4', outcome.logs) 88 | context = self._read_context() 89 | self.assertEqual(constants.SECURED_REST_PORT, context.rest_port) 90 | self.assertEqual(constants.SECURED_REST_PROTOCOL, 91 | context.rest_protocol) 92 | 93 | @patch('cloudify_cli.commands.profiles._get_provider_context', 94 | return_value={}) 95 | @patch('cloudify_rest_client.client.HTTPClient._do_request', 96 | return_value={}) 97 | def test_use_secured(self, *_): 98 | outcome = self.invoke('profiles use 1.2.3.4 --ssl') 99 | self.assertIn('Using manager 1.2.3.4', outcome.logs) 100 | context = self._read_context() 101 | self.assertEqual(constants.SECURED_REST_PORT, context.rest_port) 102 | self.assertEqual(constants.SECURED_REST_PROTOCOL, 103 | context.rest_protocol) 104 | 105 | @patch('cloudify_cli.commands.profiles._get_provider_context', 106 | return_value={}) 107 | @patch('cloudify_rest_client.client.HTTPClient._do_request', 108 | return_value={}) 109 | def test_use_sets_default_port_and_protocol(self, *_): 110 | outcome = self.invoke('profiles use 1.2.3.4') 111 | self.assertIn('Using manager 1.2.3.4', outcome.logs) 112 | context = self._read_context() 113 | self.assertEqual(constants.DEFAULT_REST_PORT, context.rest_port) 114 | self.assertEqual(constants.DEFAULT_REST_PROTOCOL, 115 | context.rest_protocol) 116 | 117 | def _test_use(self): 118 | host = 'localhost' 119 | self.client.manager.get_context = MagicMock( 120 | return_value={ 121 | 'name': 'name', 122 | 'context': {} 123 | } 124 | ) 125 | 126 | self.headers = None 127 | self.request_url = None 128 | self.verify = None 129 | 130 | def mock_do_request(*_, **kwargs): 131 | self.do_request_headers = kwargs.get('headers') 132 | self.request_url = kwargs.get('request_url') 133 | self.verify = kwargs.get('verify') 134 | return 'success' 135 | 136 | with patch('cloudify_rest_client.client.HTTPClient._do_request', 137 | new=mock_do_request): 138 | if self.client._client.port == SSL_PORT: 139 | secured_flag = '--ssl' 140 | else: 141 | secured_flag = '' 142 | 143 | self.invoke('cfy profiles use {0} {1}'.format( 144 | host, secured_flag)) 145 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_user_groups.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | from mock import MagicMock 17 | 18 | from .test_base import CliCommandTest 19 | from cloudify_cli.exceptions import CloudifyValidationError 20 | 21 | 22 | class UserGroupsTest(CliCommandTest): 23 | def setUp(self): 24 | super(UserGroupsTest, self).setUp() 25 | self.use_manager() 26 | self.client.user_groups = MagicMock() 27 | 28 | def test_user_groups_create(self): 29 | self.client.user_groups.create = MagicMock() 30 | self.invoke('cfy user-groups create my_group_name') 31 | self.assertEquals(1, len(self.client.user_groups.method_calls)) 32 | self.assertEquals('create', self.client.user_groups.method_calls[0][0]) 33 | self.assertEquals(('my_group_name', 'default'), 34 | self.client.user_groups.method_calls[0][1]) 35 | 36 | def test_user_groups_get(self): 37 | self.client.user_groups.get = MagicMock( 38 | return_value={'name': '', 'tenants': [], 'users': []}) 39 | self.invoke('cfy user-groups get my_group_name') 40 | self.assertEquals(1, len(self.client.user_groups.method_calls)) 41 | self.assertEquals('get', self.client.user_groups.method_calls[0][0]) 42 | self.assertEquals( 43 | ('my_group_name',), self.client.user_groups.method_calls[0][1]) 44 | 45 | def test_user_groups_add_user(self): 46 | self.client.user_groups = MagicMock() 47 | self.client.user_groups.add_user = MagicMock() 48 | self.invoke('cfy user-groups add-user my_username -g my_group_name') 49 | self.assertEquals(1, len(self.client.user_groups.method_calls)) 50 | self.assertEquals( 51 | 'add_user', self.client.user_groups.method_calls[0][0]) 52 | self.assertEquals(('my_username', 'my_group_name',), 53 | self.client.user_groups.method_calls[0][1]) 54 | 55 | def test_user_groups_remove_user(self): 56 | self.client.user_groups = MagicMock() 57 | self.client.user_groups.remove_user = MagicMock() 58 | self.invoke('cfy user-groups remove-user my_username -g my_group_name') 59 | self.assertEquals(1, len(self.client.user_groups.method_calls)) 60 | self.assertEquals( 61 | 'remove_user', self.client.user_groups.method_calls[0][0]) 62 | self.assertEquals(('my_username', 'my_group_name',), 63 | self.client.user_groups.method_calls[0][1]) 64 | 65 | def test_group_create(self): 66 | self.invoke('cfy user-groups create group1 -l ldap_dn') 67 | 68 | def test_empty_user_group_name(self): 69 | self.invoke( 70 | 'cfy user-groups create ""', 71 | err_str_segment='ERROR: The `user_group_name` argument is empty', 72 | exception=CloudifyValidationError 73 | ) 74 | 75 | def test_illegal_characters_in_user_group_name(self): 76 | self.invoke( 77 | 'cfy user-groups create "#&*"', 78 | err_str_segment='ERROR: The `user_group_name` argument contains ' 79 | 'illegal characters', 80 | exception=CloudifyValidationError 81 | ) 82 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_users.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | from mock import MagicMock 17 | 18 | from .test_base import CliCommandTest 19 | from cloudify_cli.exceptions import CloudifyValidationError 20 | 21 | 22 | class BaseUsersTest(CliCommandTest): 23 | def setUp(self): 24 | super(BaseUsersTest, self).setUp() 25 | self.use_manager() 26 | self.client.users = MagicMock() 27 | 28 | 29 | class UsersTest(BaseUsersTest): 30 | def setUp(self): 31 | super(UsersTest, self).setUp() 32 | 33 | def test_create_users_missing_username(self): 34 | outcome = self.invoke( 35 | 'cfy users create', 36 | err_str_segment='2', # Exit code 37 | exception=SystemExit 38 | ) 39 | self.assertIn('missing argument', outcome.output.lower()) 40 | self.assertIn('username', outcome.output.lower()) 41 | 42 | def test_create_users_missing_password(self): 43 | outcome = self.invoke( 44 | 'cfy users create username', 45 | err_str_segment='2', # Exit code 46 | exception=SystemExit 47 | ) 48 | self.assertIn('missing option', outcome.output.lower()) 49 | self.assertIn('--password', outcome.output) 50 | 51 | def test_create_users_default_role(self): 52 | self.invoke('cfy users create username -p password') 53 | call_list = self.client.users.method_calls[0][1] 54 | self.assertEqual(call_list, ('username', 'password', 'default')) 55 | 56 | def test_create_users_custom_role(self): 57 | self.invoke('cfy users create username -p password -r admin') 58 | call_list = self.client.users.method_calls[0][1] 59 | self.assertEqual(call_list, ('username', 'password', 'admin')) 60 | 61 | def test_empty_password(self): 62 | self.invoke( 63 | 'cfy users create user -p ""', 64 | err_str_segment='ERROR: The password is empty', 65 | exception=CloudifyValidationError 66 | ) 67 | 68 | def test_unlock_user(self): 69 | self.invoke('cfy users unlock user1') 70 | call_list = self.client.users.method_calls[0][1][0] 71 | self.assertEqual(call_list, 'user1') 72 | 73 | 74 | class CreateUsersWithTenantTest(BaseUsersTest): 75 | def setUp(self): 76 | super(CreateUsersWithTenantTest, self).setUp() 77 | self.client.tenants = MagicMock() 78 | 79 | def test_create_users_without_tenant_info(self): 80 | self.invoke('cfy users create username -p password') 81 | call_list = self.client.users.method_calls[0][1] 82 | self.assertEqual(call_list, ('username', 'password', 'default')) 83 | adding_to_tenant_call_list = self.client.tenants.method_calls 84 | self.assertEqual(adding_to_tenant_call_list, []) 85 | 86 | def test_create_users_with_full_tenant_info(self): 87 | self.invoke('cfy users create username -p password -t\ 88 | test_tenant -l test_user') 89 | user_create_call_list = self.client.users.method_calls[0][1] 90 | self.assertEqual(user_create_call_list, 91 | ('username', 'password', 'default')) 92 | adding_to_tenant_call_list = self.client.tenants.method_calls[0][1] 93 | self.assertEqual(adding_to_tenant_call_list, 94 | ('username', 'test_tenant', 'test_user')) 95 | 96 | def test_create_users_with_full_tenant_info_long_flags_names(self): 97 | self.invoke('cfy users create username -p password --tenant-name\ 98 | test_tenant --user-tenant-role test_user') 99 | user_create_call_list = self.client.users.method_calls[0][1] 100 | self.assertEqual(user_create_call_list, 101 | ('username', 'password', 'default')) 102 | adding_to_tenant_call_list = self.client.tenants.method_calls[0][1] 103 | self.assertEqual(adding_to_tenant_call_list, 104 | ('username', 'test_tenant', 'test_user')) 105 | 106 | def test_create_fail_users_with_tenant_name_only(self): 107 | self.invoke('cfy users create username -p password -t default') 108 | user_create_call_list = self.client.users.method_calls[0][1] 109 | self.assertEqual(user_create_call_list, 110 | ('username', 'password', 'default')) 111 | adding_to_tenant_call_list = self.client.tenants.method_calls 112 | self.assertEqual(adding_to_tenant_call_list, []) 113 | 114 | def test_create_fail_users_with_user_tenant_role_only(self): 115 | self.invoke('cfy users create username -p password -l user') 116 | user_create_call_list = self.client.users.method_calls[0][1] 117 | self.assertEqual(user_create_call_list, 118 | ('username', 'password', 'default')) 119 | adding_to_tenant_call_list = self.client.tenants.method_calls 120 | self.assertEqual(adding_to_tenant_call_list, []) 121 | -------------------------------------------------------------------------------- /cloudify_cli/tests/commands/test_version.py: -------------------------------------------------------------------------------- 1 | import mock 2 | 3 | from .test_base import CliCommandTest 4 | 5 | 6 | def manager_data(): 7 | return { 8 | 'date': '', 'commit': '', 9 | 'version': '3.4.0', 10 | 'build': '85', 11 | 'ip': '10.10.1.10', 12 | 'edition': 'community' 13 | } 14 | 15 | 16 | class VersionTest(CliCommandTest): 17 | 18 | def test_version(self): 19 | self.use_local_profile() 20 | outcome = self.invoke('cfy --version') 21 | self.assertIn('Cloudify CLI', outcome.logs) 22 | 23 | @mock.patch('cloudify_cli.env.is_manager_active', return_value=True) 24 | @mock.patch('cloudify_cli.env.get_manager_version_data', 25 | return_value=manager_data()) 26 | def test_version_with_manager(self, *_): 27 | outcome = self.invoke('cfy --version') 28 | self.assertIn('Cloudify Manager', outcome.logs) 29 | self.assertIn('ip=10.10.1.10', outcome.logs) 30 | -------------------------------------------------------------------------------- /cloudify_cli/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def class_caplog(request, caplog): 6 | request.cls.caplog = caplog 7 | 8 | 9 | @pytest.fixture() 10 | def class_tmpdir(request, tmpdir): 11 | request.cls.tmpdir = tmpdir 12 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/__init__.py -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/bad_blueprint/blueprint.yaml: -------------------------------------------------------------------------------- 1 | This is not a blueprint file -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/bad_blueprint/blueprint_catalog.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_3 2 | 3 | description: > 4 | This plugin tries to use a blueprint from Cloudify catalog URL (`blueprint:...`) 5 | with a local `cfy blueprints validate` 6 | 7 | imports: 8 | - ns--blueprint:test 9 | 10 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/bad_blueprint/plugin_repo.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_3 2 | 3 | description: > 4 | This plugin tries to use a plugin repository URL (`plugin:...`) 5 | with a local `cfy blueprints validate` 6 | 7 | imports: 8 | - plugin:cloudify-openstack-plugin 9 | 10 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/cloudify-hello-world-example-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/blueprints/cloudify-hello-world-example-master.zip -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/helloworld.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/blueprints/helloworld.zip -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/helloworld/blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | imports: 4 | - sample_app.yaml 5 | 6 | inputs: 7 | key1: 8 | default: default_val1 9 | key2: 10 | default: default_val2 11 | key3: 12 | default: default_val3 13 | 14 | node_templates: 15 | node1: 16 | type: type1 17 | 18 | node2: 19 | type: type2 20 | properties: 21 | prop3: 8080 22 | relationships: 23 | - type: relationship_type1 24 | target: node1 25 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/helloworld/inputs.yaml: -------------------------------------------------------------------------------- 1 | key1: val1 2 | key2: val2 -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/helloworld/sample_app.yaml: -------------------------------------------------------------------------------- 1 | 2 | node_types: 3 | type1: 4 | interfaces: 5 | interface1: 6 | install: 7 | implementation: plugin.tasks.install 8 | inputs: {} 9 | start: 10 | implementation: plugin.tasks.start 11 | inputs: {} 12 | 13 | properties: 14 | prop1: 15 | default: true 16 | prop2: 17 | default: {} 18 | prop3: 19 | default: '' 20 | 21 | type2: 22 | derived_from: type1 23 | properties: 24 | prop1: 25 | default: true 26 | prop2: 27 | default: 28 | sub_prop1: val1 29 | sub_prop2: val2 30 | type3: 31 | derived_from: type2 32 | 33 | plugins: 34 | plugin: 35 | executor: central_deployment_agent 36 | source: plugin_url.zip 37 | 38 | relationships: 39 | relationship_type1: {} -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/helloworld/simple_blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | plugins: 4 | p: 5 | executor: central_deployment_agent 6 | install: false 7 | 8 | node_types: 9 | type1: 10 | properties: 11 | prop1: 12 | default: true 13 | prop2: 14 | default: {} 15 | prop3: 16 | default: '' 17 | 18 | type2: 19 | derived_from: type1 20 | properties: 21 | prop1: 22 | default: true 23 | prop2: 24 | default: 25 | sub_prop1: val1 26 | sub_prop2: val2 27 | type3: 28 | derived_from: type2 29 | 30 | inputs: 31 | key1: 32 | default: default_val1 33 | key2: 34 | default: default_val2 35 | key3: 36 | default: default_val3 37 | 38 | node_templates: 39 | node1: 40 | type: type1 41 | interfaces: 42 | test: 43 | op: p.cloudify_cli.tests.commands.mocks.mock_op 44 | 45 | node2: 46 | type: type2 47 | properties: 48 | prop3: 8080 49 | interfaces: 50 | test: 51 | op: p.cloudify_cli.tests.commands.mocks.mock_op 52 | 53 | workflows: 54 | mock_workflow: 55 | mapping: p.cloudify_cli.tests.commands.mocks.mock_workflow 56 | parameters: 57 | param: 58 | default: default_param 59 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/helloworld_custom_name.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/blueprints/helloworld_custom_name.zip -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/local/blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | plugins: 4 | p: 5 | executor: central_deployment_agent 6 | install: false 7 | 8 | inputs: 9 | key1: 10 | default: default_val1 11 | key2: 12 | default: default_val2 13 | key3: 14 | default: default_val3 15 | 16 | node_types: 17 | test_type: {} 18 | 19 | node_templates: 20 | node: 21 | type: test_type 22 | interfaces: 23 | test: 24 | op: p.cloudify_cli.tests.commands.mocks.mock_op 25 | 26 | outputs: 27 | key1: 28 | value: { get_input: key1 } 29 | key2: 30 | value: { get_input: key2 } 31 | key3: 32 | value: { get_input: key3 } 33 | param: 34 | value: { get_attribute: [node, param] } 35 | custom_param: 36 | value: { get_attribute: [node, custom_param] } 37 | provider_context: 38 | value: { get_attribute: [node, provider_context] } 39 | 40 | workflows: 41 | run_test_op_on_nodes: 42 | mapping: p.cloudify_cli.tests.commands.mocks.mock_workflow 43 | parameters: 44 | param: 45 | default: default_param 46 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/local/blueprint_validate_definitions_version.yaml: -------------------------------------------------------------------------------- 1 | # The tests expected this value to be 1_0 so please don't change it 2 | tosca_definitions_version: cloudify_dsl_1_0 3 | 4 | description: | 5 | This will fail validation if the 'validate_definitions_version' is not 6 | set to false 7 | 8 | node_types: 9 | type: {} 10 | 11 | node_templates: 12 | node: 13 | type: type 14 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/local/blueprint_with_plugins.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | inputs: 4 | admin_username: 5 | default: admin 6 | admin_password: 7 | default: '' 8 | 9 | plugins: 10 | plugin: 11 | executor: central_deployment_agent 12 | source: http://localhost/plugin.zip 13 | local_plugin: 14 | executor: central_deployment_agent 15 | source: local_plugin 16 | no_install_plugin: 17 | executor: central_deployment_agent 18 | install: false 19 | host_plugin: 20 | executor: host_agent 21 | source: http://localhost/host_plugin.zip 22 | 23 | node_types: 24 | cloudify.nodes.Compute: {} 25 | 26 | 27 | node_templates: 28 | node: 29 | type: cloudify.nodes.Compute 30 | interfaces: 31 | test: 32 | op: plugin.tasks.op 33 | op2: local_plugin.tasks.op2 34 | op3: no_install_plugin.tasks.op3 35 | op4: host_plugin.tasks.op4 -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/local/blueprint_without_plugins.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | node_types: 4 | test_type: {} 5 | 6 | node_templates: 7 | node: 8 | type: test_type 9 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/local/install-agent-blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | node_types: 4 | cloudify.nodes.Compute: 5 | properties: 6 | install_agent: 7 | default: true 8 | 9 | node_templates: 10 | node: 11 | type: cloudify.nodes.Compute 12 | 13 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/local/windows_installers_blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | plugins: 4 | 5 | windows_agent_installer: 6 | executor: central_deployment_agent 7 | install: false 8 | 9 | windows_plugin_installer: 10 | executor: host_agent 11 | install: false 12 | 13 | 14 | node_types: 15 | cloudify.nodes.Compute: 16 | interfaces: 17 | cloudify.interfaces.worker_installer: 18 | install: 19 | implementation: windows_agent_installer.windows_agent_installer.tasks.install 20 | inputs: 21 | cloudify_agent: 22 | default: 23 | user: { get_attribute: [SELF, username] } 24 | password: { get_attribute: [SELF, password] } 25 | cloudify.interfaces.plugin_installer: 26 | install: 27 | implementation: windows_plugin_installer.windows_plugin_installer.tasks.install 28 | inputs: 29 | cloudify_agent: 30 | default: 31 | user: { get_attribute: [SELF, username] } 32 | password: { get_attribute: [SELF, password] } 33 | 34 | node_templates: 35 | win_host: 36 | type: cloudify.nodes.Compute 37 | 38 | 39 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/blueprints/logging/blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_2 2 | 3 | plugins: 4 | p: 5 | executor: central_deployment_agent 6 | install: false 7 | 8 | node_types: 9 | type: {} 10 | 11 | node_templates: 12 | node: 13 | type: type 14 | interfaces: 15 | test: 16 | op: p.cloudify_cli.tests.commands.test_local.logging_operation 17 | 18 | workflows: 19 | logging_workflow: 20 | mapping: p.cloudify_cli.tests.commands.test_local.logging_workflow 21 | parameters: 22 | level: 23 | default: 'INFO' 24 | message: 25 | default: '' 26 | error: 27 | default: false 28 | user_cause: 29 | default: false 30 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/inputs/bad_format.yaml: -------------------------------------------------------------------------------- 1 | some_malformed_yaml: 2 | hello 3 | hello2 -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/inputs/not_dict.yaml: -------------------------------------------------------------------------------- 1 | hello 2 | test 3 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/mocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/mocks/__init__.py -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/mocks/mock_list_response.py: -------------------------------------------------------------------------------- 1 | 2 | class MockListResponse(object): 3 | 4 | def __init__(self, items, _): 5 | self.items = items 6 | self.metadata = None 7 | 8 | def __iter__(self): 9 | return iter(self.items) 10 | 11 | def __getitem__(self, index): 12 | return self.items[index] 13 | 14 | def __len__(self): 15 | return len(self.items) 16 | 17 | def sort(self, cmp=None, key=None, reverse=False): 18 | return self.items.sort(cmp, key, reverse) 19 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/mocks/mock_tar.tar: -------------------------------------------------------------------------------- 1 | mock_for_tar 2 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/data: -------------------------------------------------------------------------------- 1 | {"plan": {"description": null, "metadata": null, "nodes": [{"type": "Root", "instances": {}, "capabilities": {"scalable": {"properties": {"min_instances": 0, "max_instances": -1, "default_instances": 1, "current_instances": 1, "planned_instances": 1}}}, "interfaces": {"int1": {"op1": {"implementation": "scripts/increase.py", "inputs": {}, "executor": null, "max_retries": null, "retry_interval": null, "timeout": null, "timeout_recoverable": null}}}, "relationships": [], "properties": {}, "name": "x", "id": "x", "type_hierarchy": ["Root"], "operations": {"op1": {"plugin": "script", "operation": "script_runner.tasks.run", "executor": "central_deployment_agent", "inputs": {"script_path": "scripts/increase.py"}, "has_intrinsic_functions": false, "max_retries": null, "retry_interval": null, "timeout": null, "timeout_recoverable": null}, "int1.op1": {"plugin": "script", "operation": "script_runner.tasks.run", "executor": "central_deployment_agent", "inputs": {"script_path": "scripts/increase.py"}, "has_intrinsic_functions": false, "max_retries": null, "retry_interval": null, "timeout": null, "timeout_recoverable": null}}, "plugins": [{"source": null, "executor": "central_deployment_agent", "install": false, "install_arguments": null, "package_name": null, "package_version": null, "supported_platform": null, "distribution": null, "distribution_version": null, "distribution_release": null, "name": "script"}], "deployment_plugins_to_install": [{"source": null, "executor": "central_deployment_agent", "install": false, "install_arguments": null, "package_name": null, "package_version": null, "supported_platform": null, "distribution": null, "distribution_version": null, "distribution_release": null, "name": "script"}]}], "relationships": {}, "workflows": {"execute_operation": {"plugin": "default_workflows", "operation": "cloudify.plugins.workflows.execute_operation", "parameters": {"operation": {}, "operation_kwargs": {"default": {}}, "allow_kwargs_override": {"default": null}, "run_by_dependency_order": {"default": false}, "type_names": {"default": []}, "node_ids": {"default": []}, "node_instance_ids": {"default": []}}, "is_cascading": false}}, "policy_types": {}, "policy_triggers": {}, "policies": {}, "groups": {}, "scaling_groups": {}, "inputs": {}, "outputs": {"out1": {"value": {"get_attribute": ["x", "a"]}}}, "deployment_plugins_to_install": [{"source": null, "executor": "central_deployment_agent", "install": false, "install_arguments": null, "package_name": null, "package_version": null, "supported_platform": null, "distribution": null, "distribution_version": null, "distribution_release": null, "name": "script"}], "workflow_plugins_to_install": [{"source": null, "executor": "central_deployment_agent", "install": false, "install_arguments": null, "package_name": null, "package_version": null, "supported_platform": null, "distribution": null, "distribution_version": null, "distribution_release": null, "name": "default_workflows"}], "host_agent_plugins_to_install": [], "version": {"raw": "cloudify_dsl_1_3", "definitions_name": "cloudify_dsl", "definitions_version": [1, 3]}, "capabilities": {}, "imported_blueprints": [], "namespaces_mapping": {}, "data_types": {}, "labels": {}, "blueprint_labels": {}, "deployment_settings": {}, "node_instances": [{"name": "x", "node_id": "x", "id": "x_0nsqzw", "relationships": []}]}, "blueprint_filename": "bp.yaml", "nodes": [{"type": "Root", "instances": {}, "capabilities": {"scalable": {"properties": {"min_instances": 0, "max_instances": -1, "default_instances": 1, "current_instances": 1, "planned_instances": 1}}}, "interfaces": {"int1": {"op1": {"implementation": "scripts/increase.py", "inputs": {}, "executor": null, "max_retries": null, "retry_interval": null, "timeout": null, "timeout_recoverable": null}}}, "relationships": [], "properties": {}, "name": "x", "id": "x", "type_hierarchy": ["Root"], "operations": {"op1": {"plugin": "script", "operation": "script_runner.tasks.run", "executor": "central_deployment_agent", "inputs": {"script_path": "scripts/increase.py"}, "has_intrinsic_functions": false, "max_retries": null, "retry_interval": null, "timeout": null, "timeout_recoverable": null}, "int1.op1": {"plugin": "script", "operation": "script_runner.tasks.run", "executor": "central_deployment_agent", "inputs": {"script_path": "scripts/increase.py"}, "has_intrinsic_functions": false, "max_retries": null, "retry_interval": null, "timeout": null, "timeout_recoverable": null}}, "plugins": [{"source": null, "executor": "central_deployment_agent", "install": false, "install_arguments": null, "package_name": null, "package_version": null, "supported_platform": null, "distribution": null, "distribution_version": null, "distribution_release": null, "name": "script"}], "deployment_plugins_to_install": [{"source": null, "executor": "central_deployment_agent", "install": false, "install_arguments": null, "package_name": null, "package_version": null, "supported_platform": null, "distribution": null, "distribution_version": null, "distribution_release": null, "name": "script"}], "number_of_instances": 1, "deploy_number_of_instances": 1, "min_number_of_instances": 0, "max_number_of_instances": -1}], "inputs": {}, "provider_context": {}, "created_at": {"__datetime__": 1640650832.0}} -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/executions: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "e82b4acf-947c-4119-96c2-165fd6e29781", 4 | "workflow_id": "execute_operation", 5 | "deployment_id": "bp1", 6 | "blueprint_id": "bp1", 7 | "is_dry_run": false, 8 | "status": "terminated", 9 | "status_display": "completed", 10 | "parameters": { 11 | "operation": "int1.op1", 12 | "operation_kwargs": {}, 13 | "allow_kwargs_override": null, 14 | "run_by_dependency_order": false, 15 | "type_names": [], 16 | "node_ids": [], 17 | "node_instance_ids": [] 18 | }, 19 | "created_at": { 20 | "__datetime__": 1640650834.0 21 | }, 22 | "started_at": { 23 | "__datetime__": 1640650834.0 24 | }, 25 | "ended_at": { 26 | "__datetime__": 1640650834.0 27 | }, 28 | "error": null 29 | } 30 | ] -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/node-instances/x_0nsqzw: -------------------------------------------------------------------------------- 1 | {"name": "x", "node_id": "x", "id": "x_0nsqzw", "relationships": [], "version": 1, "runtime_properties": {"a": 1}, "system_properties": {}} -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/payload: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/resources/bp.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_3 2 | 3 | plugins: 4 | script: 5 | executor: central_deployment_agent 6 | install: false 7 | 8 | default_workflows: 9 | executor: central_deployment_agent 10 | install: false 11 | 12 | workflows: 13 | execute_operation: 14 | mapping: default_workflows.cloudify.plugins.workflows.execute_operation 15 | is_cascading: false 16 | parameters: 17 | operation: {} 18 | operation_kwargs: 19 | default: {} 20 | allow_kwargs_override: 21 | default: null 22 | run_by_dependency_order: 23 | default: false 24 | type_names: 25 | default: [] 26 | node_ids: 27 | default: [] 28 | node_instance_ids: 29 | default: [] 30 | 31 | node_types: 32 | Root: {} 33 | 34 | node_templates: 35 | x: 36 | type: Root 37 | interfaces: 38 | int1: 39 | op1: 40 | implementation: scripts/increase.py 41 | 42 | outputs: 43 | out1: 44 | value: {get_attribute: [x, a]} 45 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/resources/scripts/increase.py: -------------------------------------------------------------------------------- 1 | from cloudify import ctx 2 | 3 | a = ctx.instance.runtime_properties.get('a', 0) 4 | ctx.instance.runtime_properties['a'] = a + 1 5 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/old_local_profile/bp1/work/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/old_local_profile/bp1/work/.gitkeep -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/plugins/Cute-Rain-Cloud-with-Rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/cloudify_cli/tests/resources/plugins/Cute-Rain-Cloud-with-Rainbow.png -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/plugins/plugin.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | cool: 3 | executor: central_deployment_agent 4 | source: https://github.com/cloudify-cosmo/cloudify-cool-plugin/archive/7.7.7.zip 5 | package_name: cloudify-cool-plugin 6 | 7 | data_types: 8 | 9 | cloudify.datatypes.cool.Config: 10 | properties: 11 | top_secret_access_key_id: 12 | description: The ID of your TOP SECRET ACCESS KEY. 13 | type: string 14 | required: false -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/profile.yaml: -------------------------------------------------------------------------------- 1 | # a more-or-less complete old-style yaml context, to be used in 2 | # old-context loading tests 3 | !CloudifyProfileContext 4 | _cluster: {} 5 | _profile_name: a 6 | _ssh_port: '22' 7 | kerberos_env: false 8 | manager_ip: 192.0.2.1 9 | manager_password: !!python/unicode admin 10 | manager_tenant: default_tenant 11 | manager_username: admin 12 | provider_context: 13 | cloudify: 14 | cloudify_agent: {broker_port: 5671, heartbeat: 30, log_level: INFO, max_workers: 5, 15 | min_workers: 2} 16 | import_resolver: 17 | parameters: 18 | fallback: true 19 | rules: 20 | - {'http://www.getcloudify.org/spec': 'file:///opt/manager/resources/spec'} 21 | - {'http://cloudify.co/spec': 'file:///opt/manager/resources/spec'} 22 | - {'https://www.getcloudify.org/spec': 'file:///opt/manager/resources/spec'} 23 | - {'https://cloudify.co/spec': 'file:///opt/manager/resources/spec'} 24 | policy_engine: {start_timeout: 30} 25 | rest_certificate: null 26 | rest_port: 80 27 | rest_protocol: http 28 | ssh_key: null 29 | ssh_user: null 30 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/snapshots/snapshot.zip: -------------------------------------------------------------------------------- 1 | test data 2 | -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/dir1/file1: -------------------------------------------------------------------------------- 1 | file1_content -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/dir1/file2: -------------------------------------------------------------------------------- 1 | file2_content -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/dir2/file1: -------------------------------------------------------------------------------- 1 | file1_content -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/dir2/file2: -------------------------------------------------------------------------------- 1 | file2_content -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/dotgit/gitfile: -------------------------------------------------------------------------------- 1 | in case you were wondering, the reason this file is in a directory 2 | called "dotgit" rather than ".git" is because apparently, committing 3 | a directory named ".git" in git is a terrible mess. 4 | 5 | really big git file 6 | oh my god this file is so big 7 | it has so much data in it 8 | i cannot fathom all this data 9 | really big git file 10 | oh my god this file is so big 11 | it has so much data in it 12 | i cannot fathom all this data 13 | really big git file 14 | oh my god this file is so big 15 | it has so much data in it 16 | i cannot fathom all this data 17 | really big git file 18 | oh my god this file is so big 19 | it has so much data in it 20 | i cannot fathom all this data -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/file1: -------------------------------------------------------------------------------- 1 | file1_content -------------------------------------------------------------------------------- /cloudify_cli/tests/resources/storage/manager1/file2: -------------------------------------------------------------------------------- 1 | file2_content -------------------------------------------------------------------------------- /cloudify_cli/tests/test_blueprint.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from mock import patch 5 | from testtools import TestCase 6 | from testtools.matchers import Equals, EndsWith 7 | 8 | from .commands.constants import ( 9 | BLUEPRINTS_DIR, 10 | SAMPLE_ARCHIVE_PATH, 11 | SAMPLE_ARCHIVE_URL, 12 | SAMPLE_BLUEPRINT_PATH, 13 | SAMPLE_CUSTOM_NAME_ARCHIVE, 14 | STUB_DIRECTORY_NAME, 15 | ) 16 | from cloudify_cli import blueprint 17 | from cloudify_cli.exceptions import CloudifyCliError 18 | 19 | 20 | class TestGet(TestCase): 21 | 22 | """Test get a blueprint.""" 23 | 24 | def test_yaml_path(self): 25 | """Get a blueprint from a yaml file.""" 26 | self.assertThat( 27 | blueprint.get(SAMPLE_BLUEPRINT_PATH), 28 | Equals(SAMPLE_BLUEPRINT_PATH), 29 | ) 30 | 31 | @patch('cloudify_cli.blueprint.os.path.isfile') 32 | @patch('cloudify_cli.blueprint.os.listdir') 33 | @patch('cloudify_cli.blueprint.utils.extract_archive') 34 | def test_archive_default_name(self, extract_archive, listdir, isfile): 35 | """Get a blueprint from a zip file.""" 36 | extract_archive.return_value = '/tmp' 37 | listdir.return_value = ['directory'] 38 | isfile.return_value = True 39 | self.assertThat( 40 | blueprint.get(SAMPLE_ARCHIVE_PATH), 41 | Equals('/tmp/directory/blueprint.yaml'), 42 | ) 43 | 44 | @patch('cloudify_cli.blueprint.os.path.isfile') 45 | @patch('cloudify_cli.blueprint.os.listdir') 46 | @patch('cloudify_cli.blueprint.utils.extract_archive') 47 | def test_archive_custom_name(self, extract_archive, listdir, isfile): 48 | """Get a blueprint with a custom name from a zip file.""" 49 | extract_archive.return_value = '/tmp' 50 | listdir.return_value = ['directory'] 51 | isfile.return_value = True 52 | self.assertThat( 53 | blueprint.get(SAMPLE_CUSTOM_NAME_ARCHIVE, 'simple_blueprint.yaml'), 54 | Equals('/tmp/directory/simple_blueprint.yaml'), 55 | ) 56 | 57 | @patch('cloudify_cli.blueprint.os.path.isfile') 58 | @patch('cloudify_cli.blueprint.os.listdir') 59 | @patch('cloudify_cli.blueprint.utils.extract_archive') 60 | def test_archive_custom_name_no_default( 61 | self, extract_archive, listdir, isfile): 62 | """Fail to get blueprint with a custom name from a zip file.""" 63 | extract_archive.return_value = '/tmp' 64 | listdir.return_value = ['directory'] 65 | isfile.return_value = False 66 | self.assertRaises( 67 | CloudifyCliError, 68 | blueprint.get, 69 | SAMPLE_CUSTOM_NAME_ARCHIVE 70 | ) 71 | 72 | def test_url_default_name(self): 73 | """Skip URL download.""" 74 | self.assertThat( 75 | blueprint.get(SAMPLE_ARCHIVE_URL), 76 | Equals(SAMPLE_ARCHIVE_URL), 77 | ) 78 | 79 | def test_url_custom_name(self): 80 | """Ignore custom name in URL.""" 81 | self.assertThat( 82 | blueprint.get(SAMPLE_ARCHIVE_URL, 'ec2-blueprint.yaml'), 83 | Equals(SAMPLE_ARCHIVE_URL), 84 | ) 85 | 86 | def test_bad_filename(self): 87 | """Fail to get blueprint from a yaml file that doesn't exist.""" 88 | self.assertRaises( 89 | CloudifyCliError, 90 | blueprint.get, 91 | 'bad_filename.yaml' 92 | ) 93 | 94 | def test_github_path(self): 95 | """Map github repository path to URL.""" 96 | # Can't check the whole path here, as it's a randomly generated temp 97 | self.assertThat( 98 | blueprint.get('cloudify-cosmo/cloudify-hello-world-example'), 99 | Equals( 100 | 'https://github.com/cloudify-cosmo/' 101 | 'cloudify-hello-world-example/archive/master.tar.gz' 102 | ), 103 | ) 104 | 105 | def test_github_path_custom_name(self): 106 | """Map github repository path to URL and ignore custom name.""" 107 | self.assertThat( 108 | blueprint.get( 109 | 'cloudify-cosmo/cloudify-hello-world-example', 110 | 'ec2-blueprint.yaml' 111 | ), 112 | Equals( 113 | 'https://github.com/cloudify-cosmo/' 114 | 'cloudify-hello-world-example/archive/master.tar.gz' 115 | ), 116 | ) 117 | 118 | @patch('cloudify_cli.utils.download_file') 119 | def test_github_path_download(self, download_file): 120 | """Map github repository path to URL and download by url""" 121 | download_file.return_value = os.path.join( 122 | BLUEPRINTS_DIR, 123 | 'cloudify-hello-world-example-master.zip' 124 | ) 125 | download_file.side_effect = None 126 | blueprint_path = blueprint.get( 127 | 'cloudify-cosmo/cloudify-hello-world-example', 128 | 'openstack-blueprint.yaml', 129 | download=True 130 | ) 131 | self.addCleanup( 132 | shutil.rmtree, 133 | os.path.dirname(os.path.dirname(blueprint_path)) 134 | ) 135 | self.assertThat( 136 | blueprint_path, 137 | EndsWith( 138 | 'cloudify-hello-world-example-master/openstack-blueprint.yaml' 139 | ), 140 | ) 141 | self.assertEqual(download_file.call_count, 1) 142 | self.assertIn( 143 | 'https://github.com/cloudify-cosmo/' 144 | 'cloudify-hello-world-example/archive/master.tar.gz', 145 | download_file.call_args.args 146 | ) 147 | 148 | 149 | class TestGenerateId(TestCase): 150 | 151 | """Test generate blueprint id.""" 152 | 153 | def test_generate_id_default(self): 154 | """Generate blueprint id from directory.""" 155 | self.assertThat( 156 | blueprint.generate_id(SAMPLE_BLUEPRINT_PATH), 157 | Equals(STUB_DIRECTORY_NAME), 158 | ) 159 | 160 | def test_generate_id_custom(self): 161 | """Generate blueprint id from directory and custom filename.""" 162 | self.assertThat( 163 | blueprint.generate_id(SAMPLE_BLUEPRINT_PATH, 'test.yaml'), 164 | Equals('{0}.test'.format(STUB_DIRECTORY_NAME)), 165 | ) 166 | 167 | def test_generate_id_in_blueprint_folder(self): 168 | """Generate blueprint id from relative directory.""" 169 | self.assertThat( 170 | blueprint.generate_id(os.path.join('.', SAMPLE_BLUEPRINT_PATH)), 171 | Equals('helloworld'), 172 | ) 173 | -------------------------------------------------------------------------------- /cloudify_cli/tests/test_inputs.py: -------------------------------------------------------------------------------- 1 | import os 2 | from testtools import TestCase 3 | 4 | from cloudify_cli import inputs 5 | from cloudify_cli.exceptions import CloudifyCliError 6 | 7 | 8 | class InputsToDictTest(TestCase): 9 | def test_valid_inline(self): 10 | resources = ['key1=value1;key2=value2'] 11 | result = inputs.inputs_to_dict(resources) 12 | self.assertDictEqual(result, {'key1': 'value1', 13 | 'key2': 'value2'}) 14 | 15 | def test_inline_not_dict(self): 16 | resources = ['key1failure'] 17 | self._verify_not_dict(resources) 18 | 19 | def test_invalid_yaml(self): 20 | resources = [os.path.join(os.path.dirname(__file__), 21 | 'resources', 22 | 'inputs', 23 | 'bad_format.yaml')] 24 | self._verify_root_cause(resources) 25 | 26 | def test_yaml_not_dict(self): 27 | resources = [os.path.join(os.path.dirname(__file__), 28 | 'resources', 29 | 'inputs', 30 | 'not_dict.yaml')] 31 | self._verify_not_dict(resources) 32 | 33 | def _verify_root_cause(self, resources): 34 | with self.assertRaisesRegex(CloudifyCliError, 'Root cause'): 35 | inputs.inputs_to_dict(resources) 36 | 37 | def _verify_not_dict(self, resources): 38 | with self.assertRaisesRegex( 39 | CloudifyCliError, 'does not represent a dictionary'): 40 | inputs.inputs_to_dict(resources) 41 | -------------------------------------------------------------------------------- /cloudify_cli/tests/test_multiple_local_profiles.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | import os 18 | 19 | from .. import env 20 | from .commands.constants import ( 21 | BLUEPRINTS_DIR, 22 | DEFAULT_BLUEPRINT_FILE_NAME, 23 | ) 24 | from testtools.matchers import DirExists 25 | 26 | from cloudify_cli.tests.commands.test_base import CliCommandTest 27 | 28 | 29 | class TestMultipleLocalProfiles(CliCommandTest): 30 | """Verify that multple local profiles can be used.""" 31 | 32 | LOCAL_BLUEPRINT_PATH = os.path.join( 33 | BLUEPRINTS_DIR, 34 | 'local', 35 | DEFAULT_BLUEPRINT_FILE_NAME, 36 | ) 37 | 38 | def test_default_blueprint_id(self): 39 | """Default blueprint id is the directory name.""" 40 | self.invoke('init {0}'.format(self.LOCAL_BLUEPRINT_PATH)) 41 | self.assertThat( 42 | os.path.join(env.PROFILES_DIR, 'local', 'blueprints', 'local'), 43 | DirExists(), 44 | ) 45 | 46 | def test_blueprint_id(self): 47 | """Blueprint id passed as argument is used.""" 48 | self.invoke( 49 | 'init -b my-blueprint {0}'.format(self.LOCAL_BLUEPRINT_PATH)) 50 | self.assertThat( 51 | os.path.join( 52 | env.PROFILES_DIR, 'local', 'blueprints', 'my-blueprint'), 53 | DirExists(), 54 | ) 55 | 56 | def test_multiple_blueprints(self): 57 | """Multiple blueprints with different id can coexist.""" 58 | blueprint_count = 5 59 | 60 | for blueprint_number in range(blueprint_count): 61 | self.invoke( 62 | 'init -b my-blueprint-{0} {1}' 63 | .format(blueprint_number, self.LOCAL_BLUEPRINT_PATH) 64 | ) 65 | for blueprint_number in range(blueprint_count): 66 | self.assertThat( 67 | os.path.join( 68 | env.PROFILES_DIR, 'local', 'blueprints', 69 | 'my-blueprint-{0}'.format(blueprint_number), 70 | ), 71 | DirExists(), 72 | ) 73 | -------------------------------------------------------------------------------- /cloudify_cli/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############ 16 | 17 | from testtools import TestCase 18 | from testtools.matchers import Equals 19 | 20 | from mock import ( 21 | mock_open, 22 | patch, 23 | ) 24 | from requests.exceptions import ConnectionError 25 | 26 | from ..exceptions import CloudifyCliError 27 | from ..utils import download_file 28 | 29 | 30 | class DownloadFileTest(TestCase): 31 | 32 | """Download file test cases.""" 33 | 34 | def setUp(self): 35 | """Initialize mock objects.""" 36 | super(DownloadFileTest, self).setUp() 37 | 38 | self.expected_destination = '' 39 | 40 | tempfile_patcher = patch('cloudify_cli.utils.tempfile') 41 | tempfile = tempfile_patcher.start() 42 | tempfile.mkstemp.side_effect = [('', self.expected_destination)] 43 | self.addCleanup(tempfile_patcher.stop) 44 | 45 | get_patcher = patch('cloudify_cli.utils.requests.get') 46 | self.get = get_patcher.start() 47 | self.addCleanup(get_patcher.stop) 48 | 49 | open_patcher = patch( 50 | 'cloudify_cli.utils.open', mock_open(), create=True) 51 | self.open = open_patcher.start() 52 | self.addCleanup(open_patcher.stop) 53 | 54 | # Avoid errors when os.close(fd) is called 55 | os_patcher = patch('cloudify_cli.utils.os') 56 | os_patcher.start() 57 | self.addCleanup(os_patcher.stop) 58 | 59 | # Disable logger output when running test cases 60 | logger_patcher = patch('cloudify_cli.utils.get_logger') 61 | self.logger = logger_patcher.start() 62 | self.addCleanup(logger_patcher.stop) 63 | 64 | def test_download_success(self): 65 | """Download file successfully.""" 66 | destination = download_file('some_url') 67 | self.assertThat(destination, Equals(self.expected_destination)) 68 | 69 | def test_download_connection_error(self): 70 | """CloudifyCliError is raised on ConnectionError.""" 71 | self.get.side_effect = ConnectionError 72 | self.assertRaises(CloudifyCliError, download_file, 'some_url') 73 | 74 | def test_download_ioerror(self): 75 | """CloudifyCliError is raised on IOError.""" 76 | self.get().iter_content.return_value = [''] 77 | self.open().__enter__().write.side_effect = IOError 78 | self.assertRaises(CloudifyCliError, download_file, 'some_url') 79 | -------------------------------------------------------------------------------- /jenkins/Jenkinsfile: -------------------------------------------------------------------------------- 1 | def install_test_dependencies() { 2 | echo 'installing test dependencies' 3 | sh """#!/bin/bash 4 | ~/venv/bin/pip install -r requirements.txt 5 | ~/venv/bin/pip install -r test-requirements.txt 6 | ~/venv/bin/pip install -e . 7 | """ 8 | } 9 | def pytest(){ 10 | echo 'running pytest' 11 | sh ''' 12 | ~/venv/bin/pytest \ 13 | --cov-report term-missing \ 14 | --cov=cloudify_cli \ 15 | cloudify_cli/tests \ 16 | --junitxml=test-results/cloudify_cli.xml 17 | ''' 18 | } 19 | 20 | def doGetVersion(){ 21 | sh(script: '''#!/bin/sh -e 22 | . cloudify-cli/packaging/version_info 23 | echo ${CLOUDIFY_VERSION} 24 | ''', label: 'get package version', returnStdout: true).trim() 25 | } 26 | 27 | def doGetPreRelease(){ 28 | sh(script: '''#!/bin/sh -e 29 | . cloudify-cli/packaging/version_info 30 | echo ${CLOUDIFY_PACKAGE_RELEASE} 31 | ''', label: 'get package release', returnStdout: true).trim() 32 | } 33 | 34 | 35 | @Library('pipeline-shared-library') _ 36 | pipeline { 37 | agent { 38 | kubernetes { 39 | defaultContainer 'jnlp' 40 | yamlFile 'jenkins/build-pod.yaml' 41 | } 42 | } 43 | 44 | options { 45 | checkoutToSubdirectory('cloudify-cli') 46 | buildDiscarder(logRotator(numToKeepStr:'10')) 47 | timeout(time: 60, unit: 'MINUTES') 48 | timestamps() 49 | } 50 | environment { 51 | PATH = "/root/.local/bin:$PATH" 52 | PROJECT = 'cloudify-cli' 53 | WORKSPACE = "${env.WORKSPACE}" 54 | PROJECT_DIR = "${env.WORKSPACE}/project" 55 | BUILD_DIR = "/tmp/build" 56 | RESULT_DIR = "/tmp/result" 57 | CLOUDIFY_PACKAGE_RELEASE = doGetPreRelease() 58 | CLOUDIFY_VERSION = doGetVersion() 59 | S3_BASE_URL = "${env.CLOUDIFY_VERSION}/${env.CLOUDIFY_PACKAGE_RELEASE}-build/${env.PROJECT}/${env.BRANCH_NAME}" 60 | S3_BUILD_PATH = "${env.S3_BASE_URL}/${env.BUILD_NUMBER}" 61 | S3_LATEST_BUILD_PATH = "${env.S3_BASE_URL}/latest" 62 | } 63 | 64 | stages { 65 | stage('flake8, build rpm and deb-build') { 66 | parallel { 67 | stage('flake8') { 68 | steps { 69 | sh script: "mkdir -p ${env.WORKSPACE}/flake8 && cp -rf ${env.WORKSPACE}/${env.PROJECT}/. ${env.WORKSPACE}/flake8", label: "copying repo to separate workspace" 70 | container('py311') { 71 | dir("${env.WORKSPACE}/flake8") { 72 | echo 'Install and run flake8' 73 | sh ''' 74 | pip install flake8 --user 75 | flake8 cloudify_cli 76 | ''' 77 | } 78 | } 79 | } 80 | } 81 | stage('build-rpm') { 82 | steps { 83 | container('rpmbuild') { 84 | echo 'Copying repo to separate workspace' 85 | sh """ 86 | cd ~/rpmbuild 87 | git clone --single-branch --branch ${env.BRANCH_NAME} https://github.com/cloudify-cosmo/cloudify-cli.git SOURCES && cd SOURCES 88 | """ 89 | echo 'building dependencies and rpm' 90 | buildRpm('~/rpmbuild/SOURCES', 'cloudify-cli.spec', "${env.CLOUDIFY_VERSION}", "${env.CLOUDIFY_PACKAGE_RELEASE}", "7", "x86_64") 91 | sh script:("mkdir -p ${env.WORKSPACE}/rpm_artifacts && cp -rf /root/rpmbuild/RPMS/x86_64/. ${env.WORKSPACE}/rpm_artifacts"), label: "Copy RPM to rpm_artifacts folder" 92 | } 93 | } 94 | post { 95 | success { 96 | uploadToReleaseS3("${env.WORKSPACE}/rpm_artifacts/","${env.S3_BUILD_PATH}") 97 | uploadToReleaseS3("${env.WORKSPACE}/rpm_artifacts/","${env.S3_LATEST_BUILD_PATH}") 98 | archiveArtifacts '**/rpm_artifacts/*.rpm' 99 | } 100 | } 101 | } 102 | stage('build-deb') { 103 | steps { 104 | sh script: "mkdir -p ${env.WORKSPACE}/project && cp -rf ${env.WORKSPACE}/${env.PROJECT}/. ${env.WORKSPACE}/project", label: "copying repo to separate workspace" 105 | container('debbuild') { 106 | sh script: 'mkdir /tmp/result', label: "create result folder" 107 | sh script: "/bin/bash ${env.WORKSPACE}/project/packaging/debian/build.sh", label: "build the deb package" 108 | sh script: """ 109 | set -ex 110 | rm -rf /opt/cfy 111 | dpkg -i /tmp/result/*.deb 112 | cfy --version 113 | """, label: "sanity-test the deb package" 114 | sh script:("mkdir -p ${env.WORKSPACE}/deb_artifacts && cp -rf /tmp/result/*.deb ${env.WORKSPACE}/deb_artifacts"), label: "Copy RPM to deb_artifacts folder" 115 | } 116 | } 117 | post { 118 | success { 119 | uploadToReleaseS3("${env.WORKSPACE}/deb_artifacts/","${env.S3_BUILD_PATH}") 120 | uploadToReleaseS3("${env.WORKSPACE}/deb_artifacts/","${env.S3_LATEST_BUILD_PATH}") 121 | archiveArtifacts '**/deb_artifacts/*.deb' 122 | } 123 | } 124 | } 125 | } 126 | } 127 | stage('tests') { 128 | steps { 129 | sh script: "mkdir -p ${env.WORKSPACE}/test_py311 && cp -rf ${env.WORKSPACE}/${env.PROJECT}/. ${env.WORKSPACE}/test_py311", label: "copying repo to separate workspace" 130 | container('py311') { 131 | dir("${env.WORKSPACE}/test_py311") { 132 | echo 'create virtual env' 133 | sh 'virtualenv ~/venv' 134 | install_test_dependencies() 135 | pytest() 136 | } 137 | } 138 | } 139 | post { 140 | always { 141 | junit '**/test-results/*.xml' 142 | } 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /jenkins/build-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | spec: 4 | containers: 5 | - name: jnlp 6 | image: jenkins/inbound-agent:4.11.2-2 7 | resources: 8 | limits: 9 | cpu: 0.2 10 | memory: 256Mi 11 | - name: py311 12 | image: 263721492972.dkr.ecr.eu-west-1.amazonaws.com/cloudify-python3.11 13 | resources: 14 | requests: 15 | cpu: 0.8 16 | memory: 1Gi 17 | limits: 18 | cpu: 2.5 19 | memory: 2Gi 20 | command: 21 | - cat 22 | tty: true 23 | securityContext: 24 | runAsUser: 0 25 | privileged: true 26 | - name: rpmbuild 27 | image: 263721492972.dkr.ecr.eu-west-1.amazonaws.com/cloudify-rpmbuild 28 | command: 29 | - cat 30 | tty: true 31 | securityContext: 32 | runAsUser: 0 33 | privileged: true 34 | resources: 35 | requests: 36 | cpu: 0.8 37 | memory: 1Gi 38 | limits: 39 | cpu: 2.5 40 | memory: 2Gi 41 | - name: debbuild 42 | image: ubuntu:18.04 43 | command: 44 | - cat 45 | tty: true 46 | securityContext: 47 | runAsUser: 0 48 | privileged: true 49 | resources: 50 | requests: 51 | cpu: 0.8 52 | memory: 1Gi 53 | limits: 54 | cpu: 2.5 55 | memory: 2Gi 56 | - name: awscli 57 | image: amazon/aws-cli 58 | command: 59 | - cat 60 | tty: true 61 | resources: 62 | limits: 63 | cpu: 0.2 64 | memory: 256Mi 65 | imagePullSecrets: 66 | - name: dockerhub 67 | nodeSelector: 68 | instance-type: spot-xlarge 69 | -------------------------------------------------------------------------------- /packaging/Vagrantfile: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | # -*- mode: ruby -*- 17 | # vi: set ft=ruby : 18 | 19 | AWS_ACCESS_KEY_ID = ENV['AWS_ACCESS_KEY_ID'] 20 | AWS_ACCESS_KEY = ENV['AWS_ACCESS_KEY'] 21 | 22 | GITHUB_USERNAME = ENV['GITHUB_USERNAME'] 23 | GITHUB_TOKEN = ENV['GITHUB_TOKEN'] 24 | BRANCH = ENV['BRANCH'] 25 | REPO = ENV['REPO'] 26 | 27 | Vagrant.configure('2') do |config| 28 | config.vm.define "debian_wheezy_x64" do |debian_wheezy_x64| 29 | #dummy box, will be overriden 30 | debian_wheezy_x64.vm.box = "dummy" 31 | debian_wheezy_x64.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" 32 | debian_wheezy_x64.vm.provider :aws do |aws, override| 33 | aws.access_key_id = AWS_ACCESS_KEY_ID 34 | aws.secret_access_key = AWS_ACCESS_KEY 35 | # official debian wheezy 64bit with omnibus installed 36 | aws.ami = "ami-61e56916" 37 | aws.region = "eu-west-1" 38 | aws.instance_type = "m3.large" 39 | aws.keypair_name = "vagrant_build" 40 | override.ssh.username = "admin" 41 | override.ssh.private_key_path = "~/.ssh/aws/vagrant_build.pem" 42 | override.nfs.functional = false 43 | aws.tags = { "Name" => "vagrant cli debian_wheezy_x64 build" } 44 | aws.security_groups = "vagrant_linux_build" 45 | end 46 | debian_wheezy_x64.vm.provision "shell" do |s| 47 | s.path = "linux/provision.sh" 48 | s.args = "#{GITHUB_USERNAME} #{GITHUB_TOKEN} #{AWS_ACCESS_KEY_ID} #{AWS_ACCESS_KEY} #{REPO} #{BRANCH}" 49 | s.privileged = false 50 | end 51 | end 52 | 53 | config.vm.define "debian_stretch_x64" do |debian_stretch_x64| 54 | #dummy box, will be overriden 55 | debian_stretch_x64.vm.box = "dummy" 56 | debian_stretch_x64.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" 57 | debian_stretch_x64.vm.provider :aws do |aws, override| 58 | aws.access_key_id = AWS_ACCESS_KEY_ID 59 | aws.secret_access_key = AWS_ACCESS_KEY 60 | # official debian stretch 64bit with omnibus installed 61 | aws.ami = "ami-01820e22b83de8d0d" 62 | aws.region = "eu-west-1" 63 | aws.instance_type = "m3.large" 64 | aws.keypair_name = "vagrant_build" 65 | override.ssh.username = "admin" 66 | override.ssh.private_key_path = "~/.ssh/aws/vagrant_build.pem" 67 | override.nfs.functional = false 68 | aws.tags = { "Name" => "vagrant cli debian_stretch_x64 build" } 69 | aws.security_groups = "vagrant_linux_build" 70 | end 71 | debian_stretch_x64.vm.provision "shell" do |s| 72 | s.path = "linux/provision.sh" 73 | s.args = "#{GITHUB_USERNAME} #{GITHUB_TOKEN} #{AWS_ACCESS_KEY_ID} #{AWS_ACCESS_KEY} #{REPO} #{BRANCH}" 74 | s.privileged = false 75 | end 76 | end 77 | 78 | config.vm.define "windows_cli" do |windows| 79 | windows.vm.box = "dummy" 80 | windows.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" 81 | windows.vm.guest = :windows 82 | windows.vm.provider :aws do |aws, override| 83 | aws.access_key_id = AWS_ACCESS_KEY_ID 84 | aws.secret_access_key = AWS_ACCESS_KEY 85 | # this a pre-baked AMI, not pure base image 86 | aws.ami = "ami-05662176" 87 | aws.region = "eu-west-1" 88 | aws.instance_type = "m3.large" 89 | aws.keypair_name = "vagrant_build" 90 | override.ssh.username = "Administrator" 91 | override.ssh.private_key_path = "~/.ssh/aws/vagrant_build.pem" 92 | override.nfs.functional = false 93 | aws.tags = { "Name" => "vagrant windows cli build" } 94 | aws.security_groups = "vagrant_windows" 95 | end 96 | windows.vm.synced_folder ".", "/vagrant", disabled: true 97 | windows.vm.synced_folder "./windows/packaging", "/home/Administrator/packaging" 98 | windows.vm.provision "file", source: "./source_branch", destination: "/home/Administrator/packaging/source_branch" 99 | # shell provisioning uses bash, so use cmd to run batch script 100 | windows.vm.provision "shell" do |shell| 101 | shell.path = 'windows/provision.sh' 102 | shell.args = "#{GITHUB_USERNAME} #{GITHUB_TOKEN} #{AWS_ACCESS_KEY_ID} #{AWS_ACCESS_KEY} #{REPO} #{BRANCH}" 103 | shell.privileged = false 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /packaging/cloudify-cli.spec: -------------------------------------------------------------------------------- 1 | %define __python /opt/cfy/bin/python 2 | %define _cli_env /opt/cfy 3 | %define __find_provides %{nil} 4 | %define __find_requires %{nil} 5 | %define _use_internal_dependency_generator 0 6 | 7 | # Prevent mangling shebangs (RH8 build default), which fails 8 | # with the test files of networkx<2 due to RH8 not having python2. 9 | %if "%{dist}" != ".el7" 10 | %undefine __brp_mangle_shebangs 11 | # Prevent creation of the build ids in /usr/lib, so we can still keep our RPM 12 | # separate from the official RH supplied software (due to a change in RH8) 13 | %define _build_id_links none 14 | %endif 15 | 16 | Name: cloudify-cli 17 | Version: %{CLOUDIFY_VERSION} 18 | Release: %{CLOUDIFY_PACKAGE_RELEASE}%{?dist} 19 | Summary: Cloudify CLI 20 | Group: Applications/Multimedia 21 | License: Apache 2.0 22 | URL: https://github.com/cloudify-cosmo/cloudify-cli 23 | Vendor: Cloudify Platform Ltd. 24 | Packager: Cloudify Platform Ltd. 25 | 26 | Source0: https://cloudify-cicd.s3.amazonaws.com/python-build-packages/cfy-python3.11-%{ARCHITECTURE}-cli.tgz 27 | 28 | 29 | %description 30 | Cloudify CLI 31 | 32 | %prep 33 | sudo tar xf %{S:0} -C / 34 | 35 | %build 36 | 37 | # Create the venv with the custom Python symlinked in 38 | /opt/cfy/python3.11/bin/python3.11 -m venv %_cli_env 39 | 40 | %_cli_env/bin/pip install --upgrade pip setuptools 41 | %_cli_env/bin/pip install -r "${RPM_SOURCE_DIR}/requirements.txt" 42 | %_cli_env/bin/pip install "${RPM_SOURCE_DIR}" 43 | 44 | 45 | %install 46 | mkdir -p %{buildroot}/opt 47 | mkdir -p %{buildroot}/usr/bin 48 | mv %_cli_env %{buildroot}%_cli_env 49 | ln -s %_cli_env/bin/cfy %{buildroot}/usr/bin/cfy 50 | 51 | %files 52 | /opt/cfy 53 | /usr/bin/cfy 54 | -------------------------------------------------------------------------------- /packaging/debian/build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | [[ -z "${CLOUDIFY_VERSION}" ]] || [[ -z "${CLOUDIFY_PACKAGE_RELEASE}" ]] && { 4 | echo "CLOUDIFY_VERSION and CLOUDIFY_PACKAGE_RELEASE must be set" 5 | exit 1 6 | } 7 | [[ -z "${PROJECT_DIR}" ]] && { 8 | echo "PROJECT_DIR must be set to the directory containing the CLI code" 9 | exit 1 10 | } 11 | [[ -z "${RESULT_DIR}" ]] && { 12 | echo "RESULT_DIR must be set to the directory where the built deb will be moved" 13 | exit 1 14 | } 15 | set -xu 16 | 17 | BUILD_DIR=~/cloudify-cli_${CLOUDIFY_VERSION}-${CLOUDIFY_PACKAGE_RELEASE}_amd64 18 | 19 | apt-get update 20 | apt-get install python3-venv git gcc python3-dev curl zlib1g-dev libffi-dev make -y 21 | mkdir -p "${BUILD_DIR}/DEBIAN" "${BUILD_DIR}/opt" 22 | cat >"${BUILD_DIR}/DEBIAN/control" < 30 | Description: Cloudify's Command Line Interface 31 | EOF 32 | 33 | # Download and untar our python3.11 package 34 | curl https://cloudify-cicd.s3.amazonaws.com/python-build-packages/cfy-python3.11-x86_64-cli.tgz -o cfy-python3.11.tgz 35 | tar xf cfy-python3.11.tgz -C / 36 | 37 | /opt/cfy/python3.11/bin/python3.11 -m venv /opt/cfy 38 | 39 | /opt/cfy/bin/pip install --upgrade pip setuptools 40 | /opt/cfy/bin/pip install -r "${PROJECT_DIR}/requirements.txt" 41 | /opt/cfy/bin/pip install "${PROJECT_DIR}" 42 | cp /opt/cfy "${BUILD_DIR}/opt/cfy" -fr 43 | 44 | mkdir -p "${BUILD_DIR}/usr/bin" 45 | ln -s "/opt/cfy/bin/cfy" "${BUILD_DIR}/usr/bin/cfy" 46 | 47 | dpkg-deb --build "${BUILD_DIR}" 48 | mv ~/*.deb "${RESULT_DIR}" 49 | -------------------------------------------------------------------------------- /packaging/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/packaging/icon.png -------------------------------------------------------------------------------- /packaging/linux/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | export GITHUB_USERNAME=$1 5 | export GITHUB_TOKEN=$2 6 | export AWS_ACCESS_KEY_ID=$3 7 | export AWS_ACCESS_KEY=$4 8 | export REPO=$5 9 | export BRANCH=$6 10 | 11 | # The build will be based on this branch 12 | export CORE_BRANCH="master" 13 | 14 | set +e 15 | . /etc/profile.d/rvm.sh 16 | set -e 17 | 18 | set +x 19 | curl -u ${GITHUB_USERNAME}:${GITHUB_TOKEN} https://raw.githubusercontent.com/cloudify-cosmo/${REPO}/${CORE_BRANCH}/packages-urls/common_build_env.sh -o ./common_build_env.sh && 20 | source common_build_env.sh && 21 | 22 | curl https://raw.githubusercontent.com/cloudify-cosmo/cloudify-common/${CORE_BRANCH}/packaging/common/provision.sh -o ./common-provision.sh && 23 | source common-provision.sh 24 | set -x 25 | 26 | install_common_prereqs 27 | cd packaging/omnibus 28 | export CLI_BRANCH="$CORE_BRANCH" 29 | 30 | # Get Omnibus software from Chef Omnibus repo 31 | rm -rf omnibus-software 32 | git clone https://github.com/chef/omnibus-software.git 33 | pushd omnibus-software 34 | git checkout 1b2dfe467cbc22e0e2e232e2648af3482830bfd7 35 | popd 36 | list_of_omnibus_softwares="gdbm cacerts config_guess gdbm libffi makedepend 37 | ncurses openssl pkg-config-lite setuptools 38 | util-macros version-manifest xproto zlib" 39 | for omnibus_softwate in $list_of_omnibus_softwares 40 | do 41 | if [[ -e omnibus-software/config/software/$omnibus_softwate.rb ]] ; then 42 | cp -r omnibus-software/config/software/$omnibus_softwate.rb \ 43 | config/software/$omnibus_softwate.rb 44 | else 45 | echo "Missing software in Omnibus-Software repo" 46 | exit 47 | fi 48 | 49 | [[ -e omnibus-software/config/patches/$omnibus_softwate ]] && 50 | cp -r omnibus-software/config/patches/$omnibus_softwate config/patches/ 51 | done 52 | 53 | [ ! -d config/templates/ ] && mkdir config/templates/ 54 | cp -r omnibus-software/config/templates/* config/templates/ 55 | curl https://raw.githubusercontent.com/chef/omnibus-software/master/config/software/preparation.rb -o config/software/preparation.rb 56 | curl https://raw.githubusercontent.com/systemizer/omnibus-software/master/config/software/pip.rb -o config/software/pip.rb 57 | 58 | if [[ "$OSTYPE" == "darwin"* ]]; then 59 | grep -l '/opt' config/software/* | xargs sed -i "" 's|/opt|/usr/local/opt|g' 60 | fi 61 | 62 | omnibus build cloudify && result="success" 63 | cd pkg 64 | cat *.json || exit 1 65 | rm -f version-manifest.json 66 | [ $(ls | grep rpm | sed -n 2p ) ] && FILEEXT="rpm" 67 | [ $(ls | grep deb | sed -n 2p ) ] && FILEEXT="deb" 68 | [ $(ls | grep pkg | sed -n 2p ) ] && FILEEXT="pkg" 69 | 70 | #remove the -1 - omnibus set the build_iteration to 1 if it null 71 | file=$(basename $(find . -type f -name "*.$FILEEXT")) 72 | echo "file=$file" 73 | file_no_build=$(echo "$file" | sed 's/\(.*\)-1/\1/' | sed 's/cloudify/cloudify-cli/') 74 | echo "file_no_build=$file_no_build" 75 | mv $file $file_no_build 76 | 77 | if [[ ! -z $BRANCH ]] && [[ "$BRANCH" != "master" ]] ; then 78 | AWS_S3_PATH="$AWS_S3_PATH/$BRANCH" 79 | fi 80 | 81 | [ "$result" == "success" ] && create_md5 $FILEEXT && 82 | [ -z ${AWS_ACCESS_KEY} ] || upload_to_s3 $FILEEXT && upload_to_s3 "md5" && 83 | upload_to_s3 "json" 84 | -------------------------------------------------------------------------------- /packaging/linux/provision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | 5 | export GITHUB_USERNAME=$1 6 | export GITHUB_TOKEN=$2 7 | export AWS_ACCESS_KEY_ID=$3 8 | export AWS_ACCESS_KEY=$4 9 | export REPO=$5 10 | export BRANCH=$6 11 | 12 | # These env vars are being updated by the bump version process 13 | export CORE_TAG_NAME="7.1.0.dev1" 14 | export CORE_BRANCH="master" 15 | 16 | # Linux Preperation 17 | function prepare_linux () { 18 | sudo chmod 777 /opt 19 | if which yum >> /dev/null; then 20 | sudo yum install -y http://opensource.wandisco.com/centos/6/git/x86_64/wandisco-git-release-6-1.noarch.rpm 21 | sudo yum install -y git fakeroot python-devel rpm-build 22 | sudo yum update -y nss 23 | gpg=gpg2 24 | else 25 | sudo apt-get install -y git curl fakeroot python-dev dirmngr 26 | gpg=gpg 27 | fi 28 | 29 | curl -sSL https://rvm.io/pkuczynski.asc | $gpg --import - 30 | $gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys \ 31 | 409B6B1796C275462A1703113804BB82D39DC0E3 \ 32 | 7D2BAF1CF37B13E2069D6956105BD0E739499BDB 33 | curl -sSL https://get.rvm.io | bash -s stable 34 | 35 | if which yum >> /dev/null; then 36 | source /etc/profile.d/rvm.sh 37 | else 38 | source $HOME/.rvm/scripts/rvm 39 | fi 40 | rvm install 2.4.4 && rvm use 2.4.4 41 | gem install bundler -v '=1.16.0' --no-document 42 | gem install mixlib-cli -v 1.7.0 --no-document 43 | gem install ohai -v 14.8.12 --no-document 44 | gem install omnibus -v 6.0.25 --no-document 45 | } 46 | 47 | echo "BRANCH=$BRANCH" 48 | echo "REPO=$REPO" 49 | prepare_linux 50 | 51 | 52 | set +x 53 | 54 | curl -u ${GITHUB_USERNAME}:${GITHUB_TOKEN} https://raw.githubusercontent.com/cloudify-cosmo/${REPO}/${CORE_BRANCH}/packages-urls/common_build_env.sh -o ./common_build_env.sh && 55 | source common_build_env.sh && 56 | 57 | curl https://raw.githubusercontent.com/cloudify-cosmo/cloudify-common/${CORE_BRANCH}/packaging/common/provision.sh -o ./common-provision.sh && 58 | source common-provision.sh 59 | 60 | set -x 61 | 62 | install_common_prereqs & 63 | rm -rf cloudify-cli 64 | git clone https://github.com/cloudify-cosmo/cloudify-cli.git 65 | cd ~/cloudify-cli/packaging/omnibus 66 | export CLI_BRANCH="$CORE_BRANCH" 67 | if [[ "$CORE_BRANCH" != "master" ]]; then 68 | if [[ "$REPO" == "cloudify-versions" ]]; then 69 | source ~/cloudify-cli/packaging/source_branch 70 | fi 71 | git checkout -b $CLI_BRANCH origin/$CLI_BRANCH 72 | else 73 | git checkout $CLI_BRANCH 74 | fi 75 | 76 | if [[ ! -z $BRANCH ]] && [[ "$BRANCH" != "master" ]] && git show-ref --quiet origin/$BRANCH ; then 77 | export CLI_BRANCH="$BRANCH" 78 | git checkout $CLI_BRANCH 79 | AWS_S3_PATH="$AWS_S3_PATH/$BRANCH" 80 | fi 81 | 82 | # Get Omnibus software from Chef Omnibus repo 83 | git clone https://github.com/chef/omnibus-software.git 84 | pushd omnibus-software 85 | git checkout 1b2dfe467cbc22e0e2e232e2648af3482830bfd7 86 | popd 87 | list_of_omnibus_softwares="gdbm cacerts config_guess gdbm libffi makedepend 88 | ncurses openssl pkg-config-lite setuptools 89 | util-macros version-manifest xproto zlib" 90 | for omnibus_softwate in $list_of_omnibus_softwares 91 | do 92 | if [[ -e omnibus-software/config/software/$omnibus_softwate.rb ]] ; then 93 | cp -r omnibus-software/config/software/$omnibus_softwate.rb \ 94 | config/software/$omnibus_softwate.rb 95 | else 96 | echo "Missing software in Omnibus-Software repo" 97 | exit 98 | fi 99 | 100 | [[ -e omnibus-software/config/patches/$omnibus_softwate ]] && 101 | cp -r omnibus-software/config/patches/$omnibus_softwate config/patches/ 102 | done 103 | 104 | [ ! -d config/templates/ ] && mkdir config/templates/ 105 | cp -r omnibus-software/config/templates/* config/templates/ 106 | curl https://raw.githubusercontent.com/chef/omnibus-software/master/config/software/preparation.rb -o config/software/preparation.rb 107 | curl https://raw.githubusercontent.com/systemizer/omnibus-software/master/config/software/pip.rb -o config/software/pip.rb 108 | 109 | if [[ "$OSTYPE" == "darwin"* ]]; then 110 | grep -l '/opt' config/software/* | xargs sed -i "" 's|/opt|/usr/local/opt|g' 111 | fi 112 | 113 | omnibus build cloudify && result="success" 114 | cd pkg 115 | cat *.json || exit 1 116 | rm -f version-manifest.json 117 | [ $(ls | grep rpm | sed -n 2p ) ] && FILEEXT="rpm" 118 | [ $(ls | grep deb | sed -n 2p ) ] && FILEEXT="deb" 119 | [ $(ls | grep pkg | sed -n 2p ) ] && FILEEXT="pkg" 120 | 121 | #remove the -1 - omnibus set the build_iteration to 1 if it null 122 | file=$(basename $(find . -type f -name "*.$FILEEXT")) 123 | echo "file=$file" 124 | file_no_build=$(echo "$file" | sed 's/\(.*\)-1/\1/' | sed 's/cloudify/cloudify-cli/') 125 | echo "file_no_build=$file_no_build" 126 | mv $file $file_no_build 127 | 128 | [ "$result" == "success" ] && create_md5 $FILEEXT && 129 | [ -z ${AWS_ACCESS_KEY} ] || upload_to_s3 $FILEEXT && upload_to_s3 "md5" && 130 | upload_to_s3 "json" 131 | -------------------------------------------------------------------------------- /packaging/source_branch: -------------------------------------------------------------------------------- 1 | export CORE_BRANCH="master" 2 | -------------------------------------------------------------------------------- /packaging/version_info: -------------------------------------------------------------------------------- 1 | export CLOUDIFY_VERSION=7.1.0 2 | # Until GA release this version must start with 0. ! 3 | export CLOUDIFY_PACKAGE_RELEASE=.dev1 4 | -------------------------------------------------------------------------------- /packaging/windows/main.tf: -------------------------------------------------------------------------------- 1 | variable "VERSION" {} 2 | variable "PRERELEASE" {} 3 | variable "password" {} 4 | variable "DEV_BRANCH" {} 5 | 6 | 7 | provider "aws" { 8 | region = "eu-west-1" 9 | } 10 | 11 | data "aws_ami" "windows_cli_builder" { 12 | most_recent = true 13 | 14 | filter { 15 | name = "name" 16 | values = ["Windows_Server-2016-English-Full-Base-*"] 17 | } 18 | 19 | owners = ["801119661308"] # This is the amazon owner ID for a bunch of their marketplace images 20 | } 21 | 22 | resource "aws_instance" "builder" { 23 | ami = "${data.aws_ami.windows_cli_builder.id}" 24 | instance_type = "m3.medium" 25 | iam_instance_profile = "windows_agent_builder" 26 | 27 | tags = { 28 | Name = "Windows Agent Builder" 29 | } 30 | 31 | user_data = <<-EOT 32 | 33 | $thumbprint = (New-SelfSignedCertificate -DnsName "winagentbuild" -CertStoreLocation Cert:\LocalMachine\My).ThumbPrint 34 | cmd.exe /c winrm quickconfig -q 35 | cmd.exe /c winrm set winrm/config '@{MaxTimeoutms="1800000"}' 36 | cmd.exe /c winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' 37 | cmd.exe /c winrm set winrm/config/service '@{AllowUnencrypted="true"}' 38 | cmd.exe /c winrm set winrm/config/service/auth '@{Basic="true"}' 39 | cmd.exe /c winrm create "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`"; Hostname=`"winagentbuild`"; CertificateThumbprint=`"$($Thumbprint)`"}" 40 | cmd.exe /c net stop winrm 41 | cmd.exe /c sc config winrm start= auto 42 | cmd.exe /c net start winrm 43 | netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow 44 | netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow 45 | netsh advfirewall firewall add rule name="RDP for troubleshooting" protocol=TCP dir=in localport=3389 action=allow 46 | $user = [ADSI]"WinNT://localhost/Administrator" 47 | $user.SetPassword("${var.password}") 48 | $user.SetInfo() 49 | # Allow older winrdp clients to connect (because remmina's clipboard sync is being unreliable) 50 | (Get-WmiObject -class Win32_TSGeneralSetting -Namespace root\cimv2\terminalservices -ComputerName $env:ComputerName -Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0) 51 | 52 | EOT 53 | 54 | provisioner "file" { 55 | source = "win_cli_builder.ps1" 56 | destination = "C:\\Users\\Administrator\\win_cli_builder.ps1" 57 | connection { 58 | type = "winrm" 59 | port = 5986 60 | https = true 61 | insecure = true 62 | user = "Administrator" 63 | password = "${var.password}" 64 | } 65 | } 66 | 67 | provisioner "remote-exec" { 68 | inline = [ "powershell.exe -File C:\\Users\\Administrator\\win_cli_builder.ps1 \"${var.VERSION}\" \"${var.PRERELEASE}\" \"${var.DEV_BRANCH}\" \"upload\""] 69 | connection { 70 | type = "winrm" 71 | port = 5986 72 | https = true 73 | insecure = true 74 | user = "Administrator" 75 | password = "${var.password}" 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packaging/windows/packaging/create_install_wizard.iss: -------------------------------------------------------------------------------- 1 | #define AppName "Cloudify CLI" 2 | #define AppVersion GetEnv('VERSION') 3 | #define AppMilestone GetEnv('PRERELEASE') 4 | #define AppBuild GetEnv('BUILD') 5 | #define AppPublisher "Cloudify Platform" 6 | #define AppURL "https://cloudify.co/" 7 | #define PluginsTagName GetEnv('PLUGINS_TAG_NAME') 8 | #define CoreTagName GetEnv('CORE_TAG_NAME') 9 | 10 | [Setup] 11 | AppName={#AppName} 12 | AppVersion={#AppVersion} 13 | AppPublisher={#AppPublisher} 14 | AppPublisherURL={#AppURL} 15 | AppSupportURL={#AppURL} 16 | AppUpdatesURL={#AppURL} 17 | DefaultDirName={commonpf}\Cloudify {#AppVersion}-{#AppMilestone} CLI 18 | DisableProgramGroupPage=yes 19 | DisableDirPage=yes 20 | OutputBaseFilename=cloudify-windows-cli_{#AppVersion}-{#AppMilestone} 21 | Compression=lzma 22 | SolidCompression=yes 23 | ArchitecturesInstallIn64BitMode=x64 arm64 ia64 24 | ArchitecturesAllowed=x64 arm64 ia64 25 | MinVersion=6.0 26 | SetupIconFile=source\icons\Cloudify.ico 27 | UninstallDisplayIcon={app}\Cloudify.ico 28 | OutputDir=output\ 29 | 30 | [Languages] 31 | Name: "english"; MessagesFile: "compiler:Default.isl" 32 | 33 | [Files] 34 | Source: "C:\Program Files\Cloudify {#AppVersion}-{#AppMilestone} CLI\*"; DestDir: "{app}"; Excludes: "\__pycache__\*"; Flags: createallsubdirs recursesubdirs 35 | Source: "source\icons\Cloudify.ico"; DestDir: "{app}" 36 | 37 | [Tasks] 38 | Name: "desktopicon"; Description: "Create a desktop icon"; 39 | 40 | [Icons] 41 | Name: "{commondesktop}\Cloudify {#AppVersion}-{#AppMilestone} CLI"; Filename: "%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe"; Parameters: "-NoExit -Command $env:Path=\""{app}\Scripts\;$env:Path\"";function prompt{{\""[Cloudify {#AppVersion}-{#AppMilestone} CLI] $($executionContext.SessionState.Path.CurrentLocation)>\""}"; WorkingDir: "{app}"; IconFilename: "{app}\Cloudify.ico"; Tasks: "desktopicon"; 42 | -------------------------------------------------------------------------------- /packaging/windows/packaging/source/icons/Cloudify.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudify-cosmo/cloudify-cli/9f14c393f6808837372aab3602b5ded8d4c97189/packaging/windows/packaging/source/icons/Cloudify.ico -------------------------------------------------------------------------------- /packaging/windows/packaging/source/import_resolver.yaml: -------------------------------------------------------------------------------- 1 | 2 | import_resolver: 3 | parameters: 4 | rules: 5 | - {'http://www.getcloudify.org/spec/cloudify/CORE_TAG_NAME/types.yaml': 'file:/CLOUDIFY_PATH/cloudify/types/types.yaml'} 6 | - {'http://www.getcloudify.org/spec/fabric-plugin/1.5.2': 'file:/CLOUDIFY_PATH/cloudify/plugins/fabric-plugin'} 7 | - {'http://www.getcloudify.org/spec/openstack-plugin/2.0.1': 'file:/CLOUDIFY_PATH/cloudify/plugins/openstack-plugin'} 8 | - {'http://www.getcloudify.org/spec/aws-plugin/1.4.10': 'file:/CLOUDIFY_PATH/cloudify/plugins/aws-plugin'} 9 | - {'http://www.getcloudify.org/spec/vsphere-plugin/2.4.0': 'file:/CLOUDIFY_PATH/cloudify/plugins/vsphere-plugin'} 10 | - {'http://www.getcloudify.org/spec/softlayer-plugin/1.3.1': 'file:/CLOUDIFY_PATH/cloudify/plugins/softlayer-plugin'} 11 | -------------------------------------------------------------------------------- /packaging/windows/packaging/update_wheel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | from zipfile import ZipFile, ZIP_DEFLATED 4 | from hashlib import sha256 5 | from wheel.util import urlsafe_b64encode 6 | from collections import namedtuple 7 | 8 | 9 | def get_sha(data): 10 | return urlsafe_b64encode(sha256(data).digest()) 11 | 12 | 13 | def modify_wheel(path, name, data): 14 | with ZipFile(path) as zf: 15 | zf.getinfo(name) 16 | new = ZipFile(path + '-new', 'w', ZIP_DEFLATED) 17 | for item in zf.infolist(): 18 | if item.filename.endswith('dist-info/RECORD'): 19 | records = zf.read(item.filename) 20 | newrecord = generate_record(records, name, data) 21 | new.writestr(item.filename, newrecord) 22 | elif item.filename == name: 23 | new.writestr(name, data) 24 | else: 25 | zipdata = zf.read(item.filename) 26 | new.writestr(item.filename, zipdata) 27 | 28 | 29 | def generate_record(records, name, data): 30 | data_sha = 'sha256=' + get_sha(data) 31 | data_size = str(len(data)) 32 | out = [] 33 | Record = namedtuple('Record', 'name hash size') 34 | for item in records.split(): 35 | record = Record(*item.split(',')) 36 | if record.name != name: 37 | out.append(item) 38 | else: 39 | if not record.hash.startswith('sha256'): 40 | raise Exception('Unexpected checksum method: {0}'.format( 41 | record.hash.split('=')[0])) 42 | out.append(','.join((record.name, data_sha, data_size))) 43 | return '\r\n'.join(out) 44 | 45 | 46 | def parse_args(): 47 | description = """This script will modify wheel file by puting data into 48 | the target inside wheel archive. It will also update the RECORD file 49 | with new checksum and file size""" 50 | parser = argparse.ArgumentParser(description=description) 51 | 52 | parser.add_argument('--path', required=True, help="wheel's file path") 53 | parser.add_argument('--name', required=True, help='name of the target ' 54 | 'file inside wheel') 55 | parser.add_argument('--data', required=True, help='data to write into ' 56 | 'target file') 57 | 58 | return parser.parse_args() 59 | 60 | 61 | def main(): 62 | args = parse_args() 63 | if args.data == '-': 64 | data = sys.stdin.read() 65 | else: 66 | data = args.data 67 | modify_wheel(path=args.path, name=args.name, data=data) 68 | 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /packaging/windows/win_cli_builder.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $VERSION, 3 | $PRERELEASE, 4 | $DEV_BRANCH = "master", 5 | $UPLOAD = "" 6 | ) 7 | Set-StrictMode -Version 1.0 8 | $ErrorActionPreference="stop" 9 | # Use TLSv1.2 for Invoke-Restmethod 10 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 11 | 12 | $CLI_PATH = "C:\Program Files\Cloudify $VERSION-$PRERELEASE CLI" 13 | $GET_PIP_URL = "http://repository.cloudifysource.org/cloudify/components/win-cli-package-resources/get-pip-20.py" 14 | $PIP_VERSION = "20.1.1" 15 | $REPO_URL = "https://github.com/cloudify-cosmo/cloudify-cli/archive/$DEV_BRANCH.zip" 16 | $PY_URL = "https://repository.cloudifysource.org/cloudify/components/python-3.11.1-embed-amd64.zip" 17 | $INNO_SETUP_URL = "http://repository.cloudifysource.org/cloudify/components/win-cli-package-resources/inno_setup_6.exe" 18 | 19 | 20 | function run { 21 | # Use this to get set -e alike behaviour, rather than running commands directly 22 | & $args[0] $args[1..($args.Length)] 23 | if ($LastExitCode -ne 0) { 24 | Write-Error "Error running @args" 25 | } 26 | } 27 | 28 | 29 | function rm_rf { 30 | # Because if you use "-ErrorAction Ignore" then you ignore all errors, not just 31 | # missing targets 32 | if (Test-Path $args[0]) { 33 | Remove-Item -Recurse -Force -Path $args[0] 34 | } 35 | } 36 | 37 | 38 | ### Main ### 39 | Write-Host "Deleting existing artifacts" 40 | rm_rf python.zip 41 | rm_rf get-pip.py 42 | rm_rf "$CLI_PATH" 43 | rm_rf inno_setup.exe 44 | 45 | Write-Host "Checking whether Inno Setup needs installing..." 46 | if (-Not (Test-Path "C:\Program Files (x86)\Inno Setup 6")) { 47 | Write-Host "Inno Setup not installed, downloading from $INNO_SETUP_URL" 48 | Invoke-RestMethod -Uri $INNO_SETUP_URL -OutFile inno_setup.exe 49 | Write-Host "Installing Inno Setup" 50 | # Cannot be invoked by run as it doesn't set LastExitCode 51 | & .\inno_setup.exe /VERYSILENT /SUPPRESSMSGBOXES 52 | } else { 53 | Write-Host "Inno Setup is already installed." 54 | } 55 | 56 | 57 | # We use . because passing "" to the script causes the default to be used 58 | if ( $DEV_BRANCH -ne "." ) { 59 | Write-Host "Deleting existing downloaded CLI." 60 | rm_rf cloudify-cli.zip 61 | rm_rf cloudify-cli 62 | Write-Host "Getting CLI repository from $REPO_URL" 63 | Invoke-RestMethod -Uri $REPO_URL -OutFile cloudify-cli.zip 64 | Expand-Archive -Path cloudify-cli.zip 65 | pushd cloudify-cli 66 | cd cloudify-cli-$DEV_BRANCH 67 | move * .. 68 | cd .. 69 | rm_rf cloudify-cli-$DEV_BRANCH 70 | popd 71 | } else { 72 | Write-Host "Using local cloudify-cli directory." 73 | } 74 | 75 | 76 | Write-Host "Getting embeddable python from $PY_URL" 77 | Invoke-RestMethod -Uri $PY_URL -OutFile python.zip 78 | 79 | Write-Host "Getting get-pip from $GET_PIP_URL" 80 | Invoke-RestMethod -Uri $GET_PIP_URL -OutFile get-pip.py 81 | 82 | 83 | Write-Host "Preparing CLI path" 84 | mkdir $CLI_PATH 85 | Expand-Archive -Path python.zip -DestinationPath $CLI_PATH 86 | 87 | 88 | # We need to expand this to make virtualenv work 89 | pushd "$CLI_PATH" 90 | Expand-Archive -Path python311.zip 91 | rm_rf python311.zip 92 | mkdir Lib 93 | move python311\* Lib 94 | rmdir python311 95 | popd 96 | 97 | 98 | Write-Host "Adding pip to embedded python" 99 | Set-Content -Path "$CLI_PATH\python311._pth" -Value ". 100 | .\Lib 101 | .\Lib\site-packages 102 | 103 | import site" 104 | run $CLI_PATH\python.exe get-pip.py pip==$PIP_VERSION 105 | 106 | 107 | Write-Host "Installing CLI" 108 | pushd cloudify-cli 109 | run $CLI_PATH\python.exe -m pip install --upgrade pip 110 | run $CLI_PATH\scripts\pip.exe install --upgrade wheel 111 | run $CLI_PATH\scripts\pip.exe install --upgrade setuptools 112 | run $CLI_PATH\scripts\pip.exe install --prefix="$CLI_PATH" -r requirements.txt 113 | run $CLI_PATH\scripts\pip.exe install --prefix="$CLI_PATH" . 114 | popd 115 | 116 | 117 | Write-Host "Building CLI package" 118 | $env:VERSION = $VERSION 119 | $env:PRERELEASE = $PRERELEASE 120 | run "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" cloudify-cli\packaging\windows\packaging\create_install_wizard.iss 121 | 122 | 123 | if ( $UPLOAD -eq "upload" ) { 124 | Write-Host "Preparing AWS CLI" 125 | run "$CLI_PATH\Scripts\pip.exe" install --prefix="$CLI_PATH" awscli 126 | Set-Content -Path "$CLI_PATH\scripts\aws.py" -Value "import awscli.clidriver 127 | import sys 128 | sys.exit(awscli.clidriver.main())" 129 | 130 | Write-Host "Uploading CLI to S3" 131 | pushd cloudify-cli\packaging\windows\packaging\output 132 | $artifact = "cloudify-windows-cli_$env:VERSION-$env:PRERELEASE.exe" 133 | $artifact_md5 = $(Get-FileHash -Path $artifact -Algorithm MD5).Hash 134 | $s3_path = "s3://cloudify-release-eu/cloudify/$env:VERSION/$env:PRERELEASE-build" 135 | run "$CLI_PATH\python.exe" "$CLI_PATH\Scripts\aws.py" s3 cp .\ $s3_path --acl public-read --recursive 136 | popd 137 | } else { 138 | Write-Host "Not uploading as upload is set to '$UPLOAD' instead of 'upload'." 139 | } 140 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | cloudify-common[dispatcher] @ https://github.com/cloudify-cosmo/cloudify-common/archive/master.zip 2 | urllib3 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --no-emit-index-url --output-file=requirements.txt requirements.in setup.py 6 | # 7 | aiohttp==3.9.5 8 | # via cloudify-common 9 | aiosignal==1.3.1 10 | # via aiohttp 11 | attrs==23.2.0 12 | # via aiohttp 13 | backports-shutil-get-terminal-size==1.0.0 14 | # via cloudify (setup.py) 15 | bcrypt==4.2.0 16 | # via paramiko 17 | bottle==0.12.25 18 | # via cloudify-common 19 | certifi==2024.7.4 20 | # via requests 21 | cffi==1.16.0 22 | # via 23 | # cryptography 24 | # pynacl 25 | charset-normalizer==3.3.2 26 | # via requests 27 | click==8.1.7 28 | # via 29 | # click-didyoumean 30 | # cloudify (setup.py) 31 | click-didyoumean==0.3.1 32 | # via cloudify (setup.py) 33 | cloudify-common[dispatcher] @ https://github.com/cloudify-cosmo/cloudify-common/archive/master.zip 34 | # via 35 | # -r requirements.in 36 | # cloudify (setup.py) 37 | colorama==0.4.6 38 | # via cloudify (setup.py) 39 | cryptography==43.0.0 40 | # via 41 | # cloudify (setup.py) 42 | # paramiko 43 | decorator==5.1.1 44 | # via fabric 45 | deprecated==1.2.14 46 | # via fabric 47 | distro==1.9.0 48 | # via cloudify-common 49 | fabric==3.2.2 50 | # via cloudify (setup.py) 51 | fasteners==0.19 52 | # via cloudify-common 53 | frozenlist==1.4.1 54 | # via 55 | # aiohttp 56 | # aiosignal 57 | idna==3.7 58 | # via 59 | # requests 60 | # yarl 61 | invoke==2.2.0 62 | # via fabric 63 | jinja2==3.1.4 64 | # via cloudify-common 65 | markupsafe==2.1.5 66 | # via jinja2 67 | multidict==6.0.5 68 | # via 69 | # aiohttp 70 | # yarl 71 | networkx==2.8.8 72 | # via cloudify-common 73 | paramiko==3.4.0 74 | # via fabric 75 | pika==1.3.2 76 | # via cloudify-common 77 | pkginfo==1.11.1 78 | # via wagon 79 | proxy-tools==0.1.0 80 | # via cloudify-common 81 | pycparser==2.22 82 | # via cffi 83 | pynacl==1.5.0 84 | # via paramiko 85 | pytz==2024.1 86 | # via cloudify-common 87 | pyyaml==6.0.1 88 | # via cloudify-common 89 | requests==2.32.3 90 | # via 91 | # cloudify (setup.py) 92 | # cloudify-common 93 | # requests-toolbelt 94 | requests-toolbelt==1.0.0 95 | # via cloudify-common 96 | retrying==1.3.4 97 | # via cloudify (setup.py) 98 | six==1.16.0 99 | # via retrying 100 | urllib3==2.2.2 101 | # via 102 | # -r requirements.in 103 | # cloudify-common 104 | # requests 105 | wagon[venv]==1.0.1 106 | # via 107 | # cloudify (setup.py) 108 | # cloudify-common 109 | wheel==0.43.0 110 | # via wagon 111 | wrapt==1.16.0 112 | # via deprecated 113 | yarl==1.9.4 114 | # via aiohttp 115 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ######## 2 | # Copyright (c) 2013-2020 Cloudify Platform Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | from setuptools import setup 17 | 18 | install_requires = [ 19 | 'backports.shutil_get_terminal_size', 20 | 'click', 21 | 'click_didyoumean', 22 | 'cloudify-common[dispatcher]', 23 | 'colorama', 24 | 'cryptography', 25 | 'fabric', 26 | 'requests>=2.32.0,<3', 27 | 'retrying', 28 | 'wagon[venv]', 29 | ] 30 | 31 | packages = [ 32 | 'cloudify_cli', 33 | 'cloudify_cli.cli', 34 | 'cloudify_cli.commands', 35 | 'cloudify_cli.config', 36 | 'cloudify_cli.async_commands', 37 | ] 38 | 39 | setup( 40 | name='cloudify', 41 | version='7.1.0.dev1', 42 | author='Cloudify', 43 | author_email='cosmo-admin@cloudify.co', 44 | packages=packages, 45 | license='LICENSE', 46 | description="Cloudify's Command Line Interface", 47 | entry_points={ 48 | 'console_scripts': [ 49 | 'cfy = cloudify_cli.main:_cfy' 50 | ] 51 | }, 52 | install_requires=install_requires, 53 | ) 54 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | mock>=1.0.1 2 | testtools 3 | coverage==6.4.4 4 | pytest 5 | pytest-cov==2.8.1 6 | decorator==4.4.2 7 | --------------------------------------------------------------------------------