Assign a label to a group of clients based on hostname.

  Sets a label as an identifier to a group of clients. Removes the label from
  other clients.

  This can be used to automate labeling clients based on externally derived
  attributes, for example machines assigned to particular users, or machines
  fulfilling particular roles.

  Args:
    label: The label to apply.
    hostnames: The collection of hostnames that should have the label.
    owner: The owner for the newly created labels. Defaults to token.username.
    token: The authentication token.
    client_index: An optional client index to use. If not provided, use the
      default client index.

def BulkLabel(label, hostnames, owner=None, token=None, client_index=None):
  """Assign a label to a group of clients based on hostname.

  Sets a label as an identifier to a group of clients. Removes the label from
  other clients.

  This can be used to automate labeling clients based on externally derived
  attributes, for example machines assigned to particular users, or machines
  fulfilling particular roles.

  Args:
    label: The label to apply.
    hostnames: The collection of hostnames that should have the label.
    owner: The owner for the newly created labels. Defaults to token.username.
    token: The authentication token.
    client_index: An optional client index to use. If not provided, use the
      default client index.
  """
  if client_index is None:
    client_index = CreateClientIndex(token=token)

  fqdns = set()
  for hostname in hostnames:
    fqdns.add(hostname.lower())

  labelled_urns = client_index.LookupClients(["+label:%s" % label])

  # If a labelled client fqdn isn't in the set of target fqdns remove the label.
  # Labelled clients with a target fqdn need no action and are removed from the
  # set of target fqdns.
  for client in aff4.FACTORY.MultiOpen(
      labelled_urns, token=token, aff4_type=aff4_grr.VFSGRRClient, mode="rw"):
    fqdn = utils.SmartStr(client.Get("FQDN")).lower()
    if fqdn not in fqdns:
      client_index.RemoveClientLabels(client)
      client.RemoveLabel(label, owner=owner)
      client.Flush()
      client_index.AddClient(client)
    else:
      fqdns.discard(fqdn)

  # The residual set of fqdns needs labelling.
  # Get the latest URN for these clients and open them to add the label.
  urns = []
  keywords = ["+host:%s" % fqdn for fqdn in fqdns]
  for client_list in client_index.ReadClientPostingLists(keywords).itervalues():
    for client_id in client_list:
      urns.append(rdfvalue.RDFURN(client_id))

  for client in aff4.FACTORY.MultiOpen(
      urns, token=token, aff4_type=aff4_grr.VFSGRRClient, mode="rw"):
    client.AddLabel(label, owner=owner)
    client.Flush()
    client_index.AddClient(client)