示例#1
0
    def test_load_config_external_app(self):
        """Test load config for external app"""
        params = [{
            'name': 'key1',
            'description': 'key1'
        }, {
            'name': 'key2',
            'description': 'key2'
        }]
        initial_config = {
            'general': {
                'a': 'b',
                'key2': 'val2'
            },
            'some-app': {
                'key1': 'val1'
            }
        }
        config = Config(answers=initial_config)
        mock_nulecule = mock.Mock(name='nulecule',
                                  spec=Nulecule('some-id', '0.0.2', config, [],
                                                'some/path'))

        nc = NuleculeComponent('some-app', 'some/path', params=params)
        nc._app = mock_nulecule
        nc.config = config
        nc.load_config()

        mock_nulecule.load_config.assert_called_once_with(config=config,
                                                          ask=False,
                                                          skip_asking=False)
示例#2
0
    def test_load_config_with_default_provider(self):
        """
        Test Nulecule load_config with a default provider.
        """
        config = Config(answers={})

        params = [{
            "name": "key1",
            "default": "val1",
        }, {
            "name": "key3",
            "default": "val3"
        }, {
            "name": "provider",
            "default": "docker"
        }]

        graph = [{
            "name":
            "component1",
            "params": [{
                "name": "key1",
            }, {
                "name": "key2",
                "default": "val2"
            }],
            "artifacts": []
        }]

        n = Nulecule(id='some-id',
                     specversion='0.0.2',
                     metadata={},
                     graph=graph,
                     params=params,
                     basepath='some/path',
                     config=config)
        n.load_components()
        n.load_config(config)

        self.assertEqual(
            n.config.runtime_answers(), {
                'general': {
                    'namespace': 'default',
                    'provider': 'docker',
                    'key1': 'val1',
                    'key3': 'val3'
                },
                'component1': {
                    'key2': 'val2'
                }
            })

        self.assertEqual(
            n.components[0].config.context(scope=n.components[0].namespace), {
                'key3': 'val3',
                'key2': 'val2',
                'key1': 'val1',
                'provider': 'docker',
                'namespace': 'default'
            })
示例#3
0
 def test_get_provider_failure(self):
     """
     Test if get_provider method when passed an invalid key raises an
     exception.
     """
     nb = NuleculeBase(params=[], basepath='', namespace='')
     # purposefully give the wrong provider key
     provider_key = u'mesos'
     nb.config = Config(answers={u'general': {u'provider': provider_key}})
     with self.assertRaises(NuleculeException):
         nb.get_provider()
示例#4
0
    def test_render_for_local_app_with_missing_artifacts_for_provider(self):
        """
        Test rendering a Nulecule component with missing artifacts for a
        provider.
        """
        provider_key = 'some-provider'
        dryrun = False

        nc = NuleculeComponent(name='some-app', basepath='some/path')
        nc.config = Config()
        nc.artifacts = {'x': ['some-artifact']}

        self.assertRaises(NuleculeException, nc.render, provider_key, dryrun)
示例#5
0
    def test_run(self):
        provider = 'docker'
        dryrun = False
        mock_component_1 = mock.Mock()
        mock_component_2 = mock.Mock()
        config = Config(answers={})

        n = Nulecule('some-id', '0.0.2', [{}], 'some/path', {}, config=config)
        n.components = [mock_component_1, mock_component_2]
        n.run(provider)

        mock_component_1.run.assert_called_once_with(provider, dryrun)
        mock_component_2.run.assert_called_once_with(provider, dryrun)
示例#6
0
    def test_render_for_local_app_with_artifacts_for_provider(
            self, mock_render_artifact, mock_get_artifact_paths_for_provider):
        """Test rendering artifacts for a local Nulecule component"""
        provider_key = 'some-provider'
        dryrun = False
        expected_rendered_artifacts = [
            'some/path/.artifact1', 'some/path/.artifact2'
        ]
        mock_get_artifact_paths_for_provider.return_value = [
            'some/path/artifact1', 'some/path/artifact2'
        ]
        mock_render_artifact.side_effect = lambda path, context, provider: path.replace(
            'artifact', '.artifact')
        # mock_get_context.return_value = context

        nc = NuleculeComponent(name='some-app', basepath='some/path')
        nc.config = Config(answers={
            'general': {
                'key1': 'val1'
            },
            'some-provider': {
                'a': 'b'
            }
        })
        nc.artifacts = {
            'some-provider': ['artifact1', 'artifact2'],
            'x': ['foo']
        }
        nc.render(provider_key, dryrun)

        expected_context = {
            'key1': 'val1',
            'namespace': 'default',
            'provider': 'kubernetes'
        }
        mock_get_artifact_paths_for_provider.assert_called_once_with(
            provider_key)
        mock_render_artifact.assert_any_call('some/path/artifact1',
                                             expected_context, 'some-provider')
        mock_render_artifact.assert_any_call('some/path/artifact2',
                                             expected_context, 'some-provider')
        mock_get_artifact_paths_for_provider.assert_called_once_with(
            provider_key)
        self.assertEqual(nc.rendered_artifacts[provider_key],
                         expected_rendered_artifacts)
