Пример #1
0
    def test_run(self, monkeypatch):
        """Test run method."""
        # TODO test _process_deployments instead of mocking it out
        deployments = [{'modules': ['test'], 'regions': 'us-east-1'}]
        test_config = Config(deployments=deployments, tests=[])
        get_env = MagicMock(return_value='test')

        monkeypatch.setattr(MODULE_PATH + '.select_modules_to_run',
                            lambda a, b, c, d, e: a)
        monkeypatch.setattr(MODULE_PATH + '.get_env', get_env)
        monkeypatch.setattr(Config, 'find_config_file',
                            MagicMock(return_value=os.getcwd() + 'runway.yml'))
        monkeypatch.setattr(ModulesCommand, 'runway_config', test_config)
        monkeypatch.setattr(ModulesCommand, '_process_deployments',
                            lambda obj, y, x: None)

        obj = ModulesCommand(cli_arguments={})

        with environ({}):
            os.environ.pop('CI', None)
            assert not obj.run(test_config.deployments, command='plan')
            os.environ['CI'] = '1'
            assert not obj.run(test_config.deployments, command='plan')

        get_env.assert_has_calls([
            call(os.getcwd(), False, prompt_if_unexpected=True),
            call(os.getcwd(), False, prompt_if_unexpected=False)
        ])
Пример #2
0
    def test_process_deployments(self, mock_merge_nested_environment_dicts,
                                 mock_merge_dicts, fx_config, monkeypatch,
                                 runway_context):
        """Test _process_deployments."""
        config = fx_config.load('min_required')  # single deployment for tests
        deployment = config.deployments[0]
        monkeypatch.setattr(deployment, 'resolve', MagicMock())
        monkeypatch.setattr(ModulesCommand, 'runway_config', config)
        monkeypatch.setattr(ModulesCommand, '_execute_deployment', MagicMock())

        ModulesCommand()._process_deployments(config.deployments,
                                              runway_context)

        deployment.resolve.assert_called_once_with(runway_context,
                                                   config.variables,
                                                   pre_process=True)
        # pylint: disable=no-member
        ModulesCommand._execute_deployment.assert_has_calls([
            call(deployment, runway_context, region)
            for region in deployment.regions
        ])
        mock_merge_nested_environment_dicts.assert_not_called()
        mock_merge_dicts.assert_not_called()

        copy_env_vars = runway_context.env_vars.copy()
        deployment._env_vars = MagicMock()
        deployment._env_vars.value = {'key': 'val'}
        mock_merge_nested_environment_dicts.return_value = {'state': 'env'}
        mock_merge_dicts.return_value = {'state': 'merged'}
        ModulesCommand()._process_deployments(config.deployments,
                                              runway_context)
        mock_merge_nested_environment_dicts.assert_called_once_with(
            {'key': 'val'},
            env_name=runway_context.env_name,
            env_root=os.getcwd())
        mock_merge_dicts.assert_called_once_with(copy_env_vars,
                                                 {'state': 'env'})
        assert runway_context.env_vars == {'state': 'merged'}
        assert ModulesCommand._execute_deployment.call_count == 2

        ModulesCommand._execute_deployment.reset_mock()
        deployment.modules = []
        ModulesCommand({
            '--tag': ['something']
        })._process_deployments(config.deployments, runway_context)
        ModulesCommand._execute_deployment.assert_not_called()

        config = fx_config.load('min_required')
        deployment = config.deployments[0]
        monkeypatch.setattr(deployment, 'resolve', MagicMock())
        deployment._regions = MagicMock()
        deployment._regions.value = []
        with pytest.raises(SystemExit) as excinfo:
            ModulesCommand()._process_deployments(config.deployments,
                                                  runway_context)
        assert excinfo.value.code == 1
Пример #3
0
    def test_reverse_deployments(self, fx_deployments, monkeypatch):
        """Test reverse_deployments."""
        deployment = fx_deployments.load('min_required')
        monkeypatch.setattr(deployment, 'reverse', MagicMock())
        mock_deployments = MagicMock()
        mock_deployments.__iter__.return_value = iter([deployment])

        assert ModulesCommand.reverse_deployments(None) == []
        assert ModulesCommand.reverse_deployments(mock_deployments)
        mock_deployments.reverse.assert_called_once()
        deployment.reverse.assert_called_once()
