[secrets] Create go library for starting up a 'secrets' server.

Bug: IN-558
Tested: unit tests added
Change-Id: Iea5b079f3914a510aa5f9f5104e3029c90c9014e
diff --git a/secrets/server.go b/secrets/server.go
new file mode 100644
index 0000000..696f9e3
--- /dev/null
+++ b/secrets/server.go
@@ -0,0 +1,78 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package secrets
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"go.chromium.org/luci/lucictx"
+	"log"
+	"net/http"
+	"strconv"
+	"strings"
+)
+
+// A mapping of test name to associated secret.
+type testSecrets map[string]string
+
+// Serves the secret associated to a test, where the request's URL is of the
+// form "/<test name>".
+func (secrets testSecrets) serveSecret(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/plain")
+	testName := strings.TrimPrefix(r.URL.Path, "/")
+	secret, ok := secrets[testName]
+	if !ok {
+		log.Printf("There is no secret to serve for \"%s\"\n", testName)
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+	log.Printf("Serving secret for \"%s\"\n", testName)
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte(secret))
+}
+
+// Parses out tests secrets serialized in the LUCI_CONTEXT JSON under the
+// "secret_bytes" key.
+func getSecrets(ctx context.Context) (*testSecrets, error) {
+	swarming := lucictx.GetSwarming(ctx)
+	if swarming == nil {
+		return nil, fmt.Errorf("GetSwarming() returned a null value.")
+	}
+	secrets := new(testSecrets)
+	err := json.Unmarshal(swarming.SecretBytes, secrets)
+	return secrets, err
+}
+
+// Starts a server to serve test secrets at localhost:<|port|>.
+func StartSecretsServer(ctx context.Context, port int) {
+	secrets, err := getSecrets(ctx)
+	if err != nil {
+		log.Printf("Unable to find secrets: %v\n", err)
+		return
+	}
+
+	log.Printf("Setting up secrets server at localhost:%d\n", port)
+	s := &http.Server{
+		Addr:    ":" + strconv.Itoa(port),
+		Handler: http.HandlerFunc(secrets.serveSecret),
+	}
+
+	go func() {
+		if err := s.ListenAndServe(); err != nil {
+			log.Print(err)
+		}
+	}()
+
+	go func() {
+		select {
+		case <-ctx.Done():
+			log.Printf("Shutting down secrets server at localhost:%d\n", port)
+			if err := s.Shutdown(context.Background()); err != nil {
+				log.Print(err)
+			}
+		default:
+		}
+	}()
+}
diff --git a/secrets/server_test.go b/secrets/server_test.go
new file mode 100644
index 0000000..786b0a0
--- /dev/null
+++ b/secrets/server_test.go
@@ -0,0 +1,74 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package secrets
+
+import (
+	"context"
+	"go.chromium.org/luci/lucictx"
+	"net/http"
+	"net/http/httptest"
+	"reflect"
+	"testing"
+)
+
+func TestGettingSecrets(t *testing.T) {
+	secretBytes := []byte(`{"testNameA":"SECRETA","testNameB":"SECRETB"}`)
+	swarming := lucictx.Swarming{
+		SecretBytes: secretBytes,
+	}
+	ctx := lucictx.SetSwarming(context.Background(), &swarming)
+
+	expectedSecrets := testSecrets{
+		"testNameA": "SECRETA",
+		"testNameB": "SECRETB",
+	}
+	actualSecrets, err := getSecrets(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(actualSecrets, &expectedSecrets) {
+		t.Errorf("Returned secrets \"%v\" do not match the expected: \"%v\"\n",
+			*actualSecrets, expectedSecrets)
+	}
+}
+
+func TestServingSecrets(t *testing.T) {
+	// Returns an GET request for the secret associated to |testName|.
+	secretRequest := func(testName string) *http.Request {
+		request, err := http.NewRequest(http.MethodGet, "/"+testName, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return request
+	}
+
+	secrets := testSecrets{
+		"foo_unittests": "FOO-SECRET",
+		"bar_e2e_tests": "BAR-SECRET",
+	}
+	handler := http.HandlerFunc(secrets.serveSecret)
+
+	// Checks that the expected content and code were returned in a mock HTTP response.
+	expectValidResponse := func(t *testing.T, testName string, expectedCode int,
+		expectedContent string) {
+		recorder := httptest.NewRecorder()
+		handler.ServeHTTP(recorder, secretRequest(testName))
+		if actualCode := recorder.Code; actualCode != expectedCode {
+			t.Errorf("serveSecret() response code: %v\n; %v was expected for test \"%v\"\n",
+				actualCode, expectedCode, testName)
+		}
+		if actualContent := recorder.Body.String(); actualContent != expectedContent {
+			t.Errorf("serveSecret() failed to returned \"%v\" instead of \"%v\" for test \"%v\"\n",
+				actualContent, expectedContent, testName)
+		}
+	}
+
+	t.Run("Succeeds when associated secret exists", func(t *testing.T) {
+		expectValidResponse(t, "foo_unittests", http.StatusOK, "FOO-SECRET")
+	})
+
+	t.Run("Fails when associated secret does not exist", func(t *testing.T) {
+		expectValidResponse(t, "non_existant_unittests", http.StatusNotFound, "")
+	})
+}