Revision 4884f187

b/lib/cli.py
44 44
from ganeti import qlang
45 45
from ganeti import objects
46 46
from ganeti import pathutils
47
from ganeti import serializer
47 48

  
48 49
from ganeti.runtime import (GetClient)
49 50

  
......
679 680
  return _SplitKeyVal(opt, value, True)
680 681

  
681 682

  
683
def check_key_private_val(option, opt, value):  # pylint: disable=W0613
684
  """Custom parser class for private and secret key=val,key=val options.
685

  
686
  This will store the parsed values as a dict {key: val}.
687

  
688
  """
689
  return serializer.PrivateDict(_SplitKeyVal(opt, value, True))
690

  
691

  
682 692
def _SplitListKeyVal(opt, value):
683 693
  retval = {}
684 694
  for elem in value.split("/"):
......
781 791
    "multilistidentkeyval",
782 792
    "identkeyval",
783 793
    "keyval",
794
    "keyprivateval",
784 795
    "unit",
785 796
    "bool",
786 797
    "list",
......
790 801
  TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val
791 802
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
792 803
  TYPE_CHECKER["keyval"] = check_key_val
804
  TYPE_CHECKER["keyprivateval"] = check_key_private_val
793 805
  TYPE_CHECKER["unit"] = check_unit
794 806
  TYPE_CHECKER["bool"] = check_bool
795 807
  TYPE_CHECKER["list"] = check_list
b/lib/config.py
2488 2488
    if destination is None:
2489 2489
      destination = self._cfg_file
2490 2490
    self._BumpSerialNo()
2491
    txt = serializer.Dump(self._config_data.ToDict())
2491
    txt = serializer.DumpJson(
2492
      self._config_data.ToDict(_with_private=True),
2493
      private_encoder=serializer.EncodeWithPrivateFields
2494
    )
2492 2495

  
2493 2496
    getents = self._getents()
2494 2497
    try:
b/lib/ht.py
29 29
from ganeti import utils
30 30
from ganeti import constants
31 31
from ganeti import objects
32

  
32
from ganeti.serializer import Private
33 33

  
34 34
_PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
35 35

  
......
77 77
  def __str__(self):
78 78
    return self._text
79 79

  
80
  def __repr__(self):
81
    return "<%s %r>" % (self._text, self._fn)
82

  
80 83

  
81 84
class _CommentWrapper(_WrapperBase):
82 85
  """Wrapper class for comment.
......
269 272
def TDict(val):
270 273
  """Checks if the given value is a dictionary.
271 274

  
275
  Note that L{PrivateDict}s subclass dict and pass this check.
276

  
272 277
  """
273 278
  return isinstance(val, dict)
274 279

  
......
416 421
  return desc(lambda val: isinstance(val, cls))
417 422

  
418 423

  
424
def TPrivate(val_type):
425
  """Checks if a given value is an instance of Private.
426

  
427
  """
428
  def fn(val):
429
    return isinstance(val, Private) and val_type(val.Get())
430

  
431
  desc = WithDesc("Private %s" % Parens(val_type))
432

  
433
  return desc(fn)
434

  
435

  
419 436
def TListOf(my_type):
420 437
  """Checks if a given value is a list with all elements of the same type.
421 438

  
b/lib/serializer.py
166 166
Load = LoadJson
167 167
DumpSigned = DumpSignedJson
168 168
LoadSigned = LoadSignedJson
169

  
170

  
171
class Private(object):
172
  """Wrap a value so it is hard to leak it accidentally.
173

  
174
  >>> x = Private("foo")
175
  >>> print "Value: %s" % x
176
  Value: <redacted>
177
  >>> print "Value: {0}".format(x)
178
  Value: <redacted>
179
  >>> x.upper() == "FOO"
180
  True
181

  
182
  """
183
  def __init__(self, item, descr="redacted"):
184
    if isinstance(item, Private):
185
      raise ValueError("Attempted to nest Private values.")
186
    self._item = item
187
    self._descr = descr
188

  
189
  def Get(self):
190
    "Return the wrapped value."
191
    return self._item
192

  
193
  def __str__(self):
194
    return "<{._descr}>".format(self)
195

  
196
  def __repr__(self):
197
    return "Private(?, descr='{._descr}')".format(self)
198

  
199
  # pylint: disable=W0212
200
  # If it doesn't access _item directly, the call will go through __getattr__
201
  # because this class defines __slots__ and "item" is not in it.
202
  # OTOH, if we do add it there, we'd risk shadowing an "item" attribute.
203
  def __eq__(self, other):