Пример #4
0
    def test_process_modules_parallel(self, mock_futures, deployment,
                                      use_concurrent, fx_deployments,
                                      monkeypatch, runway_context):
        """Test _process_modules with parallel regions."""
        # pylint: disable=no-member
        deployment = fx_deployments.load(deployment)
        executor = MagicMock()
        mock_futures.ProcessPoolExecutor.return_value = executor
        runway_context.use_concurrent = use_concurrent

        monkeypatch.setattr(ModulesCommand, '_deploy_module', MagicMock())

        assert not ModulesCommand()._process_modules(deployment,
                                                     runway_context)
        if use_concurrent:
            mock_futures.ProcessPoolExecutor.assert_called_once_with(
                max_workers=runway_context.max_concurrent_modules)
            executor.submit.assert_has_calls([
                call(ModulesCommand._deploy_module, x, deployment,
                     runway_context)
                for x in deployment.modules[0].child_modules
            ])
            mock_futures.wait.assert_called_once()
            assert executor.submit.return_value.result.call_count == \
                len(deployment.modules[0].child_modules)
            ModulesCommand._deploy_module.assert_called_once_with(
                deployment.modules[1], deployment, runway_context)
        else:
            ModulesCommand._deploy_module.assert_has_calls([
                call(module, deployment, runway_context)
                for module in deployment.modules[0].child_modules
            ] + [call(deployment.modules[1], deployment, runway_context)])
Пример #5
0
    def test_process_deployments_parallel(self, mock_futures, config,
                                          use_concurrent, fx_config,
                                          monkeypatch, runway_context):
        """Test _process_deployments with parallel regions."""
        # pylint: disable=no-member
        config = fx_config.load(config)  # single deployment for tests
        deployment = config.deployments[0]
        executor = MagicMock()
        mock_futures.ProcessPoolExecutor.return_value = executor
        runway_context.use_concurrent = use_concurrent
        monkeypatch.setattr(deployment, 'resolve', MagicMock())
        monkeypatch.setattr(ModulesCommand, 'runway_config', config)
        monkeypatch.setattr(ModulesCommand, '_execute_deployment', MagicMock())

        ModulesCommand()._process_deployments(config.deployments,
                                              runway_context)

        deployment.resolve.assert_called_once_with(runway_context,
                                                   config.variables,
                                                   pre_process=True)

        if not use_concurrent:
            ModulesCommand._execute_deployment.assert_called()
            return

        mock_futures.ProcessPoolExecutor.assert_called_once_with(
            max_workers=runway_context.max_concurrent_regions)
        executor.submit.has_calls([(ModulesCommand._execute_deployment,
                                    deployment, runway_context, region, True)
                                   for region in deployment.parallel_regions])
        mock_futures.wait.called_once()
        assert executor.submit.return_value.result.call_count == \
            len(deployment.parallel_regions)
Пример #6
0
    def test_select_deployment_to_run(self, config, command, mock_input,
                                      expected, fx_config, monkeypatch):
        """Test select_deployment_to_run."""
        config = fx_config.load(config)
        monkeypatch.setattr(MODULE + '.input', mock_input)

        assert ModulesCommand.select_deployment_to_run([], command) == []

        if isinstance(expected, int):
            with pytest.raises(SystemExit) as excinfo:
                assert not ModulesCommand.select_deployment_to_run(
                    config.deployments, command)
            assert excinfo.value.code == expected
        else:
            result = ModulesCommand.select_deployment_to_run(
                config.deployments, command)
            assert [r.name for r in result] == expected
        if mock_input:
            mock_input.assert_called_once_with(
                'Enter number of deployment to run (or "all"): ')
Пример #7
0
    def test_process_modules(self, deployment, fx_deployments, monkeypatch,
                             runway_context):
        """Test _process_modules."""
        # pylint: disable=no-member
        deployment = fx_deployments.load(deployment)
        monkeypatch.setattr(ModulesCommand, '_deploy_module', MagicMock())

        assert not ModulesCommand()._process_modules(deployment,
                                                     runway_context)
        ModulesCommand._deploy_module.assert_has_calls([
            call(module, deployment, runway_context)
            for module in deployment.modules
        ])
