Revert "Remove support for Sauce labs"

This reverts commit abae192cfdebe179aaf3be1923dad745fc0fa78c.
diff --git a/docs/owner.md b/docs/owner.md
index 64d62cd..8958ea8 100644
--- a/docs/owner.md
+++ b/docs/owner.md
@@ -61,6 +61,12 @@
     concurrently; otherwise runs the job's configurations one after the
     other
 
+    Useful if the job uses some exclusive resource such as [Sauce
+    Labs](https://wiki.saucelabs.com/).
+
+*   `sauce_enabled`: activates or deactivates [Sauce
+    Labs](https://wiki.saucelabs.com/) support
+
 ### `bazel_git_job` parameters
 
 The `bazel_git_job` rule takes the same parameters as `bazel_github_job`, but
diff --git a/jenkins/base/plugins.bzl b/jenkins/base/plugins.bzl
index c269e39..2993768 100644
--- a/jenkins/base/plugins.bzl
+++ b/jenkins/base/plugins.bzl
@@ -182,6 +182,8 @@
                   '666cbc579d82849bef8d0396a57335c9994222e8b0ba52f07be76dab9fb56c40'],
  'run-condition': ['1.0',
                    '4e55ebf4bde1202784404d856f98f7de85470ed145cde06feb45f641891780fb'],
+ 'sauce-ondemand': ['1.171',
+                    '5a7a1e1eccb199d82227e41e9bce655d33b2e33219f40a9cd0c884780cc26ae8'],
  'scm-api': ['2.2.6',
              'be7cb7c513ebeb361a8ccb5c636541313c1c54b3119566b969fe10a3552cca14'],
  'script-security': ['1.39',
diff --git a/jenkins/build_defs/bazel-job-Gerrit.xml.tpl b/jenkins/build_defs/bazel-job-Gerrit.xml.tpl
index 538b531..c008779 100644
--- a/jenkins/build_defs/bazel-job-Gerrit.xml.tpl
+++ b/jenkins/build_defs/bazel-job-Gerrit.xml.tpl
@@ -60,6 +60,9 @@
         bazel_version: "latest",
         configuration: '''{{ raw_imports['JSON_CONFIGURATION'].replace('\\', '\\\\').replace("'", "\\'") }}''',
         workspace: "{{ variables.WORKSPACE }}",
+        {% if variables.SAUCE_ENABLED == "true" %}
+        sauce: "61b4846b-279d-4369-ae20-31e9d8b9bc66",
+        {% endif %}
         run_sequentially: {{ variables.RUN_SEQUENTIAL }},
         restrict_configuration: {{ variables.RESTRICT_CONFIGURATION }}
     )
diff --git a/jenkins/build_defs/bazel-job-Global.xml.tpl b/jenkins/build_defs/bazel-job-Global.xml.tpl
index a01efd0..6826b74 100644
--- a/jenkins/build_defs/bazel-job-Global.xml.tpl
+++ b/jenkins/build_defs/bazel-job-Global.xml.tpl
@@ -60,6 +60,9 @@
     extra_bazelrc: params.EXTRA_BAZELRC,
     configuration: '''{{ raw_imports['JSON_CONFIGURATION'].replace('\\', '\\\\').replace("'", "\\'") }}''',
     workspace: "{{ variables.WORKSPACE }}",
+    {% if variables.SAUCE_ENABLED == "true" %}
+    sauce: "61b4846b-279d-4369-ae20-31e9d8b9bc66",
+    {% endif %}
     run_sequentially: {{ variables.RUN_SEQUENTIAL }},
     restrict_configuration: {{ variables.RESTRICT_CONFIGURATION }}
 )
diff --git a/jenkins/build_defs/bazel-job-PR.xml.tpl b/jenkins/build_defs/bazel-job-PR.xml.tpl
index fd62b3a..bf7eea5 100644
--- a/jenkins/build_defs/bazel-job-PR.xml.tpl
+++ b/jenkins/build_defs/bazel-job-PR.xml.tpl
@@ -66,6 +66,9 @@
         bazel_version: "latest",
         configuration: '''{{ raw_imports['JSON_CONFIGURATION'].replace('\\', '\\\\').replace("'", "\\'") }}''',
         workspace: "{{ variables.WORKSPACE }}",
+        {% if variables.SAUCE_ENABLED == "true" %}
+        sauce: "61b4846b-279d-4369-ae20-31e9d8b9bc66",
+        {% endif %}
         run_sequentially: {{ variables.RUN_SEQUENTIAL }},
         restrict_configuration: {{ variables.RESTRICT_CONFIGURATION }}
     )