示例#7
0
    def test_get_provider_success(self):
        """
        Test if get_provider method when passed a particular valid key returns
        the corresponding class.
        """
        nb = NuleculeBase(params=[], basepath='', namespace='')
        provider_key = u'openshift'
        # method `get_provider` will read from this config, we give it here
        # since we have neither provided it before nor it is auto-generated
        nb.config = Config(answers={u'general': {u'provider': provider_key}})

        return_provider = mock.Mock()
        # mocking return value of method plugin.getProvider,because it returns
        # provider class and that class gets called with values
        nb.plugin.getProvider = mock.Mock(return_value=return_provider)
        ret_provider_key, ret_provider = nb.get_provider()
        self.assertEqual(provider_key, ret_provider_key)
        return_provider.assert_called_with(
            {
                'provider': provider_key,
                'namespace': 'default'
            }, '', False)
示例#8
0
    def test_load_config_local_app(self):
        """Test load config for local app"""
        params = [{
            'name': 'key1',
            'description': 'key1'
        }, {
            'name': 'key2',
            'description': 'key2'
        }]
        initial_config = {
            'general': {
                'a': 'b',
                'key2': 'val2'
            },
            'some-app': {
                'key1': 'val1'
            }
        }
        conf = Config(answers=initial_config)

        nc = NuleculeComponent('some-app',
                               'some/path',
                               params=params,
                               config=conf)
        nc.load_config()
        runtime_answers = nc.config.runtime_answers()
        self.assertEqual(
            runtime_answers, {
                'general': {
                    'a': 'b',
                    'key2': 'val2',
                    'provider': 'kubernetes',
                    'namespace': 'default'
                },
                'some-app': {
                    'key1': 'val1'
                }
            })
示例#9
0
    def test_load_components(self, MockNuleculeComponent):
        graph = [{
            'name': 'app1',
            'source': 'docker://somecontainer',
            'params': []
        }, {
            'name': 'app2',
            'artifacts': [{
                'a': 'b'
            }]
        }]

        config = Config(answers={})

        n = Nulecule('some-id', '0.0.2', graph, 'some/path', config=config)
        n.load_components()

        MockNuleculeComponent.assert_any_call(graph[0]['name'], n.basepath,
                                              'somecontainer',
                                              graph[0]['params'], None, config)
        MockNuleculeComponent.assert_any_call(graph[1]['name'], n.basepath,
                                              None, graph[1].get('params'),
                                              graph[1].get('artifacts'),
                                              config)
示例#10
0
文件: main.py 项目: cdrage/atomicapp
    def __init__(self, app_spec, destination=None, cli_answers=None, answers_file=None, answers_format=None):
        """
        init function for NuleculeManager. Sets a few instance variables.

        Args:
            app_spec: either a path to an unpacked nulecule app or a
                      container image name where a nulecule can be found
            destination: where to unpack a nulecule to if it isn't local
            cli_answers: some answer file values provided from cli args
            answers_file: the location of the answers file
            answers_format (str): File format for writing sample answers file
        """
        self.answers_format = answers_format or ANSWERS_FILE_SAMPLE_FORMAT
        self.answers_file = None  # The path to an answer file
        self.app_path = None  # The path where the app resides or will reside
        self.image = None  # The container image to pull the app from

        # Adjust app_spec, destination, and answer file paths if absolute.
        if os.path.isabs(app_spec):
            app_spec = Utils.get_real_abspath(app_spec)
        if destination and os.path.isabs(destination):
            destination = Utils.get_real_abspath(destination)
        if answers_file and os.path.isabs(answers_file):
            answers_file = Utils.get_real_abspath(answers_file)

        # If the user doesn't want the files copied to a permanent
        # location then he provides 'none'. If that is the case we'll
        # use a temporary directory
        if destination and destination.lower() == "none":
            logger.debug("'none' destination requested. Using tmp dir")
            destination = tempfile.mkdtemp(prefix="atomicapp")

        # Determine if the user passed us an image or a path to an app
        if not os.path.exists(app_spec):
            self.image = app_spec
        else:
            self.app_path = app_spec

        # Doesn't really make much sense to provide an app path and destination,
        # but if they want to we'll simply just copy the files for them
        if self.app_path and destination:
            Utils.copy_dir(self.app_path, destination, update=True)
            self.app_path = destination

        # If the user provided an image, make sure we have a destination
        if self.image:
            if destination:
                self.app_path = destination
            else:
                self.app_path = Utils.getNewAppCacheDir(self.image)

        logger.debug("NuleculeManager init app_path: %s", self.app_path)
        logger.debug("NuleculeManager init image: %s", self.image)

        # Create the app_path if it doesn't exist yet
        if not os.path.isdir(self.app_path):
            os.makedirs(self.app_path)

        # Set where the main nulecule file should be
        self.main_file = os.path.join(self.app_path, MAIN_FILE)

        # Process answers.
        self.answers_file = answers_file
        self.config = Config(cli=cli_answers)
