Support using container tag in rbe_autoconfig(). (#298)

* Support using container tag in rbe_autoconfig().

* Add a rbe_autoconfig target for RBE Ubuntu1604 container testing.
diff --git a/WORKSPACE b/WORKSPACE
index 225f6e9..513f28e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -101,7 +101,7 @@
     sha256 = "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9",
 )
 
-load("//rules:rbe_repo.bzl", "rbe_autoconfig")
+load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
 
 rbe_autoconfig(name = "rbe_default")
 
@@ -130,3 +130,12 @@
     ),
     output_base = "configs/ubuntu16_04_clang/1.1",
 )
+
+# Use in the RBE Ubuntu1604 container release.
+rbe_autoconfig(
+    name = "rbe_ubuntu1604_test",
+    env = clang_env(),
+    registry = "gcr.io",
+    repository = "asci-toolchain/test-rbe-ubuntu16_04",
+    tag = "latest",
+)
diff --git a/configs/ubuntu16_04_clang/versions.bzl b/configs/ubuntu16_04_clang/versions.bzl
index 0dbf8b8..2307032 100644
--- a/configs/ubuntu16_04_clang/versions.bzl
+++ b/configs/ubuntu16_04_clang/versions.bzl
@@ -12,9 +12,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License
 
-# Returns a dict with major container versions mapped to supported bazel versions.
-def config_to_bazel_versions():
+# Returns a dict with suppported Bazel versions mapped to the config version to use.
+def bazel_to_config_versions():
     return {
-        "1.0": ["0.13.0", "0.14.1", "0.15.0", "0.16.1"],
-        "1.1": ["0.16.1", "0.17.1", "0.18.0", "0.19.0", "0.19.2", "0.20.0", "0.21.0"],
+        "0.13.0": "1.0",
+        "0.14.1": "1.0",
+        "0.15.0": "1.0",
+        "0.16.1": "1.1",
+        "0.17.1": "1.1",
+        "0.18.0": "1.1",
+        "0.19.0": "1.1",
+        "0.19.2": "1.1",
+        "0.20.0": "1.1",
+        "0.21.0": "1.1",
+        "0.22.0": "1.1",
     }
diff --git a/rules/get_java_home.sh.tpl b/rules/get_java_home.sh.tpl
index 7cd2593..5f4857c 100644
--- a/rules/get_java_home.sh.tpl
+++ b/rules/get_java_home.sh.tpl
@@ -20,4 +20,3 @@
 # var in a docker image.
 
 echo $(docker inspect -f '{{range $i, $v := .Config.Env}}{{println $v}}{{end}}' %{image_name} | grep JAVA_HOME | cut -d'=' -f2)
-
diff --git a/rules/rbe_repo.bzl b/rules/rbe_repo.bzl
index 2ea33f2..71caa74 100644
--- a/rules/rbe_repo.bzl
+++ b/rules/rbe_repo.bzl
@@ -78,7 +78,7 @@
     name = "rbe_my_custom_container",
     registry = "gcr.io",
     repository = "my-project/my-base",
-    # tag is not supported, always use a digest
+    # Digest is recommended for any use case other than testing.
     digest = "sha256:deadbeef",
   )
 
@@ -144,8 +144,8 @@
 The {bazel_version} above corresponds to the version of bazel installed locally.
 Note you can override this version and pass an optional rc # if desired.
 Running this rule with a non release version (e.g., built from source) will result in
-picking as bazel version <bazel_version_fallback> defined in the attrs of this rule.
-Note the bazel_version / bazel_rc must be published in https://releases.bazel.build/...
+picking as bazel version _BAZEL_VERSION_FALLBACK. Note the bazel_version / bazel_rc_version
+must be published in https://releases.bazel.build/...
 
 Note this is a very not hermetic repository rule that can actually change the
 contents of your project sources. While this is generally not recommended by