204
    if isinstance(other, Private):
205
      return self._item == other._item
206
    else:
207
      return self._item == other
208

  
209
  def __hash__(self):
210
    return hash(self._item)
211

  
212
  def __format__(self, *_1, **_2):
213
    return self.__str__()
214

  
215
  def __getattr__(self, attr):
216
    return Private(getattr(self._item, attr),
217
                   descr="%s.%s" % (self._descr, attr))
218

  
219
  def __call__(self, *args, **kwargs):
220
    return Private(self._item(*args, **kwargs),
221
                   descr="%s()" % self._descr)
222

  
223
  # pylint: disable=R0201
224
  # While this could get away with being a function, it needs to be a method.
225
  # Required by the copy.deepcopy function used by FillDict.
226
  def __getnewargs__(self):
227
    return tuple()
228

  
229
  def __nonzero__(self):
230
    return bool(self._item)
231

  
232
  # Get in the way of Pickle by implementing __slots__ but not __getstate__
233
  # ...and get a performance boost, too.
234
  __slots__ = ["_item", "_descr"]
235

  
236

  
237
class PrivateDict(dict):
238
  """A dictionary that turns its values to private fields.
239

  
240
  >>> PrivateDict()
241
  {}
242
  >>> supersekkrit = PrivateDict({"password": "foobar"})
243
  >>> print supersekkrit["password"]
244
  <password>
245
  >>> supersekkrit["password"].Get()
246
  'foobar'
247
  >>> supersekkrit.GetPrivate("password")
248
  'foobar'
249
  >>> supersekkrit["user"] = "eggspam"
250
  >>> supersekkrit.Unprivate()
251
  {'password': 'foobar', 'user': 'eggspam'}
252

  
253
  """
254
  def __init__(self, data=None):
255
    dict.__init__(self)
256
    self.update(data)
257

  
258
  def __setitem__(self, item, value):
259
    if not isinstance(value, Private):
260
      if not isinstance(item, dict):
261
        value = Private(value, descr=item)
262
      else:
263
        value = PrivateDict(value)
264
    dict.__setitem__(self, item, value)
265

  
266
  # The actual conversion to Private containers is done by __setitem__
267

  
268
  # copied straight from cpython/Lib/UserDict.py
269
  # Copyright (c) 2001-2014 Python Software Foundation; All Rights Reserved
270
  def update(self, other=None, **kwargs):
271
    # Make progressively weaker assumptions about "other"
272
    if other is None:
273
      pass
274
    elif hasattr(other, 'iteritems'):  # iteritems saves memory and lookups
275
      for k, v in other.iteritems():
276
        self[k] = v
277
    elif hasattr(other, 'keys'):
278
      for k in other.keys():
279
        self[k] = other[k]
280
    else:
281
      for k, v in other:
282
        self[k] = v
283
    if kwargs:
284
      self.update(kwargs)
285

  
286
  def GetPrivate(self, *args):
287
    """Like dict.get, but extracting the value in the process.
288

  
289
    Arguments are semantically equivalent to ``dict.get``
290

  
291
    >>> PrivateDict({"foo": "bar"}).GetPrivate("foo")
292
    'bar'
293
    >>> PrivateDict({"foo": "bar"}).GetPrivate("baz", "spam")
294
    'spam'
295

  
296
    """
297
    if len(args) == 1:
298
      key, = args
299
      return self[key].Get()
300
    elif len(args) == 2:
301
      key, default = args
302
      if key not in self:
303
        return default
304
      else:
305
        return self[key].Get()
306
    else:
307
      raise TypeError("GetPrivate() takes 2 arguments (%d given)" % len(args))
308

  
309
  def Unprivate(self):
310
    """Turn this dict of Private() values to a dict of values.
311

  
312
    >>> PrivateDict({"foo": "bar"}).Unprivate()
313
    {'foo': 'bar'}
314

  
315
    @rtype: dict
316

  
317
    """
318
    returndict = {}
319
    for key in self:
320
      returndict[key] = self[key].Get()
321
    return returndict
b/src/Ganeti/JSON.hs
48 48
  , toArray
49 49
  , optionalJSField
50 50
  , optFieldsToObj
51
  , readContainer
51 52
  , HasStringRepr(..)
52 53
  , GenericContainer(..)
53 54
  , Container
b/src/Ganeti/Types.hs
159 159
  , hotplugTargetToRaw
160 160
  , HotplugAction(..)
161 161
  , hotplugActionToRaw
162
  , Private(..)
163
  , showPrivateJSObject
162 164
  ) where