diff --git a/jenkins/build_defs/bazel-job.xml.tpl b/jenkins/build_defs/bazel-job.xml.tpl
index 389bb2a..8d7bf8b 100644
--- a/jenkins/build_defs/bazel-job.xml.tpl
+++ b/jenkins/build_defs/bazel-job.xml.tpl
@@ -47,6 +47,9 @@
     bazel_version: "latest",
     configuration: '''{{ raw_imports['JSON_CONFIGURATION'].replace('\\', '\\\\').replace("'", "\\'") }}''',
     workspace: "{{ variables.WORKSPACE }}",
+    {% if variables.SAUCE_ENABLED == "true" %}
+    sauce: "61b4846b-279d-4369-ae20-31e9d8b9bc66",
+    {% endif %}
     run_sequentially: {{ variables.RUN_SEQUENTIAL }},
     restrict_configuration: {{ variables.RESTRICT_CONFIGURATION }}
 )
diff --git a/jenkins/build_defs/jenkins_job.bzl b/jenkins/build_defs/jenkins_job.bzl
index 2d09db9..dc09f0a 100644
--- a/jenkins/build_defs/jenkins_job.bzl
+++ b/jenkins/build_defs/jenkins_job.bzl
@@ -119,6 +119,7 @@
                      pr_enabled=True,
                      github_enabled=True,
                      run_sequential=False,
+                     sauce_enabled=False,
                      use_upstream_branch=False):
   """Create a generic github job configuration to build against Bazel head."""
   if poll == None:
@@ -137,6 +138,7 @@
     "github": str(github_enabled),
     "GERRIT_PROJECT": str(gerrit_project) if gerrit_project else "",
     "RUN_SEQUENTIAL": str(run_sequential).lower(),
+    "SAUCE_ENABLED": str(sauce_enabled).lower(),
     "GLOBAL_USE_UPSTREAM_BRANCH": str(use_upstream_branch)
   }
 
diff --git a/jenkins/config/credentials.xml b/jenkins/config/credentials.xml
index 8065814..a5f664e 100644
--- a/jenkins/config/credentials.xml
+++ b/jenkins/config/credentials.xml
@@ -13,6 +13,13 @@
           <username>bazel-io</username>
           <password>##SECRET:github.bazel-io.jenkins.password##</password>
         </com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
+        <hudson.plugins.sauce_ondemand.credentials.SauceCredentials>
+          <scope>GLOBAL</scope>
+          <id>61b4846b-279d-4369-ae20-31e9d8b9bc66</id>
+          <description>bazel_rules_webtesting @ Sauce Labs</description>
+          <username>bazel_rules_webtesting</username>
+          <apiKey>##SECRET:saucelabs.bazel_rules_webtesting.access_key##</apiKey>
+        </hudson.plugins.sauce_ondemand.credentials.SauceCredentials>
         <org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl plugin="plain-credentials@1.4">
           <scope>GLOBAL</scope>
           <id>remote-execution</id>
diff --git a/jenkins/jobs/BUILD b/jenkins/jobs/BUILD
index 455d7ca..2e206ae 100644
--- a/jenkins/jobs/BUILD
+++ b/jenkins/jobs/BUILD
@@ -132,6 +132,7 @@
     config = ":configs/rules_webtesting.json",
     project = "rules_webtesting",
     run_sequential = True,
+    sauce_enabled = True,
 )
 
 bazel_github_job(
diff --git a/jenkins/lib/vars/bazelCiConfiguredJob.groovy b/jenkins/lib/vars/bazelCiConfiguredJob.groovy
index edccd5a..f4b1fe3 100644
--- a/jenkins/lib/vars/bazelCiConfiguredJob.groovy
+++ b/jenkins/lib/vars/bazelCiConfiguredJob.groovy
@@ -44,7 +44,8 @@
                           extra_bazelrc: config.extra_bazelrc,
                           build_tag_filters: params.get("build_tag_filters", []),
                           test_tag_filters: params.get("test_tag_filters", []),
-                          workspace: config.workspace
+                          workspace: config.workspace,
+                          sauce: config.sauce
       )
     })
   }
@@ -69,6 +70,7 @@
  *   - repository: git repository to clone.
  *   - branch: branch of the repository to clone (default: master).
  *   - refspec: specification of the references to fetch