@@ -168,7 +168,7 @@
 
 load(
     "//configs/ubuntu16_04_clang:versions.bzl",
-    "config_to_bazel_versions",
+    "bazel_to_config_versions",
 )
 load("//rules:environments.bzl", "clang_env")
 load(
@@ -183,6 +183,9 @@
     "parse_rc",
 )
 
+# Version to fallback to if not provided explicitly and local is non-release version.
+_BAZEL_VERSION_FALLBACK = "0.22.0"
+
 # External folder is set to be deprecated, lets keep it here for easy
 # refactoring
 # https://github.com/bazelbuild/bazel/issues/1262
@@ -214,23 +217,11 @@
 def _impl(ctx):
     """Core implementation of _rbe_autoconfig repository rule."""
 
-    # Resolve the Bazel version to use
-    bazel_version = None
-    bazel_rc_version = None
-    if ctx.attr.bazel_version == "local":
-        bazel_version = str(extract_version_number(ctx.attr.bazel_version_fallback))
-        rc = parse_rc(native.bazel_version)
-        bazel_rc_version = rc if rc != -1 else None
-    if ctx.attr.bazel_version != "local":
-        bazel_version = ctx.attr.bazel_version
-        bazel_rc_version = ctx.attr.bazel_rc_version
-
     # Deal with the simple case first: if user picks rbe-ubuntu 16_04 container and
     # a config exists for the current version of Bazel, just create aliases
-    if ctx.attr.use_checked_in_confs:
-        if _use_standard_config(ctx, bazel_version, bazel_rc_version, ctx.attr.revision):
-            # If a standard config was found we are done and can just return.
-            return
+    if ctx.attr.config_version:
+        _use_standard_config(ctx, ctx.attr.bazel_version, ctx.attr.config_version)
+        return
 
     # Perform some safety checks
     _validate_host(ctx)
@@ -256,9 +247,22 @@
     outputs_tar = ctx.attr.name + "_out.tar"
 
     # Pull the image using 'docker pull'
-    image_name = ctx.attr.registry + "/" + ctx.attr.repository + "@" + ctx.attr.digest
+    image_name = None
+    if ctx.attr.digest:
+        image_name = ctx.attr.registry + "/" + ctx.attr.repository + "@" + ctx.attr.digest
+    else:
+        image_name = ctx.attr.registry + "/" + ctx.attr.repository + ":" + ctx.attr.tag
     _pull_image(ctx, image_name)
 
+    # If tag is specified instead of digest, resolve it to digest in the
+    # image_name as it will be used later on in the platform targets.
+    if ctx.attr.tag:
+        result = ctx.execute(["docker", "inspect", "--format={{index .RepoDigests 0}}", image_name])
+        _print_exec_results("Resolve image digest", result, fail_on_error = True)
+        image_name = result.stdout.splitlines()[0]
+        print("Image with given tag `%s` is resolved to %s" %
+              (ctx.attr.tag, image_name))
+
     # Get the value of JAVA_HOME to set in the produced
     # java_runtime
     java_home = _get_java_home(ctx, image_name)