示例#11
0
文件: main.py 项目: cdrage/atomicapp
class NuleculeManager(object):

    """
    Interface to fetch, run, stop a Nulecule application.
    """

    def __init__(self, app_spec, destination=None, cli_answers=None, answers_file=None, answers_format=None):
        """
        init function for NuleculeManager. Sets a few instance variables.

        Args:
            app_spec: either a path to an unpacked nulecule app or a
                      container image name where a nulecule can be found
            destination: where to unpack a nulecule to if it isn't local
            cli_answers: some answer file values provided from cli args
            answers_file: the location of the answers file
            answers_format (str): File format for writing sample answers file
        """
        self.answers_format = answers_format or ANSWERS_FILE_SAMPLE_FORMAT
        self.answers_file = None  # The path to an answer file
        self.app_path = None  # The path where the app resides or will reside
        self.image = None  # The container image to pull the app from

        # Adjust app_spec, destination, and answer file paths if absolute.
        if os.path.isabs(app_spec):
            app_spec = Utils.get_real_abspath(app_spec)
        if destination and os.path.isabs(destination):
            destination = Utils.get_real_abspath(destination)
        if answers_file and os.path.isabs(answers_file):
            answers_file = Utils.get_real_abspath(answers_file)

        # If the user doesn't want the files copied to a permanent
        # location then he provides 'none'. If that is the case we'll
        # use a temporary directory
        if destination and destination.lower() == "none":
            logger.debug("'none' destination requested. Using tmp dir")
            destination = tempfile.mkdtemp(prefix="atomicapp")

        # Determine if the user passed us an image or a path to an app
        if not os.path.exists(app_spec):
            self.image = app_spec
        else:
            self.app_path = app_spec

        # Doesn't really make much sense to provide an app path and destination,
        # but if they want to we'll simply just copy the files for them
        if self.app_path and destination:
            Utils.copy_dir(self.app_path, destination, update=True)
            self.app_path = destination

        # If the user provided an image, make sure we have a destination
        if self.image:
            if destination:
                self.app_path = destination
            else:
                self.app_path = Utils.getNewAppCacheDir(self.image)

        logger.debug("NuleculeManager init app_path: %s", self.app_path)
        logger.debug("NuleculeManager init image: %s", self.image)

        # Create the app_path if it doesn't exist yet
        if not os.path.isdir(self.app_path):
            os.makedirs(self.app_path)

        # Set where the main nulecule file should be
        self.main_file = os.path.join(self.app_path, MAIN_FILE)

        # Process answers.
        self.answers_file = answers_file
        self.config = Config(cli=cli_answers)

    @staticmethod
    def init(app_name, destination=None, app_version="1.0", app_desc="App description"):
        """Initialize a new Nulecule app

        Args:
            app_name (str): Application name
            destination (str): Destination path
            app_version (str): Application version
            app_desc (str): Application description

        Returns:
            destination (str)
        """

        # context to render template files for Atomic App
        context = dict(
            app_name=app_name,
            app_version=app_version,
            app_desc=app_desc,
            atomicapp_version=__ATOMICAPPVERSION__,
            nulecule_spec_version=__NULECULESPECVERSION__,
        )

        if destination is None:
            destination = os.path.join(".", app_name)

        # Check if destination directory exists and is not empty
        if os.path.exists(destination) and os.path.isdir(destination) and os.listdir(destination):
            value = raw_input("Destination directory is not empty! " "Do you still want to proceed? [Y]/n: ")
            value = value or "y"
            if value.lower() != "y":
                return  # Exit out as the user has chosen not to proceed

        # Temporary working dir to render the templates
        tmpdir = tempfile.mkdtemp(prefix="nulecule-new-app-")
        template_dir = os.path.join(os.path.dirname(__file__), "external/templates/nulecule")

        try:
            # Copy template dir to temporary working directory and render templates
            distutils.dir_util.copy_tree(template_dir, tmpdir)
            for item in os.walk(tmpdir):
                parent_dir, dirs, files = item
                for filename in files:
                    if not filename.endswith(".tpl"):
                        continue
                    templ_path = os.path.join(parent_dir, filename)
                    if parent_dir.endswith("artifacts/docker") or parent_dir.endswith("artifacts/kubernetes"):
                        file_path = os.path.join(parent_dir, "{}_{}".format(app_name, filename[:-4]))
                    else:
                        file_path = os.path.join(parent_dir, filename[:-4])
                    with open(templ_path) as f:
                        s = f.read()
                    t = Template(s)
                    with open(file_path, "w") as f:
                        f.write(t.safe_substitute(**context))
                    os.remove(templ_path)

            # Copy rendered templates to destination directory
            distutils.dir_util.copy_tree(tmpdir, destination, True)
        finally:
            # Remove temporary working directory
            distutils.dir_util.remove_tree(tmpdir)
        return destination

    def unpack(self, update=False, dryrun=False, nodeps=False, config=None):
        """
        Unpacks a Nulecule application from a Nulecule image to a path
        or load a Nulecule that already exists locally.

        Args:
            update (bool): Update existing Nulecule application in
                           app_path, if True
            dryrun (bool): Do not make any change to the host system
            nodeps (bool): Do not unpack external dependencies
            config (dict): Config data, if any, to use for unpacking

        Returns:
            A Nulecule instance.
        """
        logger.debug("Request to unpack to %s to %s" % (self.image, self.app_path))

        # If the user provided an image then unpack it and return the
        # resulting Nulecule. Else, load from existing path
        if self.image:
            return Nulecule.unpack(
                self.image, self.app_path, config=config, nodeps=nodeps, dryrun=dryrun, update=update
            )
        else:
            return Nulecule.load_from_path(self.app_path, dryrun=dryrun, config=config)

    def genanswers(self, dryrun=False, **kwargs):
        """
        Renders artifacts and then generates an answer file. Finally
        copies answer file to the current working directory.

        Args:
            dryrun (bool): Do not make any change to the host system if True
            kwargs (dict): Extra keyword arguments

        Returns:
            None
        """

        # Check to make sure an answers.conf file doesn't exist already
        answers_file = os.path.join(os.getcwd(), ANSWERS_FILE)
        if os.path.exists(answers_file):
            raise NuleculeException("Can't generate answers.conf over existing file")

        # Call unpack to get the app code
        self.nulecule = self.unpack(update=False, dryrun=dryrun, config=self.config)

        self.nulecule.load_config(skip_asking=True)
        # Get answers and write them out to answers.conf in cwd
        answers = self._get_runtime_answers(self.nulecule.config, None)
        self._write_answers(answers_file, answers, self.answers_format)

    def fetch(self, nodeps=False, update=False, dryrun=False, **kwargs):
        """
        Installs (unpacks) a Nulecule application from a Nulecule image
        to a target path.
        Args:
            answers (dict or str): Answers data or local path to answers file
            nodeps (bool): Install the nulecule application without installing
                           external dependencies
            update (bool): Pull requisite Nulecule image and install or
                           update already installed Nulecule application
            dryrun (bool): Do not make any change to the host system if True
            kwargs (dict): Extra keyword arguments
        Returns:
            None
        """
        # Call unpack. If the app doesn't exist it will be pulled. If
        # it does exist it will be just be loaded and returned
        self.nulecule = self.unpack(update, dryrun, config=self.config)

        self.nulecule.load_config(skip_asking=True)
        runtime_answers = self._get_runtime_answers(self.nulecule.config, None)
        # write sample answers file
        self._write_answers(os.path.join(self.app_path, ANSWERS_FILE_SAMPLE), runtime_answers, self.answers_format)

        cockpit_logger.info("Install Successful.")

    def run(self, answers_output, ask, **kwargs):
        """
        Runs a Nulecule application from a local path or a Nulecule image
        name.

        Args:
            answers (dict or str): Answers data or local path to answers file
            answers_output (str): Path to file to export runtime answers data
                                  to
            ask (bool): Ask for values for params with default values from
                        user, if True
            kwargs (dict): Extra keyword arguments

        Returns:
            None
        """
        dryrun = kwargs.get("dryrun") or False

        # Call unpack. If the app doesn't exist it will be pulled. If
        # it does exist it will be just be loaded and returned
        self.nulecule = self.unpack(dryrun=dryrun, config=self.config)

        # Process answers file
        self._process_answers()

        self.nulecule.load_config(ask=ask)
        provider = self.nulecule.config.get("provider")
        self.nulecule.render(provider, dryrun)
        self.nulecule.run(provider, dryrun)
        runtime_answers = self._get_runtime_answers(self.nulecule.config, provider)
        self._write_answers(os.path.join(self.app_path, ANSWERS_RUNTIME_FILE), runtime_answers, self.answers_format)
        if answers_output:
            self._write_answers(answers_output, runtime_answers, self.answers_format)

    def stop(self, **kwargs):
        """
        Stops a running Nulecule application.

        Args:
            kwargs (dict): Extra keyword arguments
        """
        # For stop we use the generated answer file from the run
        self.answers_file = os.path.join(self.app_path, ANSWERS_RUNTIME_FILE)
        self._process_answers()

        dryrun = kwargs.get("dryrun") or False
        self.nulecule = Nulecule.load_from_path(self.app_path, config=self.config, dryrun=dryrun)
        self.nulecule.load_config()
        self.nulecule.render(self.nulecule.config.get("provider"), dryrun=dryrun)
        self.nulecule.stop(self.nulecule.config.get("provider"), dryrun)

    def clean(self, force=False):
        # For future use
        self.uninstall()
        distutils.dir_util.remove_tree(self.unpack_path)
        self.initialize()

    def _process_answers(self):
        """
        Processes answer files to load data from them and then merges
        any cli provided answers into the config.

        NOTE: This function should be called once on startup and then
        once more after the application has been extracted, but only
        if answers file wasn't found on the first invocation. The idea
        is to allow for people to embed an answers file in the application
        if they want, which won't be available until after extraction.

        Returns:
            None
        """
        answers = None
        app_path_answers = os.path.join(self.app_path, ANSWERS_FILE)

        # If the user didn't provide an answers file then check the app
        # dir to see if one exists.
        if not self.answers_file:
            if os.path.isfile(app_path_answers):
                self.answers_file = app_path_answers

        # At this point if we have an answers file, load it
        if self.answers_file:

            # If this is a url then download answers file to app directory
            if urlparse.urlparse(self.answers_file).scheme != "":
                logger.debug("Retrieving answers file from: {}".format(self.answers_file))
                with open(app_path_answers, "w+") as f:
                    stream = urllib.urlopen(self.answers_file)
                    f.write(stream.read())
                self.answers_file = app_path_answers

            # Check to make sure the file exists
            if not os.path.isfile(self.answers_file):
                raise NuleculeException("Provided answers file doesn't exist: {}".format(self.answers_file))

            # Load answers
            answers = Utils.loadAnswers(self.answers_file, self.answers_format)

        self.config.update_source(source="answers", data=answers)

    def _write_answers(self, path, answers, answers_format):
        """
        Write answers data to file.

        Args:
            path (str): path to answers file to write to
            answers (dict): Answers data
            answers_format (str): Format to use to dump answers data to file,
                                  e.g., json
        Returns:
            None
        """
        logger.debug("Writing answers to file.")
        logger.debug("FILE: %s", path)
        logger.debug("ANSWERS: %s", answers)
        logger.debug("ANSWERS FORMAT: %s", answers_format)
        anymarkup.serialize_file(answers, path, format=answers_format)

        # Make sure that the permission of the file is set to the current user
        Utils.setFileOwnerGroup(path)

    # TODO - once we rework config data we shouldn't need this
    # function anymore, we should be able to take the data
    # straight from the config object since the defaults and args
    # provided from the cli would have already been merged.
    def _get_runtime_answers(self, config, cli_provider):
        """
        Get runtime answers data from config (Nulecule config) by adding
        default data if missing.

        Args:
            config (dict): Nulecule config data
            cli_provider (str): Provider used for running Nulecule application

        Returns:
            dict
        """
        return self.nulecule.config.runtime_answers()