+ *   - sauce: identifier of the crendentials to connect to SauceLabs.
  *   - run_sequentially: run each configuration sequentially rather than in parallel
  */
 def call(config = [:]) {
@@ -80,6 +82,7 @@
   config["repository"] = config.get("repository", "")
   config["branch"] = config.get("branch", "master")
   config["refspec"] = config.get("refspec", "+refs/heads/*:refs/remotes/origin/*")
+  config["sauce"] = config.get("sauce", "")
   config["run_sequentially"] = config.get("run_sequentially", false)
 
   // Remove special characters from bazel_version (which can be coming from a URL post):
diff --git a/jenkins/lib/vars/bazelCiJob.groovy b/jenkins/lib/vars/bazelCiJob.groovy
index 49b8633..a4553be 100644
--- a/jenkins/lib/vars/bazelCiJob.groovy
+++ b/jenkins/lib/vars/bazelCiJob.groovy
@@ -31,6 +31,7 @@
  *   - repository: git repository to clone.
  *   - branch: branch of the repository to clone (default: master).
  *   - refspec: specification of the references to fetch
+ *   - sauce: identifier of the credentials to connect to SauceLabs.
  *   - node_label: label of the node to run on
  */
 def call(config = [:]) {
@@ -46,6 +47,7 @@
   config["test_tag_filters"] = config.get("test_tag_filters", [])
   config["workspace"] = config.get("workspace", "")
   config["repository"] = config.get("repository", "")
+  config["sauce"] = config.get("sauce", "")
   config["branch"] = config.get("branch", "master")
   config["refspec"] = config.get("refspec", "+refs/heads/*:refs/remotes/origin/*")
 
@@ -63,34 +65,36 @@
   ] + config.test_opts
   machine(config.node_label) {
     ws("workspace/${currentBuild.fullProjectName}-${prefix}") {
-      // Checkout the code
-      echo "Checkout ${config.repository}"
-      recursiveGit(repository: config.repository,
-                   refspec: config.refspec,
-                   branch: config.branch)
+      maybeSauce(config.sauce) {
+        // Checkout the code
+        echo "Checkout ${config.repository}"
+        recursiveGit(repository: config.repository,
+                     refspec: config.refspec,
+                     branch: config.branch)
 
-      // And build
-      maybeDir(config.workspace) {
-        def bazel = bazelPath(config.bazel_version, config.node_label)
-        def extrarc = config.extra_bazelrc
-        if (config.bazel_version.startsWith("custom")) {
-          // Make the server dies after 10 minutes on custom bazel version.
-          // Other bazel servers stays alive for 3 hours, which is ok for
-          // release but not for custom binaries used for Global tests. We
-          // do not set the max_idle_secs to 1 because we still want the
-          // server to survive between steps.
-          extrarc += "\nstartup --max_idle_secs 600\n"
+        // And build
+        maybeDir(config.workspace) {
+          def bazel = bazelPath(config.bazel_version, config.node_label)
+          def extrarc = config.extra_bazelrc
+          if (config.bazel_version.startsWith("custom")) {
+            // Make the server dies after 10 minutes on custom bazel version.
+            // Other bazel servers stays alive for 3 hours, which is ok for
+            // release but not for custom binaries used for Global tests. We
+            // do not set the max_idle_secs to 1 because we still want the
+            // server to survive between steps.
+            extrarc += "\nstartup --max_idle_secs 600\n"
+          }
+          bazelJob(binary: bazel,
+                   build_opts: build_options,
+                   test_opts: test_options,
+                   startup_opts: config.startup_opts,
+                   extra_bazelrc: extrarc,
+                   targets: config.targets,
+                   tests: config.tests,
+                   configuration: config.configuration,
+                   stage_name: prefix
+          )
         }
-        bazelJob(binary: bazel,
-                 build_opts: build_options,
-                 test_opts: test_options,
-                 startup_opts: config.startup_opts,
-                 extra_bazelrc: extrarc,
-                 targets: config.targets,
-                 tests: config.tests,
-                 configuration: config.configuration,
-                 stage_name: prefix
-        )
       }
     }
   }
diff --git a/jenkins/lib/vars/maybeSauce.groovy b/jenkins/lib/vars/maybeSauce.groovy
new file mode 100644
index 0000000..d40f503
--- /dev/null
+++ b/jenkins/lib/vars/maybeSauce.groovy
@@ -0,0 +1,30 @@
+// Copyright (C) 2017 The Bazel Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * Define a step "maybeSauce" that executes its body under the SauceLabs wrapper with the
+ * credentials identifier given in argument, except if that argument is empty (in which
+ * case the body is executed directly).
+ */
+def call(String id, Closure body) {
+  if (id.isEmpty()) {
+    body()
+  } else {
+    sauce(id) {
+      sauceconnect(useGeneratedTunnelIdentifier: true) {
+        body()
+      }
+    }
+  }
+}