Пример #8
0
    def test_deploy_module_invalid_env(self, mock_runwaymoduletype,
                                       mock_validate_environment,
                                       fx_deployments, monkeypatch,
                                       runway_context):
        """Test _deploy_module where validate_environment fails."""
        deployment = fx_deployments.load('environments_map_str')
        module = deployment.modules[0]

        monkeypatch.setattr(deployment, 'resolve', MagicMock())
        monkeypatch.setattr(module, 'resolve', MagicMock())
        monkeypatch.setattr(MODULE + '.load_module_opts_from_file',
                            lambda x, y: y)  # return module_opts
        mock_validate_environment.return_value = False

        assert not ModulesCommand()._deploy_module(module, deployment,
                                                   runway_context)
        mock_validate_environment.assert_called_once()
        mock_runwaymoduletype.assert_not_called()
Пример #9
0
    def test_deploy_module_missing_method(self, mock_hasattr, fx_deployments,
                                          monkeypatch, runway_context):
        """Test _deploy_module where the command method is missing."""
        deployment = fx_deployments.load('min_required')
        module = deployment.modules[0]

        mock_hasattr.return_value = False
        monkeypatch.setattr(deployment, 'resolve', MagicMock())
        monkeypatch.setattr(module, 'resolve', MagicMock())
        monkeypatch.setattr(MODULE + '.load_module_opts_from_file',
                            lambda x, y: y)  # return module_opts
        monkeypatch.setattr(MODULE + '.change_dir', MagicMock())

        with pytest.raises(SystemExit) as excinfo:
            assert ModulesCommand()._deploy_module(module, deployment,
                                                   runway_context)
        assert excinfo.value.code == 1
        # ANY is a child mock
        mock_hasattr.assert_called_once_with(ANY, runway_context.command)
Пример #10
0
    def test_execute_deployment(self, mock_post_deploy_assume_role,
                                mock_validate_account_credentials,
                                mock_pre_deploy_assume_role, deployment,
                                region, parallel, fx_deployments, monkeypatch,
                                runway_context):
        """Test _execute_deployment."""
        # pylint: disable=no-member
        deployment = fx_deployments.load(deployment)
        monkeypatch.setattr(ModulesCommand, '_process_modules', MagicMock())

        assert not ModulesCommand()._execute_deployment(
            deployment, runway_context, region, parallel)
        ModulesCommand._process_modules.assert_called_once_with(
            deployment,
            # context will be a new instance if parallel
            runway_context if not parallel else ANY)

        if parallel:
            assert runway_context.env_region == 'us-east-1'
        else:
            assert runway_context.env_region == region
            assert runway_context.env_vars['AWS_DEFAULT_REGION'] == region
            assert runway_context.env_vars['AWS_REGION'] == region

        if deployment.assume_role:
            mock_pre_deploy_assume_role.assume_called_once_with(
                deployment.assume_role,
                # context will be a new instance if parallel
                runway_context if not parallel else ANY)
            mock_post_deploy_assume_role.assume_called_once_with(
                deployment.assume_role,
                # context will be a new instance if parallel
                runway_context if not parallel else ANY)
        if deployment.account_id or deployment.account_alias:
            mock_validate_account_credentials.assert_called_once_with(
                deployment,
                # context will be a new instance if parallel
                runway_context if not parallel else ANY)