示例#12
0
def get_answers(nulecule_path):
    nulecule = Nulecule.load_from_path(nulecule_path)
    nulecule.config = Config()
    nulecule.load_config(skip_asking=True)
    return nulecule.config.runtime_answers()
示例#13
0
    def __init__(self,
                 app_spec,
                 destination=None,
                 cli_answers=None,
                 answers_file=None,
                 answers_format=None):
        """
        init function for NuleculeManager. Sets a few instance variables.

        Args:
            app_spec: either a path to an unpacked nulecule app or a
                      container image name where a nulecule can be found
            destination: where to unpack a nulecule to if it isn't local
            cli_answers: some answer file values provided from cli args
            answers_file: the location of the answers file
            answers_format (str): File format for writing sample answers file
        """
        self.answers_format = answers_format or ANSWERS_FILE_SAMPLE_FORMAT
        self.answers_file = None  # The path to an answer file
        self.app_path = None  # The path where the app resides or will reside
        self.image = None  # The container image to pull the app from

        # Adjust app_spec, destination, and answer file paths if absolute.
        if os.path.isabs(app_spec):
            app_spec = Utils.get_real_abspath(app_spec)
        if destination and os.path.isabs(destination):
            destination = Utils.get_real_abspath(destination)
        if answers_file and os.path.isabs(answers_file):
            answers_file = Utils.get_real_abspath(answers_file)

        # If the user doesn't want the files copied to a permanent
        # location then he provides 'none'. If that is the case we'll
        # use a temporary directory
        if destination and destination.lower() == 'none':
            logger.debug("'none' destination requested. Using tmp dir")
            destination = tempfile.mkdtemp(prefix='atomicapp')

        # Determine if the user passed us an image or a path to an app
        if not os.path.exists(app_spec):
            self.image = app_spec
        else:
            self.app_path = app_spec

        # Doesn't really make much sense to provide an app path and destination,
        # but if they want to we'll simply just copy the files for them
        if self.app_path and destination:
            Utils.copy_dir(self.app_path, destination, update=True)
            self.app_path = destination

        # If the user provided an image, make sure we have a destination
        if self.image:
            if destination:
                self.app_path = destination
            else:
                self.app_path = Utils.getNewAppCacheDir(self.image)

        logger.debug("NuleculeManager init app_path: %s", self.app_path)
        logger.debug("NuleculeManager init image: %s", self.image)

        # Create the app_path if it doesn't exist yet
        if not os.path.isdir(self.app_path):
            os.makedirs(self.app_path)

        # Set where the main nulecule file should be
        self.main_file = os.path.join(self.app_path, MAIN_FILE)

        # Process answers.
        self.answers_file = answers_file
        self.config = Config(cli=cli_answers)