163 165

  
164 166
import Control.Monad (liftM)
......
689 691
absoluteJobIdDep :: (Monad m) => JobIdDep -> JobId -> m JobIdDep
690 692
absoluteJobIdDep (JobDepAbsolute jid) _ = return $ JobDepAbsolute jid
691 693
absoluteJobIdDep (JobDepRelative rjid) jid =
692
  liftM JobDepAbsolute . makeJobId $ fromJobId jid + fromNegative rjid 
694
  liftM JobDepAbsolute . makeJobId $ fromJobId jid + fromNegative rjid
693 695

  
694 696
-- | Job Dependency type.
695 697
data JobDependency = JobDependency JobIdDep [FinalizedJobStatus]
......
702 704
-- | From job dependency and job id compute an absolute job dependency.
703 705
absoluteJobDependency :: (Monad m) => JobDependency -> JobId -> m JobDependency
704 706
absoluteJobDependency (JobDependency jdep fstats) jid =
705
  liftM (flip JobDependency fstats) $ absoluteJobIdDep jdep jid 
707
  liftM (flip JobDependency fstats) $ absoluteJobIdDep jdep jid
706 708

  
707 709
-- | Valid opcode priorities for submit.
708 710
$(THH.declareIADT "OpSubmitPriority"
......
889 891
  , ("HTNic",  "hotnic")
890 892
  ])
891 893
$(THH.makeJSONInstance ''HotplugTarget)
894

  
895
-- * Private type and instances
896

  
897
-- | A container for values that should be happy to be manipulated yet
898
-- refuses to be shown unless explicitly requested.
899
newtype Private a = Private { getPrivate :: a }
900
  deriving Eq
901

  
902
instance (Show a, JSON.JSON a) => JSON.JSON (Private a) where
903
  readJSON = liftM Private . JSON.readJSON
904
  showJSON (Private x) = JSON.showJSON x
905

  
906
-- | "Show" the value of the field.
907
--
908
-- It would be better not to implement this at all.
909
-- Alas, Show OpCode requires Show Private.
910
instance Show a => Show (Private a) where
911
  show _ = "<redacted>"
912

  
913
instance THH.PyValue a => THH.PyValue (Private a) where
914
  showValue (Private x) = "Private(" ++ THH.showValue x ++ ")"
915

  
916
instance Functor Private where
917
  fmap f (Private x) = Private $ f x
918

  
919
instance Monad Private where
920
  (Private x) >>= f = f x
921
  return = Private
922

  
923
showPrivateJSObject :: (JSON.JSON a) =>
924
                       [(String, a)] -> JSON.JSObject (Private JSON.JSValue)
925
showPrivateJSObject value = JSON.toJSObject $ map f value
926
  where f (k, v) = (k, Private $ JSON.showJSON v)
b/test/hs/Test/Ganeti/Objects.hs
219 219
instance Arbitrary OsParams where
220 220
  arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
221 221

  
222
instance Arbitrary Objects.ClusterOsParamsPrivate where
223
  arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
224

  
225
instance Arbitrary a => Arbitrary (Private a) where
226
  arbitrary = Private <$> arbitrary
227

  
222 228
instance Arbitrary ClusterOsParams where
223 229
  arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
224 230

  
......
552 558
caseIncludeLogicalIdDrbd =
553 559
  let vg_name = "xenvg" :: String
554 560
      lv_name = "1234sdf-qwef-2134-asff-asd2-23145d.data" :: String
555
      d = 
561
      d =
556 562
        Disk
557 563
          (LIDDrbd8 "node1.example.com" "node2.example.com" 2000 1 5 "secret")
558 564
          [ Disk (LIDPlain "onevg" "onelv") [] "disk1" 1000 DiskRdWr Nothing
b/test/hs/Test/Ganeti/OpCodes.hs
431 431
  octets <- vectorOf 3 $ choose (0::Int, 255)
432 432
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
433 433

  
434
-- | JSObject of arbitrary data.
435
--
436
-- Since JSValue does not implement Arbitrary, I'll simply generate
437
-- (String, String) objects.
438
arbitraryPrivateJSObj :: Gen (J.JSObject (Private J.JSValue))
439
arbitraryPrivateJSObj =
440
  constructor <$> (fromNonEmpty <$> genNameNE)
441
              <*> (fromNonEmpty <$> genNameNE)
442
    where constructor k v = showPrivateJSObject [(k, v)]
443

  
434 444
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
435 445
$(genArbitrary ''OpCodes.MetaOpCode)
436 446

  

Also available in: Unified diff