@@ -266,8 +270,8 @@
     # run the container and extract the autoconf directory
     _run_and_extract(
         ctx,
-        bazel_version = bazel_version,
-        bazel_rc_version = bazel_rc_version,
+        bazel_version = ctx.attr.bazel_version,
+        bazel_rc_version = ctx.attr.bazel_rc_version,
         image_name = image_name,
         outputs_tar = outputs_tar,
         project_root = project_root,
@@ -278,7 +282,7 @@
     # will work with RBE with the produced toolchain
     _create_platform(
         ctx,
-        bazel_version = bazel_version,
+        bazel_version = ctx.attr.bazel_version,
         image_name = image_name,
         java_home = java_home,
         name = name,
@@ -287,7 +291,7 @@
     # Expand outputs to project dir if user requested it
     _expand_outputs(
         ctx,
-        bazel_version = bazel_version,
+        bazel_version = ctx.attr.bazel_version,
         project_root = project_root,
     )
 
@@ -315,26 +319,11 @@
     if not ctx.which("tar"):
         fail("Cannot run rbe_autoconfig as 'tar' was not found on the path.")
 
-# Checks if a standard published config can be used for the given revision
-# of the rbe ubuntu 16_04 container. If so, produces BUILD files with aliases
-# for all the required toolchain / platform targets. Otherwise returns false.
-def _use_standard_config(ctx, bazel_version, bazel_rc_version, revision):
-    # If rc version is required, simply return
-    if bazel_rc_version:
-        return False
+# Produces BUILD files with aliases for all the required toolchain / platform targets.
+def _use_standard_config(ctx, bazel_version, config_version):
+    print("Using checked-in configs.")
 
-    # Verify a toolchain config exists for the given version of Bazel and the
-    # given revision of the container
-    config_version = public_rbe_ubuntu16_04_config_version().get(revision, None)
-    config_found = False
-    for b_v in config_to_bazel_versions().get(config_version):
-        if b_v == bazel_version:
-            config_found = True
-            break
-    if not config_found:
-        return False
-
-    # If a config is found, create the BUILD files with the aliases
+    # Create the BUILD files with the aliases
     template = ctx.path(Label("@bazel_toolchains//rules:BUILD.std_container.tpl"))
     jdk = "@bazel_toolchains//configs/ubuntu16_04_clang/%s:jdk8" % config_version
     cc_toolchain = "@bazel_toolchains//configs/ubuntu16_04_clang/%s/bazel_%s/cpp:cc-toolchain-clang-x86_64-default" % (config_version, bazel_version)
@@ -360,11 +349,10 @@
         },
         False,
     )
-    return True
 
 # Pulls an image using 'docker pull'.
 def _pull_image(ctx, image_name):
-    print("Pulling image.")
+    print("Pulling image %s." % image_name)
     result = ctx.execute(["docker", "pull", image_name])
     _print_exec_results("pull image", result, fail_on_error = True)
     print("Image pulled.")
@@ -504,7 +492,10 @@
         # If we use the default project, we need to modify the WORKSPACE
         # and BUILD files, so don't mount read-only
         mount_read_only_flag = ""
-    target = project_root + ":" + _REPO_DIR + mount_read_only_flag
+
+    # If the rule is invoked from bazel-toolchains itself, then project_root
+    # is a symlink, which can cause mounting issues on GCB.
+    target = "$(realpath " + project_root + "):" + _REPO_DIR + mount_read_only_flag
     docker_run_flags += ["-v", target]
     docker_run_flags += ["-v", str(ctx.path("container")) + ":/container"]
 