示例#14
0
class NuleculeManager(object):
    """
    Interface to fetch, run, stop a Nulecule application.
    """
    def __init__(self,
                 app_spec,
                 destination=None,
                 cli_answers=None,
                 answers_file=None,
                 answers_format=None):
        """
        init function for NuleculeManager. Sets a few instance variables.

        Args:
            app_spec: either a path to an unpacked nulecule app or a
                      container image name where a nulecule can be found
            destination: where to unpack a nulecule to if it isn't local
            cli_answers: some answer file values provided from cli args
            answers_file: the location of the answers file
            answers_format (str): File format for writing sample answers file
        """
        self.answers_format = answers_format or ANSWERS_FILE_SAMPLE_FORMAT
        self.answers_file = None  # The path to an answer file
        self.app_path = None  # The path where the app resides or will reside
        self.image = None  # The container image to pull the app from

        # Adjust app_spec, destination, and answer file paths if absolute.
        if os.path.isabs(app_spec):
            app_spec = Utils.get_real_abspath(app_spec)
        if destination and os.path.isabs(destination):
            destination = Utils.get_real_abspath(destination)
        if answers_file and os.path.isabs(answers_file):
            answers_file = Utils.get_real_abspath(answers_file)

        # If the user doesn't want the files copied to a permanent
        # location then he provides 'none'. If that is the case we'll
        # use a temporary directory
        if destination and destination.lower() == 'none':
            logger.debug("'none' destination requested. Using tmp dir")
            destination = tempfile.mkdtemp(prefix='atomicapp')

        # Determine if the user passed us an image or a path to an app
        if not os.path.exists(app_spec):
            self.image = app_spec
        else:
            self.app_path = app_spec

        # Doesn't really make much sense to provide an app path and destination,
        # but if they want to we'll simply just copy the files for them
        if self.app_path and destination:
            Utils.copy_dir(self.app_path, destination, update=True)
            self.app_path = destination

        # If the user provided an image, make sure we have a destination
        if self.image:
            if destination:
                self.app_path = destination
            else:
                self.app_path = Utils.getNewAppCacheDir(self.image)

        logger.debug("NuleculeManager init app_path: %s", self.app_path)
        logger.debug("NuleculeManager init image: %s", self.image)

        # Create the app_path if it doesn't exist yet
        if not os.path.isdir(self.app_path):
            os.makedirs(self.app_path)

        # Set where the main nulecule file should be
        self.main_file = os.path.join(self.app_path, MAIN_FILE)

        # Process answers.
        self.answers_file = answers_file
        self.config = Config(cli=cli_answers)

    @staticmethod
    def init(app_name,
             destination=None,
             app_version='1.0',
             app_desc='App description'):
        """Initialize a new Nulecule app

        Args:
            app_name (str): Application name
            destination (str): Destination path
            app_version (str): Application version
            app_desc (str): Application description

        Returns:
            destination (str)
        """

        # context to render template files for Atomic App
        context = dict(app_name=app_name,
                       app_version=app_version,
                       app_desc=app_desc,
                       atomicapp_version=__ATOMICAPPVERSION__,
                       nulecule_spec_version=__NULECULESPECVERSION__)

        if destination is None:
            destination = os.path.join('.', app_name)

        # Check if destination directory exists and is not empty
        if os.path.exists(destination) and \
           os.path.isdir(destination) and os.listdir(destination):
            value = raw_input('Destination directory is not empty! '
                              'Do you still want to proceed? [Y]/n: ')
            value = value or 'y'
            if value.lower() != 'y':
                return  # Exit out as the user has chosen not to proceed

        # Temporary working dir to render the templates
        tmpdir = tempfile.mkdtemp(prefix='nulecule-new-app-')
        template_dir = os.path.join(os.path.dirname(__file__),
                                    'external/templates/nulecule')

        try:
            # Copy template dir to temporary working directory and render templates
            distutils.dir_util.copy_tree(template_dir, tmpdir)
            for item in os.walk(tmpdir):
                parent_dir, dirs, files = item
                for filename in files:
                    if not filename.endswith('.tpl'):
                        continue
                    templ_path = os.path.join(parent_dir, filename)
                    if parent_dir.endswith('artifacts/docker') or \
                       parent_dir.endswith('artifacts/kubernetes'):
                        file_path = os.path.join(
                            parent_dir, '{}_{}'.format(app_name,
                                                       filename[:-4]))
                    else:
                        file_path = os.path.join(parent_dir, filename[:-4])
                    with open(templ_path) as f:
                        s = f.read()
                    t = Template(s)
                    with open(file_path, 'w') as f:
                        f.write(t.safe_substitute(**context))
                    os.remove(templ_path)

            # Copy rendered templates to destination directory
            distutils.dir_util.copy_tree(tmpdir, destination, True)
        finally:
            # Remove temporary working directory
            distutils.dir_util.remove_tree(tmpdir)
        return destination

    def unpack(self, update=False, dryrun=False, nodeps=False, config=None):
        """
        Unpacks a Nulecule application from a Nulecule image to a path
        or load a Nulecule that already exists locally.

        Args:
            update (bool): Update existing Nulecule application in
                           app_path, if True
            dryrun (bool): Do not make any change to the host system
            nodeps (bool): Do not unpack external dependencies
            config (dict): Config data, if any, to use for unpacking

        Returns:
            A Nulecule instance.
        """
        logger.debug('Request to unpack to %s to %s' %
                     (self.image, self.app_path))

        # If the user provided an image then unpack it and return the
        # resulting Nulecule. Else, load from existing path
        if self.image:
            return Nulecule.unpack(self.image,
                                   self.app_path,
                                   config=config,
                                   nodeps=nodeps,
                                   dryrun=dryrun,
                                   update=update)
        else:
            return Nulecule.load_from_path(self.app_path,
                                           dryrun=dryrun,
                                           config=config)

    def genanswers(self, dryrun=False, **kwargs):
        """
        Renders artifacts and then generates an answer file. Finally
        copies answer file to the current working directory.

        Args:
            dryrun (bool): Do not make any change to the host system if True
            kwargs (dict): Extra keyword arguments

        Returns:
            None
        """

        # Check to make sure an answers.conf file doesn't exist already
        answers_file = os.path.join(os.getcwd(), ANSWERS_FILE)
        if os.path.exists(answers_file):
            raise NuleculeException(
                "Can't generate answers.conf over existing file")

        # Call unpack to get the app code
        self.nulecule = self.unpack(update=False,
                                    dryrun=dryrun,
                                    config=self.config)

        self.nulecule.load_config(skip_asking=True)
        # Get answers and write them out to answers.conf in cwd
        answers = self._get_runtime_answers(self.nulecule.config, None)
        self._write_answers(answers_file, answers, self.answers_format)

    def fetch(self, nodeps=False, update=False, dryrun=False, **kwargs):
        """
        Installs (unpacks) a Nulecule application from a Nulecule image
        to a target path.
        Args:
            answers (dict or str): Answers data or local path to answers file
            nodeps (bool): Install the nulecule application without installing
                           external dependencies
            update (bool): Pull requisite Nulecule image and install or
                           update already installed Nulecule application
            dryrun (bool): Do not make any change to the host system if True
            kwargs (dict): Extra keyword arguments
        Returns:
            None
        """
        # Call unpack. If the app doesn't exist it will be pulled. If
        # it does exist it will be just be loaded and returned
        self.nulecule = self.unpack(update, dryrun, config=self.config)

        self.nulecule.load_config(skip_asking=True)
        runtime_answers = self._get_runtime_answers(self.nulecule.config, None)
        # write sample answers file
        self._write_answers(os.path.join(self.app_path, ANSWERS_FILE_SAMPLE),
                            runtime_answers, self.answers_format)

        cockpit_logger.info("Install Successful.")

    def run(self, answers_output, ask, **kwargs):
        """
        Runs a Nulecule application from a local path or a Nulecule image
        name.

        Args:
            answers (dict or str): Answers data or local path to answers file
            answers_output (str): Path to file to export runtime answers data
                                  to
            ask (bool): Ask for values for params with default values from
                        user, if True
            kwargs (dict): Extra keyword arguments

        Returns:
            None
        """
        dryrun = kwargs.get('dryrun') or False

        # Call unpack. If the app doesn't exist it will be pulled. If
        # it does exist it will be just be loaded and returned
        self.nulecule = self.unpack(dryrun=dryrun, config=self.config)

        # Process answers file
        self._process_answers()

        self.nulecule.load_config(ask=ask)
        provider = self.nulecule.config.get('provider')
        self.nulecule.render(provider, dryrun)
        self.nulecule.run(provider, dryrun)
        runtime_answers = self._get_runtime_answers(self.nulecule.config,
                                                    provider)
        self._write_answers(os.path.join(self.app_path, ANSWERS_RUNTIME_FILE),
                            runtime_answers, self.answers_format)
        if answers_output:
            self._write_answers(answers_output, runtime_answers,
                                self.answers_format)

    def stop(self, **kwargs):
        """
        Stops a running Nulecule application.

        Args:
            kwargs (dict): Extra keyword arguments
        """
        # For stop we use the generated answer file from the run
        self.answers_file = os.path.join(self.app_path, ANSWERS_RUNTIME_FILE)
        self._process_answers()

        dryrun = kwargs.get('dryrun') or False
        self.nulecule = Nulecule.load_from_path(self.app_path,
                                                config=self.config,
                                                dryrun=dryrun)
        self.nulecule.load_config()
        self.nulecule.render(self.nulecule.config.get('provider'),
                             dryrun=dryrun)
        self.nulecule.stop(self.nulecule.config.get('provider'), dryrun)

    def clean(self, force=False):
        # For future use
        self.uninstall()
        distutils.dir_util.remove_tree(self.unpack_path)
        self.initialize()

    def _process_answers(self):
        """
        Processes answer files to load data from them and then merges
        any cli provided answers into the config.

        NOTE: This function should be called once on startup and then
        once more after the application has been extracted, but only
        if answers file wasn't found on the first invocation. The idea
        is to allow for people to embed an answers file in the application
        if they want, which won't be available until after extraction.

        Returns:
            None
        """
        answers = None
        app_path_answers = os.path.join(self.app_path, ANSWERS_FILE)

        # If the user didn't provide an answers file then check the app
        # dir to see if one exists.
        if not self.answers_file:
            if os.path.isfile(app_path_answers):
                self.answers_file = app_path_answers

        # At this point if we have an answers file, load it
        if self.answers_file:

            # If this is a url then download answers file to app directory
            if urlparse.urlparse(self.answers_file).scheme != "":
                logger.debug("Retrieving answers file from: {}".format(
                    self.answers_file))
                with open(app_path_answers, 'w+') as f:
                    stream = urllib.urlopen(self.answers_file)
                    f.write(stream.read())
                self.answers_file = app_path_answers

            # Check to make sure the file exists
            if not os.path.isfile(self.answers_file):
                raise NuleculeException(
                    "Provided answers file doesn't exist: {}".format(
                        self.answers_file))

            # Load answers
            answers = Utils.loadAnswers(self.answers_file, self.answers_format)

        self.config.update_source(source='answers', data=answers)

    def _write_answers(self, path, answers, answers_format):
        """
        Write answers data to file.

        Args:
            path (str): path to answers file to write to
            answers (dict): Answers data
            answers_format (str): Format to use to dump answers data to file,
                                  e.g., json
        Returns:
            None
        """
        logger.debug("Writing answers to file.")
        logger.debug("FILE: %s", path)
        logger.debug("ANSWERS: %s", answers)
        logger.debug("ANSWERS FORMAT: %s", answers_format)
        anymarkup.serialize_file(answers, path, format=answers_format)

        # Make sure that the permission of the file is set to the current user
        Utils.setFileOwnerGroup(path)

    # TODO - once we rework config data we shouldn't need this
    # function anymore, we should be able to take the data
    # straight from the config object since the defaults and args
    # provided from the cli would have already been merged.
    def _get_runtime_answers(self, config, cli_provider):
        """
        Get runtime answers data from config (Nulecule config) by adding
        default data if missing.

        Args:
            config (dict): Nulecule config data
            cli_provider (str): Provider used for running Nulecule application

        Returns:
            dict
        """
        return self.nulecule.config.runtime_answers()