diff --git a/lib/cli.py b/lib/cli.py
index e9bec2aed0eed2e5109d4affedba87299cc00957..d225bafed2ab9adce84ccd2f985a9bd9b50f6c47 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -32,6 +32,7 @@ from ganeti import logger
 from ganeti import errors
 from ganeti import mcpu
 from ganeti import constants
+from ganeti import opcodes
 
 from optparse import (OptionParser, make_option, TitledHelpFormatter,
                       Option, OptionValueError, SUPPRESS_HELP)
@@ -39,7 +40,81 @@ from optparse import (OptionParser, make_option, TitledHelpFormatter,
 __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode",
            "cli_option", "GenerateTable", "AskUser",
            "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
-           "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT"]
+           "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT",
+           "ListTags", "AddTags", "RemoveTags",
+           ]
+
+
+def _ExtractTagsObject(opts, args):
+  """Extract the tag type object.
+
+  Note that this function will modify its args parameter.
+
+  """
+  if not hasattr(opts, "tag_type"):
+    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
+  kind = opts.tag_type
+  if kind == constants.TAG_CLUSTER:
+    retval = kind, kind
+  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
+    if not args:
+      raise errors.OpPrereq("no arguments passed to the command")
+    name = args.pop(0)
+    retval = kind, name
+  else:
+    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
+  return retval
+
+
+def ListTags(opts, args):
+  """List the tags on a given object.
+
+  This is a generic implementation that knows how to deal with all
+  three cases of tag objects (cluster, node, instance). The opts
+  argument is expected to contain a tag_type field denoting what
+  object type we work on.
+
+  """
+  kind, name = _ExtractTagsObject(opts, args)
+  op = opcodes.OpGetTags(kind=kind, name=name)
+  result = SubmitOpCode(op)
+  result = list(result)
+  result.sort()
+  for tag in result:
+    print tag
+
+
+def AddTags(opts, args):
+  """Add tags on a given object.
+
+  This is a generic implementation that knows how to deal with all
+  three cases of tag objects (cluster, node, instance). The opts
+  argument is expected to contain a tag_type field denoting what
+  object type we work on.
+
+  """
+  kind, name = _ExtractTagsObject(opts, args)
+  if not args:
+    raise errors.OpPrereqError("No tags to be added")
+  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
+  SubmitOpCode(op)
+
+
+def RemoveTags(opts, args):
+  """Remove tags from a given object.
+
+  This is a generic implementation that knows how to deal with all
+  three cases of tag objects (cluster, node, instance). The opts
+  argument is expected to contain a tag_type field denoting what
+  object type we work on.
+
+  """
+  kind, name = _ExtractTagsObject(opts, args)
+  if not args:
+    raise errors.OpPrereqError("No tags to be removed")
+  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
+  SubmitOpCode(op)
+
 
 DEBUG_OPT = make_option("-d", "--debug", default=False,
                         action="store_true",
@@ -68,7 +143,6 @@ FORCE_OPT = make_option("-f", "--force", dest="force", action="store_true",
 _LOCK_OPT = make_option("--lock-retries", default=None,
                         type="int", help=SUPPRESS_HELP)
 
-
 def ARGS_FIXED(val):
   """Macro-like function denoting a fixed number of arguments"""
   return -val
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index 97ac0e8d332b04bf7a2e6035de8e0c635da229df..6361c3bcdd3f1cb9668b66a966ba28da6ff7cf3b 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -263,7 +263,13 @@ commands = {
               "Runs a command on all (or only some) nodes"),
   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
                  "", "Show cluster configuration"),
+  'list-tags': (ListTags, ARGS_NONE,
+                [DEBUG_OPT], "", "List the tags of the cluster"),
+  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT],
+               "tag...", "Add tags to the cluster"),
+  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT],
+                  "tag...", "Remove tags from the cluster"),
   }
 
 if __name__ == '__main__':
-  sys.exit(GenericMain(commands))
+  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))
diff --git a/scripts/gnt-instance b/scripts/gnt-instance
index 97b1f38d61653db23f49096c290fcf9649282677..488ed3e2b20e8ecfc894b872e0b5c2f85c9a6cc0 100755
--- a/scripts/gnt-instance
+++ b/scripts/gnt-instance
@@ -754,7 +754,14 @@ commands = {
   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
                        "<instance>",
                        "Deactivate an instance's disks"),
+  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
+                "<node_name>", "List the tags of the given instance"),
+  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT],
+               "<node_name> tag...", "Add tags to the given instance"),
+  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT],
+                  "<node_name> tag...", "Remove tags from given instance"),
   }
 
 if __name__ == '__main__':
-  sys.exit(GenericMain(commands))
+  sys.exit(GenericMain(commands,
+                       override={"tag_type": constants.TAG_INSTANCE}))
diff --git a/scripts/gnt-node b/scripts/gnt-node
index bfc9940e1f9c8e8f01b4172d2c126612284cb4b0..c87070b6a355b9750b847931c69d6fbe56f2d367 100755
--- a/scripts/gnt-node
+++ b/scripts/gnt-node
@@ -26,6 +26,7 @@ from ganeti.cli import *
 from ganeti import opcodes
 from ganeti import logger
 from ganeti import utils
+from ganeti import constants
 
 
 def AddNode(opts, args):
@@ -180,8 +181,14 @@ commands = {
   'volumes': (ListVolumes, ARGS_ANY,
               [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
               "[<node_name>...]", "List logical volumes on node(s)"),
+  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
+                "<node_name>", "List the tags of the given node"),
+  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT],
+               "<node_name> tag...", "Add tags to the given node"),
+  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT],
+                  "<node_name> tag...", "Remove tags from the given node"),
   }
 
 
 if __name__ == '__main__':
-  sys.exit(GenericMain(commands))
+  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))