@@ -603,29 +594,29 @@
 _rbe_autoconfig = repository_rule(
     attrs = {
         "bazel_rc_version": attr.string(
-            doc = ("Optional. An rc version to use. Note an installer for the rc " +
-                   "must be available in https://releases.bazel.build."),
+            doc = ("Optional. An rc version to use. Note an installer for " +
+                   "the rc must be available in https://releases.bazel.build."),
         ),
         "bazel_version": attr.string(
             default = "local",
             doc = ("The version of Bazel to use to generate toolchain configs." +
                    "Use only (major, minor, patch), e.g., '0.20.0'."),
         ),
-        "bazel_version_fallback": attr.string(
-            default = "0.22.0",
-            doc = ("Version to fallback to if not provided explicitly and local " +
-                   "is non-release version."),
-        ),
         "config_dir": attr.string(
-            doc = ("Optional. Use only if output_base is defined. If you want to " +
-                   "create multiple toolchain configs (for the same version of Bazel) " +
-                   "you can use this attr to indicate a type of config (e.g., default, " +
-                   "msan). The configs will be generated in a sub-directory when this attr  " +
-                   "is used."),
+            doc = ("Optional. Use only if output_base is defined. If you " +
+                   "want to create multiple toolchain configs (for the same " +
+                   "version of Bazel) you can use this attr to indicate a " +
+                   "type of config (e.g., default,  msan). The configs will " +
+                   "be generated in a sub-directory when this attr is used."),
+        ),
+        "config_version": attr.string(
+            doc = ("The config version found for the given container and " +
+                   "Bazel version. " +
+                   "Used internally when use_checked_in_confs is true."),
         ),
         "digest": attr.string(
-            mandatory = True,
-            doc = ("The digest (sha256 sum) of the image to pull. For example, " +
+            doc = ("Optional. The digest (sha256 sum) of the image to pull. " +
+                   "For example, " +
                    "sha256:f1330b2f02714d3a3e98c5b1f6524fbb9c15154e44a31fb3caecb7a6ad4e8445" +
                    ", note the digest includes 'sha256:'"),
         ),
@@ -636,7 +627,7 @@
         ),
         "exec_compatible_with": attr.string_list(
             default = _RBE_UBUNTU_EXEC_COMPAT_WITH,
-            doc = ("The list of constraints that will be added to the " +
+            doc = ("Optional. The list of constraints that will be added to the " +
                    "toolchain in its exec_compatible_with attribute (and to " +
                    "the platform in its constraint_values attr). For " +
                    "example, [\"@bazel_tools//platforms:linux\"]. Default " +
@@ -655,25 +646,25 @@
         ),
         "registry": attr.string(
             default = _RBE_UBUNTU_REGISTRY,
-            doc = ("The registry to pull the container from. For example, " +
+            doc = ("Optional. The registry to pull the container from. For example, " +
                    "l.gcr.io or marketplace.gcr.io. The default is the " +
                    "value for rbe-ubuntu16-04 image."),
         ),
         "repository": attr.string(
             default = _RBE_UBUNTU_REPO,
-            doc = ("The repository to pull the container from. For example," +
+            doc = ("Optional. The repository to pull the container from. For example," +
                    " google/ubuntu. The default is the " +
                    " value for the rbe-ubuntu16-04 image."),
         ),
-        "revision": attr.string(
-            doc = ("The revision of the rbe-ubuntu16-04 container."),
-        ),
         "setup_cmd": attr.string(
             default = "cd .",
             doc = ("Optional. Pass an additional command that will be executed " +
                    "(inside the container) before running bazel to generate the " +
                    "toolchain configs"),
         ),
+        "tag": attr.string(
+            doc = ("Optional. The tag of the image to pull, e.g. latest."),
+        ),
         "target_compatible_with": attr.string_list(
             default = _RBE_UBUNTU_TARGET_COMPAT_WITH,
             doc = ("The list of constraints that will be added to the " +
@@ -681,11 +672,6 @@
                    "example, [\"@bazel_tools//platforms:linux\"]. Default " +
                    " is set to values for rbe-ubuntu16-04 container."),
         ),
-        "use_checked_in_confs": attr.bool(
-            default = True,
-            doc = ("If set to False the checked-in configs in bazel-toolchains" +
-                   "repo are ignored and a container is allways pulled."),
-        ),
     },
     environ = [
         _RBE_AUTOCONF_ROOT,
@@ -696,14 +682,14 @@
 def rbe_autoconfig(
         name,
         bazel_version = None,
-        bazel_rc = None,
+        bazel_rc_version = None,
         config_dir = None,
         digest = None,
         env = None,
         exec_compatible_with = None,
         java_home = None,
         output_base = None,
-        revision = None,
+        tag = None,
         registry = None,
         repository = None,
         target_compatible_with = None,
@@ -718,33 +704,39 @@
           `Use only (major, minor, patch), e.g., '0.20.0'. Default is "local"
           which means the same version of Bazel that is currently running will
           be used. If local is a non release version, rbe_autoconfig will fallback
-          to using the latest release version (see default for bazel_version_fallback
-          in attrs of _rbe_autoconfig for current latest).
-      bazel_rc: The rc (for the given version of Bazel) to use. Must be published
-          in https://releases.bazel.build
+          to using the latest release version (see _BAZEL_VERSION_FALLBACK).
+      bazel_rc_version: The rc (for the given version of Bazel) to use.
+          Must be published in https://releases.bazel.build. E.g. 2.
+      config_dir: Optional. Subdirectory where configs will be copied to.
+          Use only if output_base is defined.
+      digest: Optional. The digest of the image to pull.
+          Should not be set if tag is used.
+          Must be set together with registry and repository.
       exec_compatible_with: Optional. List of constraints to add to the produced
           toolchain/platform targets (e.g., ["@bazel_tools//platforms:linux"] in the
           exec_compatible_with/constraint_values attrs, respectively.
-      digest: Optional. The digest of the image to pull. Should only be set if
-          a custom container is required.
-          Must be set together with registry and repository.
+      env: dict. Optional. Additional env variables that will be set when
+          running the Bazel command to generate the toolchain configs.
+          Set to values for gcr.io/cloud-marketplace/google/rbe-ubuntu16-04 container.
+          Does not need to be set if your custom container extends
+          the rbe-ubuntu16-04 container.
+          Should be overriden if a custom container does not extend the
+          rbe-ubuntu16-04 container.
       java_home: Optional. The location of java_home in the container. For
           example , '/usr/lib/jvm/java-8-openjdk-amd64'. If not set, the rule
           will attempt to read the JAVA_HOME env var from the container.
           If that is not set the rule will fail.
       output_base: Optional. The directory (under the project root) where the
           produced toolchain configs will be copied to.
-      config_dir: Optional. Subdirectory where configs will be copied to.
-          Use only if output_base is defined.
+      tag: Optional. The tag of the container to use.
+          Should not be set if digest is used.
+          Must be set together with registry and repository.
       registry: Optional. The registry from which to pull the base image.
           Should only be set if a custom container is required.
           Must be set together with digest and repository.
       repository: Optional. he `repository` of images to pull from.
           Should only be set if a custom container is required.
           Must be set together with registry and digest.
-      revision: Optional. A revision of an rbe-ubuntu16-04 container to use.
-          Should not be set if repository, registry and digest are used.
-          See gcr.io/cloud-marketplace/google/rbe-ubuntu16-04
       target_compatible_with: List of constraints to add to the produced
           toolchain target (e.g., ["@bazel_tools//platforms:linux"]) in the
           target_compatible_with attr.
@@ -752,49 +744,58 @@
           before generating them. If set to false the rule will allways attempt
           to generate the configs by pulling a toolchain container and running
           Bazel inside.
-      env: dict. Optional. Additional env variables that will be set when
-          running the Bazel command to generate the toolchain configs.
-          Set to values for rbe-ubuntu16-04 container.
-          Does not need to be set if your custom container extends
-          an rbe-ubuntu16-04 container.
-          Should be overriden if a custom container does not extend
-          rbe-ubuntu16-04.
     """
     if not output_base and config_dir:
         fail("config_dir can only be used when output_base is set.")
-    if revision and (digest or repository or registry):
-        fail("'revision' cannot be set if 'digest', 'repository' or " +
-             "'registry' are set.")
-    if not ((not digest and not repository and not registry) or
-            (digest and repository and registry)):
-        fail("All of 'digest', 'repository' and 'registry' or none of them " +
-             "must be set.")
-    if bazel_rc and not bazel_version:
-        fail("bazel_rc can only be used with bazel_version.")
-    if not digest:
-        if not revision or revision == "latest":
-            revision = RBE_UBUNTU16_04_LATEST
-        digest = public_rbe_ubuntu16_04_sha256s().get(revision, None)
-        if not env:
-            env = clang_env()
-    if not digest:
-        fail(("Could not find a valid digest for revision %s, " +
-              "please make sure it is declared in " +
-              "@bazel_toolchains//rules:toolchain_containers.bzl" % revision))
 
-    # If the user has set a custom env, custom java_home,
-    # registry or repository, don't use
-    # checked in configs
-    if ((env and env != clang_env()) or
-        (java_home) or
-        (registry and registry != _RBE_UBUNTU_REGISTRY) or
-        (repository and repository != _RBE_UBUNTU_REPO)):
-        use_checked_in_confs = False
+    if bazel_rc_version and not bazel_version:
+        fail("bazel_rc_version can only be used with bazel_version.")
+
+    # Resolve the Bazel version to use.
+    if not bazel_version or bazel_version == "local":
+        bazel_version = str(extract_version_number(_BAZEL_VERSION_FALLBACK))
+        rc = parse_rc(native.bazel_version)
+        bazel_rc_version = rc if rc != -1 else None
+
+    if tag and digest:
+        fail("'tag' and 'digest' cannot be set at the same time.")
+
+    if not ((not digest and not tag and not repository and not registry) or
+            (digest and repository and registry) or
+            (tag and repository and registry)):
+        fail("All of 'digest', 'repository' and 'registry' or " +
+             "all of 'tag', 'repository' and 'registry' or " +
+             "none of them must be set.")
+
+    # Set to defaults only if all are unset.
+    if not repository and not registry and not tag and not digest:
+        repository = _RBE_UBUNTU_REPO
+        registry = _RBE_UBUNTU_REGISTRY
+        tag = RBE_UBUNTU16_04_LATEST
+
+    if ((registry and registry == _RBE_UBUNTU_REGISTRY) and
+        (repository and repository == _RBE_UBUNTU_REPO) and
+        (not env)):
+        env = clang_env()
+
+    config_version = validateUseOfCheckedInConfigs(
+        registry = registry,
+        repository = repository,
+        tag = tag,
+        digest = digest,
+        use_checked_in_confs = use_checked_in_confs,
+        env = env,
+        java_home = java_home,
+        bazel_version = bazel_version,
+        bazel_rc_version = bazel_rc_version,
+    )
+
     _rbe_autoconfig(
         name = name,
         bazel_version = bazel_version,
-        bazel_rc = bazel_rc,
+        bazel_rc_version = bazel_rc_version,
         config_dir = config_dir,
+        config_version = config_version,
         digest = digest,
         env = env,
         exec_compatible_with = exec_compatible_with,
@@ -802,7 +803,55 @@
         output_base = output_base,
         registry = registry,
         repository = repository,
-        revision = revision,
+        tag = tag,
         target_compatible_with = target_compatible_with,
-        use_checked_in_confs = use_checked_in_confs,
     )
+
+# Check if checked-in configs are available and should be used. If so, return
+# the config version. Otherwise return None.
+def validateUseOfCheckedInConfigs(
+        registry,
+        repository,
+        tag,
+        digest,
+        use_checked_in_confs,
+        env,
+        java_home,
+        bazel_version,
+        bazel_rc_version):
+    if not use_checked_in_confs:
+        return None
+    if registry and registry != _RBE_UBUNTU_REGISTRY:
+        return None
+    if repository and repository != _RBE_UBUNTU_REPO:
+        return None
+    if env and env != clang_env():
+        return None
+    if java_home:
+        return None
+    if bazel_rc_version:
+        return None
+
+    if tag:  # Implies `digest` is not specified.
+        if tag == "latest":
+            tag = RBE_UBUNTU16_04_LATEST
+        digest = public_rbe_ubuntu16_04_sha256s().get(tag, None)
+        if not digest:  # We didn't find checked-in configs in this repo.
+            return None
+    else:  # Implies tag is not specified
+        for pair in public_rbe_ubuntu16_04_sha256s().items():
+            if pair[1] == digest:
+                tag = pair[0]
+                break
+        if not tag:
+            # The given RBE Ubuntu1604 container digest is not one of the
+            # released ones.
+            return None
+
+    # Verify a toolchain config exists for the given version of Bazel and the
+    # given tag of the container
+    config_version = public_rbe_ubuntu16_04_config_version().get(tag, None)
+    if not config_version or config_version != bazel_to_config_versions().get(bazel_version):
+        return None
+
+    return config_version