This file is indexed.

/usr/share/pyshared/juju/charm/metadata.py is in juju-0.7 0.7-0ubuntu2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
import logging
import os

import yaml

from juju.charm.errors import MetaDataError
from juju.errors import FileNotFound
from juju.lib import serializer
from juju.lib.format import is_valid_charm_format
from juju.lib.schema import (
    SchemaError, Bool, Constant, Dict, Int,
    KeyDict, OneOf, UnicodeOrString)


log = logging.getLogger("juju.charm")
UTF8_SCHEMA = UnicodeOrString("utf-8")
SCOPE_GLOBAL = "global"
SCOPE_CONTAINER = "container"

INTERFACE_SCHEMA = KeyDict({
    "interface": UTF8_SCHEMA,
    "limit": OneOf(Constant(None), Int()),
    "scope": OneOf(Constant(SCOPE_GLOBAL), Constant(SCOPE_CONTAINER)),
    "optional": Bool()},
    optional=["scope"])


class InterfaceExpander(object):
    """Schema coercer that expands the interface shorthand notation.

    We need this class because our charm shorthand is difficult to
    work with (unfortunately). So we coerce shorthand and then store
    the desired format in ZK.

    Supports the following variants::

      provides:
        server: riak
        admin: http
        foobar:
          interface: blah

      provides:
        server:
          interface: mysql
          limit:
          optional: false

    In all input cases, the output is the fully specified interface
    representation as seen in the mysql interface description above.
    """

    def __init__(self, limit):
        """Create relation interface reshaper.

        @limit: the limit for this relation. Used to provide defaults
            for a given kind of relation role (peer, provider, consumer)
        """
        self.limit = limit

    def coerce(self, value, path):
        """Coerce `value` into an expanded interface.

        Helper method to support each of the variants, either the
        charm does not specify limit and optional, such as foobar in
        the above example; or the interface spec is just a string,
        such as the ``server: riak`` example.
        """
        if not isinstance(value, dict):
            return {
                "interface": UTF8_SCHEMA.coerce(value, path),
                "limit": self.limit,
                "scope": SCOPE_GLOBAL,
                "optional": False}
        else:
            # Optional values are context-sensitive and/or have
            # defaults, which is different than what KeyDict can
            # readily support. So just do it here first, then
            # coerce.
            if "limit" not in value:
                value["limit"] = self.limit
            if "optional" not in value:
                value["optional"] = False
            value["scope"] = value.get("scope", SCOPE_GLOBAL)
            return INTERFACE_SCHEMA.coerce(value, path)


SCHEMA = KeyDict({
    "name": UTF8_SCHEMA,
    "revision": Int(),
    "summary": UTF8_SCHEMA,
    "description": UTF8_SCHEMA,
    "format": Int(),
    "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
    "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),
    "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
    "subordinate": Bool(),
    }, optional=set(
        ["format", "provides", "requires", "peers", "revision",
         "subordinate"]))


class MetaData(object):
    """Represents the charm info file.

    The main metadata for a charm (name, revision, etc) is maintained
    in the charm's info file.  This class is able to parse,
    validate, and provide access to data in the info file.
    """

    def __init__(self, path=None):
        self._data = {}
        if path is not None:
            self.load(path)

    @property
    def name(self):
        """The charm name."""
        return self._data.get("name")

    @property
    def obsolete_revision(self):
        """The charm revision.

        The charm revision acts as a version, but unlike e.g. package
        versions, the charm revision is a monotonically increasing
        integer. This should not be stored in metadata any more, but remains
        for backward compatibility's sake.
        """
        return self._data.get("revision")

    @property
    def summary(self):
        """The charm summary."""
        return self._data.get("summary")

    @property
    def description(self):
        """The charm description."""
        return self._data.get("description")

    @property
    def format(self):
        """Optional charm format, defaults to 1"""
        return self._data.get("format", 1)

    @property
    def provides(self):
        """The charm provides relations."""
        return self._data.get("provides")

    @property
    def requires(self):
        """The charm requires relations."""
        return self._data.get("requires")

    @property
    def peers(self):
        """The charm peers relations."""
        return self._data.get("peers")

    @property
    def is_subordinate(self):
        """Indicates the charm requires a contained relationship.

        This property will effect the deployment options of its
        charm. When a charm is_subordinate it can only be deployed
        when its contained relationship is satisfied. See the
        subordinates specification.
        """
        return self._data.get("subordinate", False)

    def get_serialization_data(self):
        """Get internal dictionary representing the state of this instance.

        This is useful to embed this information inside other storage-related
        dictionaries.
        """
        return dict(self._data)

    def load(self, path):
        """Load and parse the info file.

        @param path: Path of the file to load.

        Internally, this function will pass the content of the file to
        the C{parse()} method.
        """
        if not os.path.isfile(path):
            raise FileNotFound(path)
        with open(path) as f:
            self.parse(f.read(), path)

    def parse(self, content, path=None):
        """Parse the info file described by the given content.

        @param content: Content of the info file to parse.
        @param path: Optional path of the loaded file.  Used when raising
            errors.

        @raise MetaDataError: When errors are found in the info data.
        """
        try:
            self.parse_serialization_data(
                serializer.yaml_load(content), path)
        except yaml.MarkedYAMLError, e:
            # Capture the path name on the error if present.
            if path is not None:
                e.problem_mark = serializer.yaml_mark_with_path(
                    path, e.problem_mark)
            raise

        if "revision" in self._data and path:
            log.warning(
                "%s: revision field is obsolete. Move it to the 'revision' "
                "file." % path)

        if self.provides:
            for rel in self.provides:
                if rel.startswith("juju-"):
                    raise MetaDataError(
                        "Charm %s attempting to provide relation in "
                        "implicit relation namespace: %s" %
                        (self.name, rel))
                interface = self.provides[rel]["interface"]
                if interface.startswith("juju-"):
                    raise MetaDataError(
                        "Charm %s attempting to provide interface in implicit namespace: "
                        "%s (relation: %s)" % (self.name, interface, rel))

        if self.is_subordinate:
            proper_subordinate = False
            if self.requires:
                for relation_data in self.requires.values():
                    if relation_data.get("scope") == SCOPE_CONTAINER:
                        proper_subordinate = True
            if not proper_subordinate:
                raise MetaDataError(
                    "%s labeled subordinate but lacking scope:container `requires` relation" %
                    path)

        if not is_valid_charm_format(self.format):
            raise MetaDataError("Charm %s uses an unknown format: %s" % (
                    self.name, self.format))

    def parse_serialization_data(self, serialization_data, path=None):
        """Parse the unprocessed serialization data and load in this instance.

        @param serialization_data: Unprocessed data matching the
            metadata schema.
        @param path: Optional path of the loaded file.  Used when
            raising errors.

        @raise MetaDataError: When errors are found in the info data.
        """
        try:
            self._data = SCHEMA.coerce(serialization_data, [])
        except SchemaError, error:
            if path:
                path_info = " %s:" % path
            else:
                path_info = ""
            raise MetaDataError("Bad data in charm info:%s %s" %
                                (path_info, error))