diff --git a/atomicapp/__init__.py b/atomicapp/__init__.py
index 24dd79f8..e69de29b 100644
--- a/atomicapp/__init__.py
+++ b/atomicapp/__init__.py
@@ -1,41 +0,0 @@
-"""
- Copyright 2015 Red Hat, Inc.
-
- This file is part of Atomic App.
-
- Atomic App is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Atomic App is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with Atomic App. If not, see .
-"""
-
-import logging
-
-
-def set_logging(name="atomicapp", level=logging.DEBUG):
- # create logger
- logger = logging.getLogger()
- logger.handlers = []
- logger.setLevel(level)
-
- # create console handler
- ch = logging.StreamHandler()
-
- # create formatter
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-
- # add formatter to ch
- ch.setFormatter(formatter)
-
- # add ch to logger
- logger.addHandler(ch)
-
-set_logging(level=logging.DEBUG) # override this however you want
diff --git a/atomicapp/applogging.py b/atomicapp/applogging.py
new file mode 100644
index 00000000..ad5030a6
--- /dev/null
+++ b/atomicapp/applogging.py
@@ -0,0 +1,152 @@
+"""
+ Copyright 2015 Red Hat, Inc.
+
+ This file is part of Atomic App.
+
+ Atomic App is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Atomic App is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with Atomic App. If not, see .
+"""
+
+import sys
+import logging
+
+from atomicapp.constants import (LOGGER_COCKPIT,
+ LOGGER_DEFAULT)
+
+
+class colorizeOutputFormatter(logging.Formatter):
+ """
+ A class to colorize the log msgs based on log level
+ """
+
+ def format(self, record):
+ # Call the parent class to do formatting.
+ msg = super(colorizeOutputFormatter, self).format(record)
+
+ # Now post process and colorize if needed
+ if record.levelno == logging.DEBUG:
+ msg = self._colorize(msg, 'cyan')
+ elif record.levelno == logging.WARNING:
+ msg = self._colorize(msg, 'yellow')
+ elif record.levelno == logging.INFO:
+ msg = self._colorize(msg, 'white')
+ elif record.levelno == logging.ERROR:
+ msg = self._colorize(msg, 'red')
+ else:
+ raise Exception("Invalid logging level {}".format(record.levelno))
+ return self._make_unicode(msg)
+
+ def _colorize(self, text, color):
+ """
+ Colorize based upon the color codes indicated.
+ """
+ # Console color codes
+ colorCodes = {
+ 'white': '0', 'bright white': '1;37',
+ 'blue': '0;34', 'bright blue': '1;34',
+ 'green': '0;32', 'bright green': '1;32',
+ 'cyan': '0;36', 'bright cyan': '1;36',
+ 'red': '0;31', 'bright red': '1;31',
+ 'purple': '0;35', 'bright purple': '1;35',
+ 'yellow': '0;33', 'bright yellow': '1;33',
+ }
+ return "\033[" + colorCodes[color] + "m" + text + "\033[0m"
+
+ def _make_unicode(self, input):
+ """
+ Convert all input to utf-8 for multi language support
+ """
+ if type(input) != unicode:
+ input = input.decode('utf-8')
+ return input
+
+
+class Logging:
+
+ @staticmethod
+ def setup_logging(verbose=None, quiet=None, logtype=None):
+ """
+ This function sets up logging based on the logtype requested.
+ The 'none' level outputs no logs at all
+ The 'cockpit' level outputs just logs for the cockpit logger
+ The 'nocolor' level prints out normal log msgs (no cockpit) without color
+ The 'color' level prints out normal log msgs (no cockpit) with color
+ """
+
+ # If no logtype was set then let's have a sane default
+ # If connected to a tty, then default to color, else, no color
+ if not logtype:
+ if sys.stdout.isatty():
+ logtype = 'color'
+ else:
+ logtype = 'nocolor'
+
+ # Determine what logging level we should use
+ if verbose:
+ logging_level = logging.DEBUG
+ elif quiet:
+ logging_level = logging.WARNING
+ else:
+ logging_level = logging.INFO
+
+ # Get the loggers and clear out the handlers (allows this function
+ # to be ran more than once)
+ logger = logging.getLogger(LOGGER_DEFAULT)
+ logger.handlers = []
+ cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+ cockpit_logger.handlers = []
+
+ if logtype == 'none':
+ # blank out both loggers
+ logger.addHandler(logging.NullHandler())
+ cockpit_logger.addHandler(logging.NullHandler())
+ return
+
+ if logtype == 'cockpit':
+ # blank out normal log messages
+ logger.addHandler(logging.NullHandler())
+
+ # configure cockpit logger
+ handler = logging.StreamHandler(stream=sys.stdout)
+ formatter = logging.Formatter('atomicapp.status.%(levelname)s.message=%(message)s')
+ handler.setFormatter(formatter)
+ cockpit_logger.addHandler(handler)
+ cockpit_logger.setLevel(logging_level)
+ return
+
+ if logtype == 'nocolor':
+ # blank out cockpit log messages
+ cockpit_logger.addHandler(logging.NullHandler())
+
+ # configure logger for basic no color printing to stdout
+ handler = logging.StreamHandler(stream=sys.stdout)
+ formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(filename)s - %(message)s')
+ handler.setFormatter(formatter)
+ logger.addHandler(handler)
+ logger.setLevel(logging_level)
+ return
+
+ if logtype == 'color':
+ # blank out cockpit log messages
+ cockpit_logger.addHandler(logging.NullHandler())
+
+ # configure logger for color printing to stdout
+ handler = logging.StreamHandler(stream=sys.stdout)
+ formatter = colorizeOutputFormatter('%(asctime)s - [%(levelname)s] - %(filename)s - %(message)s')
+ handler.setFormatter(formatter)
+ logger.addHandler(handler)
+ logger.setLevel(logging_level)
+ return
+
+ # If we made it here then there is an error
+ raise Exception("Invalid logging output type: {}".format(logtype))
diff --git a/atomicapp/cli/main.py b/atomicapp/cli/main.py
index d1fc4d35..2cf50ed6 100644
--- a/atomicapp/cli/main.py
+++ b/atomicapp/cli/main.py
@@ -25,7 +25,7 @@
from lockfile import LockFile
from lockfile import AlreadyLocked
-from atomicapp import set_logging
+from atomicapp.applogging import Logging
from atomicapp.constants import (__ATOMICAPPVERSION__,
__NULECULESPECVERSION__,
ANSWERS_FILE,
@@ -34,12 +34,13 @@
CACHE_DIR,
HOST_DIR,
LOCK_FILE,
+ LOGGER_DEFAULT,
PROVIDERS)
from atomicapp.nulecule import NuleculeManager
from atomicapp.nulecule.exceptions import NuleculeException
from atomicapp.utils import Utils
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(LOGGER_DEFAULT)
def print_app_location(app_path):
@@ -218,7 +219,7 @@ def create_parser(self):
action="store_true",
help=(
"Don't actually call provider. The commands that should be "
- "run will be sent to stdout but not run."))
+ "run will be logged but not run."))
globals_parser.add_argument(
"--answers-format",
dest="answers_format",
@@ -249,6 +250,18 @@ def create_parser(self):
"--providerapi",
dest="providerapi",
help='Value for providerapi answers option.')
+ globals_parser.add_argument(
+ "--logtype",
+ dest="logtype",
+ choices=['cockpit', 'color', 'nocolor', 'none'],
+ help="""
+ Override the default logging output. The options are:
+ nocolor: we will only log to stdout;
+ color: log to stdout with color;
+ cockpit: used with cockpit integration;
+ none: atomicapp will disable any logging.
+ If nothing is set and logging to file then 'nocolor' by default.
+ If nothing is set and logging to tty then 'color' by default.""")
# === "run" SUBPARSER ===
run_subparser = toplevel_subparsers.add_parser(
@@ -367,6 +380,9 @@ def create_parser(self):
def run(self):
cmdline = sys.argv[1:] # Grab args from cmdline
+ # Initial setup of logging (to allow for a few early debug statements)
+ Logging.setup_logging(verbose=True, quiet=False)
+
# If we are running in an openshift pod (via `oc new-app`) then
# there is no cmdline but we want to default to "atomicapp run".
if Utils.running_on_openshift():
@@ -399,11 +415,15 @@ def run(self):
if args.mode:
args.action = args.mode # Allow mode to override 'action'
cmdline.insert(0, args.action) # Place 'action' at front
- logger.info("Action/Mode Selected is: %s" % args.action)
# Finally, parse args and give error if necessary
args = self.parser.parse_args(cmdline)
+ # Setup logging (now with arguments from cmdline) and log a few msgs
+ Logging.setup_logging(args.verbose, args.quiet, args.logtype)
+ logger.info("Action/Mode Selected is: %s" % args.action)
+ logger.debug("Final parsed cmdline: {}".format(' '.join(cmdline)))
+
# In the case of Atomic CLI we want to allow the user to specify
# a directory if they want to for "run". For that reason we won't
# default the RUN label for Atomic App to provide an app_spec argument.
@@ -425,17 +445,6 @@ def run(self):
if hasattr(args, item) and getattr(args, item) is not None:
args.cli_answers[item] = getattr(args, item)
- # Set logging level
- if args.verbose:
- set_logging(level=logging.DEBUG)
- elif args.quiet:
- set_logging(level=logging.WARNING)
- else:
- set_logging(level=logging.INFO)
-
- # Now that we have set the logging level let's print out the cmdline
- logger.debug("Final parsed cmdline: {}".format(' '.join(cmdline)))
-
lock = LockFile(os.path.join(Utils.getRoot(), LOCK_FILE))
try:
lock.acquire(timeout=-1)
diff --git a/atomicapp/constants.py b/atomicapp/constants.py
index 04c61806..ab2a89cd 100644
--- a/atomicapp/constants.py
+++ b/atomicapp/constants.py
@@ -54,6 +54,10 @@
ANSWERS_FILE_SAMPLE_FORMAT = 'ini'
WORKDIR = ".workdir"
LOCK_FILE = "/run/lock/atomicapp.lock"
+
+LOGGER_DEFAULT = "atomicapp"
+LOGGER_COCKPIT = "cockpit"
+
HOST_DIR = "/host"
DEFAULT_PROVIDER = "kubernetes"
diff --git a/atomicapp/nulecule/base.py b/atomicapp/nulecule/base.py
index f7efca7b..fbac1ec2 100644
--- a/atomicapp/nulecule/base.py
+++ b/atomicapp/nulecule/base.py
@@ -10,6 +10,8 @@
from atomicapp.constants import (APP_ENT_PATH,
EXTERNAL_APP_DIR,
GLOBAL_CONF,
+ LOGGER_COCKPIT,
+ LOGGER_DEFAULT,
MAIN_FILE,
RESOURCE_KEY,
PARAMS_KEY,
@@ -27,7 +29,8 @@
from jsonpointer import resolve_pointer, set_pointer, JsonPointerException
-logger = logging.getLogger(__name__)
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+logger = logging.getLogger(LOGGER_DEFAULT)
class Nulecule(NuleculeBase):
@@ -100,6 +103,7 @@ def unpack(cls, image, dest, config=None, namespace=GLOBAL_CONF,
docker_handler = DockerHandler(dryrun=dryrun)
docker_handler.pull(image)
docker_handler.extract(image, APP_ENT_PATH, dest, update)
+ cockpit_logger.info("All dependencies installed successfully.")
return cls.load_from_path(
dest, config=config, namespace=namespace, nodeps=nodeps,
dryrun=dryrun, update=update)
@@ -159,6 +163,7 @@ def run(self, provider_key=None, dryrun=False):
# Process components
for component in self.components:
component.run(provider_key, dryrun)
+ cockpit_logger.info("Component %s installed successfully" % provider_key)
def stop(self, provider_key=None, dryrun=False):
"""
@@ -271,6 +276,7 @@ def load(self, nodeps=False, dryrun=False):
"""
Load external application of the Nulecule component.
"""
+ cockpit_logger.info("Loading app %s ." % self.name)
if self.source:
if nodeps:
logger.info(
@@ -282,6 +288,7 @@ def run(self, provider_key, dryrun=False):
"""
Run the Nulecule component with the specified provider,
"""
+ cockpit_logger.info("Deploying component %s ..." % self.name)
if self._app:
self._app.run(provider_key, dryrun)
return
@@ -346,6 +353,7 @@ def load_external_application(self, dryrun=False, update=False):
update=update
)
self._app = nulecule
+ cockpit_logger.info("Copied app successfully.")
@property
def components(self):
diff --git a/atomicapp/nulecule/container.py b/atomicapp/nulecule/container.py
index cb50c80f..cc40b426 100644
--- a/atomicapp/nulecule/container.py
+++ b/atomicapp/nulecule/container.py
@@ -4,11 +4,14 @@
import logging
from atomicapp.constants import (APP_ENT_PATH,
+ LOGGER_COCKPIT,
+ LOGGER_DEFAULT,
MAIN_FILE)
from atomicapp.utils import Utils
from atomicapp.nulecule.exceptions import NuleculeException
-logger = logging.getLogger(__name__)
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+logger = logging.getLogger(LOGGER_DEFAULT)
class DockerHandler(object):
@@ -47,6 +50,7 @@ def pull(self, image, update=False):
"""
if not self.is_image_present(image) or update:
logger.info('Pulling Docker image: %s' % image)
+ cockpit_logger.info('Pulling Docker image: %s' % image)
pull_cmd = [self.docker_cli, 'pull', image]
logger.debug(' '.join(pull_cmd))
else:
@@ -58,6 +62,8 @@ def pull(self, image, update=False):
elif subprocess.call(pull_cmd) != 0:
raise Exception("Could not pull Docker image %s" % image)
+ cockpit_logger.info('Skipping pulling Docker image: %s' % image)
+
def extract(self, image, source, dest, update=False):
"""
Extracts content from a directory in a Docker image to specified
@@ -109,6 +115,7 @@ def extract(self, image, source, dest, update=False):
if os.path.exists(mainfile):
existing_id = Utils.getAppId(mainfile)
new_id = Utils.getAppId(tmpmainfile)
+ cockpit_logger.info("Loading app_id %s ." % new_id)
if existing_id != new_id:
raise NuleculeException(
"Existing app (%s) and requested app (%s) differ" %
diff --git a/atomicapp/nulecule/lib.py b/atomicapp/nulecule/lib.py
index 4dd41f39..c1e052da 100644
--- a/atomicapp/nulecule/lib.py
+++ b/atomicapp/nulecule/lib.py
@@ -1,11 +1,16 @@
# -*- coding: utf-8 -*-
+import logging
+
from atomicapp.constants import (GLOBAL_CONF,
+ LOGGER_COCKPIT,
NAME_KEY,
DEFAULTNAME_KEY,
PROVIDER_KEY)
from atomicapp.utils import Utils
from atomicapp.plugin import Plugin
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+
class NuleculeBase(object):
@@ -44,6 +49,7 @@ def load_config(self, config, ask=False, skip_asking=False):
config.get(GLOBAL_CONF, {}).get(param[NAME_KEY])
if value is None and (ask or (
not skip_asking and param.get(DEFAULTNAME_KEY) is None)):
+ cockpit_logger.info("%s is missing in answers.conf." % param[NAME_KEY])
value = Utils.askFor(param[NAME_KEY], param)
elif value is None:
value = param.get(DEFAULTNAME_KEY)
diff --git a/atomicapp/nulecule/main.py b/atomicapp/nulecule/main.py
index 6c38ace5..79a5a723 100644
--- a/atomicapp/nulecule/main.py
+++ b/atomicapp/nulecule/main.py
@@ -14,13 +14,16 @@
ANSWERS_FILE_SAMPLE,
ANSWERS_RUNTIME_FILE,
DEFAULT_ANSWERS,
+ LOGGER_COCKPIT,
+ LOGGER_DEFAULT,
MAIN_FILE,
PROVIDER_KEY)
from atomicapp.nulecule.base import Nulecule
from atomicapp.nulecule.exceptions import NuleculeException
from atomicapp.utils import Utils
-logger = logging.getLogger(__name__)
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+logger = logging.getLogger(LOGGER_DEFAULT)
class NuleculeManager(object):
@@ -191,6 +194,8 @@ def fetch(self, nodeps=False, update=False, dryrun=False,
os.path.join(self.app_path, ANSWERS_FILE_SAMPLE),
runtime_answers, answers_format)
+ cockpit_logger.info("Install Successful.")
+
def run(self, cli_provider, answers_output, ask,
answers_format=ANSWERS_FILE_SAMPLE_FORMAT, **kwargs):
"""
diff --git a/atomicapp/plugin.py b/atomicapp/plugin.py
index 85f52e2e..96a43f11 100644
--- a/atomicapp/plugin.py
+++ b/atomicapp/plugin.py
@@ -26,9 +26,11 @@
import logging
from utils import Utils
-from constants import HOST_DIR, PROVIDER_CONFIG_KEY
+from constants import (HOST_DIR,
+ LOGGER_DEFAULT,
+ PROVIDER_CONFIG_KEY)
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(LOGGER_DEFAULT)
class Provider(object):
diff --git a/atomicapp/providers/docker.py b/atomicapp/providers/docker.py
index 6b116c8a..a157ccc7 100644
--- a/atomicapp/providers/docker.py
+++ b/atomicapp/providers/docker.py
@@ -21,11 +21,13 @@
import subprocess
import re
import logging
-from atomicapp.constants import DEFAULT_CONTAINER_NAME, DEFAULT_NAMESPACE
+from atomicapp.constants import (DEFAULT_CONTAINER_NAME,
+ DEFAULT_NAMESPACE,
+ LOGGER_DEFAULT)
from atomicapp.plugin import Provider, ProviderFailedException
from atomicapp.utils import Utils
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(LOGGER_DEFAULT)
class DockerProvider(Provider):
diff --git a/atomicapp/providers/kubernetes.py b/atomicapp/providers/kubernetes.py
index 600e28fa..ad6c8be8 100644
--- a/atomicapp/providers/kubernetes.py
+++ b/atomicapp/providers/kubernetes.py
@@ -22,10 +22,13 @@
import os
from string import Template
+from atomicapp.constants import (LOGGER_COCKPIT,
+ LOGGER_DEFAULT)
from atomicapp.plugin import Provider, ProviderFailedException
-from atomicapp.utils import printErrorStatus, Utils
+from atomicapp.utils import Utils
-logger = logging.getLogger(__name__)
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+logger = logging.getLogger(LOGGER_DEFAULT)
class KubernetesProvider(Provider):
@@ -123,7 +126,7 @@ def process_k8s_artifacts(self):
except Exception:
msg = "Error processing %s artifcats, Error:" % os.path.join(
self.path, artifact)
- printErrorStatus(msg)
+ cockpit_logger.error(msg)
raise
if "kind" in data:
self.k8s_manifests.append((data["kind"].lower(), artifact))
diff --git a/atomicapp/providers/marathon.py b/atomicapp/providers/marathon.py
index 40d2d7f7..c22165d8 100644
--- a/atomicapp/providers/marathon.py
+++ b/atomicapp/providers/marathon.py
@@ -21,12 +21,14 @@
import urlparse
import logging
import os
+from atomicapp.constants import (LOGGER_COCKPIT,
+ LOGGER_DEFAULT)
from atomicapp.plugin import Provider, ProviderFailedException
-from atomicapp.utils import printErrorStatus
from atomicapp.utils import Utils
from atomicapp.constants import PROVIDER_API_KEY
-logger = logging.getLogger(__name__)
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+logger = logging.getLogger(LOGGER_DEFAULT)
class Marathon(Provider):
@@ -115,10 +117,10 @@ def _process_artifacts(self):
# every marathon app has to have id. 'id' key is also used for showing messages
if "id" not in data.keys():
msg = "Error processing %s artifact. There is no id" % artifact
- printErrorStatus(msg)
+ cockpit_logger.error(msg)
raise ProviderFailedException(msg)
except anymarkup.AnyMarkupError, e:
msg = "Error processing artifact - %s" % e
- printErrorStatus(msg)
+ cockpit_logger.error(msg)
raise ProviderFailedException(msg)
self.marathon_artifacts.append(data)
diff --git a/atomicapp/providers/openshift.py b/atomicapp/providers/openshift.py
index 3695d12f..0a73b811 100644
--- a/atomicapp/providers/openshift.py
+++ b/atomicapp/providers/openshift.py
@@ -32,13 +32,14 @@
from atomicapp.constants import (ACCESS_TOKEN_KEY,
ANSWERS_FILE,
DEFAULT_NAMESPACE,
+ LOGGER_DEFAULT,
NAMESPACE_KEY,
PROVIDER_API_KEY,
PROVIDER_TLS_VERIFY_KEY,
PROVIDER_CA_KEY)
from requests.exceptions import SSLError
import logging
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(LOGGER_DEFAULT)
# If running in an openshift POD via `oc new-app`, the ca file is here
OPENSHIFT_POD_CA_FILE = "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
diff --git a/atomicapp/requirements.py b/atomicapp/requirements.py
index 899b26d2..f618ffbc 100644
--- a/atomicapp/requirements.py
+++ b/atomicapp/requirements.py
@@ -1,9 +1,10 @@
import logging
-from atomicapp.constants import REQUIREMENT_FUNCTIONS
+from atomicapp.constants import (LOGGER_DEFAULT,
+ REQUIREMENT_FUNCTIONS)
from atomicapp.plugin import Plugin
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(LOGGER_DEFAULT)
class Requirements:
diff --git a/atomicapp/utils.py b/atomicapp/utils.py
index d25bd6d8..bb7a77c3 100644
--- a/atomicapp/utils.py
+++ b/atomicapp/utils.py
@@ -36,28 +36,19 @@
CACHE_DIR,
EXTERNAL_APP_DIR,
HOST_DIR,
+ LOGGER_COCKPIT,
+ LOGGER_DEFAULT,
WORKDIR)
__all__ = ('Utils')
-logger = logging.getLogger(__name__)
+cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
+logger = logging.getLogger(LOGGER_DEFAULT)
class AtomicAppUtilsException(Exception):
pass
-# Following Methods(printStatus, printErrorStatus)
-# are required for Cockpit or thirdparty management tool integration
-# DONOT change the atomicapp.status.* prefix in the logger method.
-
-
-def printStatus(message):
- logger.info("atomicapp.status.info.message=" + str(message))
-
-
-def printErrorStatus(message):
- logger.info("atomicapp.status.error.message=" + str(message))
-
def find_binary(executable, path=None):
"""Tries to find 'executable' in the directories listed in 'path'.
@@ -270,7 +261,7 @@ def run_cmd(cmd, checkexitcode=True, stdin=None):
# we were asked not to.
if checkexitcode:
if ec != 0:
- printErrorStatus("cmd failed: %s" % str(cmd)) # For cockpit
+ cockpit_logger.error("cmd failed: %s" % str(cmd))
raise AtomicAppUtilsException(
"cmd: %s failed: \n%s" % (str(cmd), stderr))
diff --git a/tests/units/cli/test_default_provider.py b/tests/units/cli/test_default_provider.py
index 73437966..cb16a9b2 100644
--- a/tests/units/cli/test_default_provider.py
+++ b/tests/units/cli/test_default_provider.py
@@ -61,16 +61,16 @@ def test_run_helloapache_app(self, capsys):
# Run the dry-run command
with pytest.raises(SystemExit) as exec_info:
self.exec_cli(command)
- nil, out = capsys.readouterr()
+ stdout, stderr = capsys.readouterr()
# Tear down and remove all those useless generated files
self.tear_down()
# Print out what we've captured just in case the test fails
- print out
+ print stdout
# Since this a Docker-only provider test, docker *should* be in it, NOT Kubernetes
- assert "u'provider': u'docker'" in out
- assert "Deploying to Kubernetes" not in out
+ assert "u'provider': u'docker'" in stdout
+ assert "Deploying to Kubernetes" not in stdout
assert exec_info.value.code == 0