Пример #11
0
    def test_run(self, mock_context, mock_get_env, mock_select_modules_to_run,
                 config, command, cli_args, mock_input, fx_config,
                 monkeypatch):
        """Test run."""
        config = fx_config.load(config)
        config.future.strict_environments = True

        mock_get_env.return_value = 'test'
        mock_context.return_value = mock_context
        mock_context.is_interactive = bool(mock_input)
        mock_context.is_noninteractive = not bool(mock_input)  # True if None
        mock_context.env_vars = {}

        monkeypatch.setattr(MODULE + '.input', mock_input)
        monkeypatch.setattr(ModulesCommand, 'reverse_deployments',
                            MagicMock(return_value=['reversed-deployments']))
        monkeypatch.setattr(ModulesCommand, 'runway_config', config)
        monkeypatch.setattr(ModulesCommand, 'select_deployment_to_run',
                            MagicMock(return_value=['deployment']))
        monkeypatch.setattr(ModulesCommand, '_process_deployments',
                            MagicMock())

        if command == 'destroy' and mock_input and mock_input.return_value != 'y':
            with pytest.raises(SystemExit) as excinfo:
                ModulesCommand(cli_args).run(command=command)
            mock_input.assert_called_once_with('Proceed?: ')
            assert excinfo.value.code == 0
            return  # end here since a lot of the rest will be skipped

        env_vars = {}
        if not mock_input:
            env_vars['CI'] = '1'

        with environ(env_vars):
            ModulesCommand(cli_args).run(command=command)

            mock_get_env.assert_called_once_with(
                os.getcwd(),
                config.ignore_git_branch,
                prompt_if_unexpected=bool(mock_input))
            mock_context.assert_called_once_with(env_name='test',
                                                 env_region=None,
                                                 env_root=os.getcwd(),
                                                 env_vars=os.environ,
                                                 command=command)

        assert mock_context.env_vars['RUNWAYCONFIG'] == './runway.yml'

        if command == 'destroy' and mock_input:
            mock_input.assert_called_once_with('Proceed?: ')
            ModulesCommand.reverse_deployments.assert_called_once()

        if mock_context.is_noninteractive or cli_args.get('--tag'):
            mock_select_modules_to_run.assert_called()
        else:
            ModulesCommand.select_deployment_to_run.assert_called_once_with(
                config.deployments, command)
            mock_select_modules_to_run.assert_called_once_with(
                'deployment', cli_args.get('--tag'), command,
                mock_context.is_noninteractive, mock_context.env_name)
        # pylint: disable=no-member
        ModulesCommand._process_deployments.assert_called_once_with(
            ANY, mock_context)
Пример #12
0
 def test_execute(self):
     """Test execute."""
     with pytest.raises(NotImplementedError):
         ModulesCommand().execute()
Пример #13
0
    def test_deploy_module(self, mock_change_dir, mock_runwaymoduletype,
                           mock_path, mock_merge_nested_environment_dicts,
                           mock_validate_environment, deployment, command,
                           strict, should_validate_env, fx_deployments,
                           monkeypatch, runway_context, patch_runway_config):
        """Test _deploy_module.

        This test is not well designed but it is about as good as it can be
        given the density of logic and branches in the corresponding method.

        """
        deployment = fx_deployments.load(deployment)
        module = deployment.modules[0]
        runway_context.command = command
        mock_module_instance = MagicMock()
        patch_runway_config.future.strict_environments = strict

        mock_runwaymoduletype.return_value = mock_runwaymoduletype
        mock_runwaymoduletype.module_class.return_value = mock_module_instance

        monkeypatch.setattr(deployment, 'resolve', MagicMock())
        monkeypatch.setattr(module, 'resolve', MagicMock())
        monkeypatch.setattr(MODULE + '.load_module_opts_from_file',
                            lambda x, y: y)  # return module_opts

        assert not ModulesCommand()._deploy_module(module, deployment,
                                                   runway_context)
        # second arg should be a mock
        deployment.resolve.assert_called_once_with(runway_context, ANY)
        deployment.resolve.assert_called_once_with(runway_context, ANY)
        mock_path.assert_called_once_with(
            module, os.getcwd(), os.path.join(os.getcwd(), '.runway_cache'))
        if deployment.env_vars or module.env_vars:
            mock_merge_nested_environment_dicts.assert_called_once_with(
                ANY, env_name=runway_context.env_name, env_root=os.getcwd(
                ))  # ANY is a dict constructed during execution
            mock_runwaymoduletype.module_class.assert_called_once_with(
                context=ANY,  # context object is altered if env_vars
                path=mock_path().module_root,
                options=ANY  # dict constructed during execution
            )
        else:
            mock_merge_nested_environment_dicts.assert_not_called()
            mock_runwaymoduletype.module_class.assert_called_once_with(
                context=runway_context,
                path=mock_path().module_root,
                options=ANY  # dict constructed during execution
            )
        if should_validate_env:
            mock_validate_environment.assert_called_once_with(
                # second arg is constructed during execution
                context=ANY,  # potentially changed runway_context
                env_def=ANY,  # merged dict of environments
                module=module,
                strict=strict)
        else:
            mock_validate_environment.assert_not_called()
        mock_change_dir.assert_has_calls([
            call(mock_path().module_root),
            call().__enter__(),
            call().__exit__(None, None, None)
        ])
        # last two args come from module_opts
        mock_runwaymoduletype.assert_called_once_with(mock_path().module_root,
                                                      ANY, ANY)
        mock_module_instance[command].assert_called_once_with()