cloud/docker/upspinserver: add upspinserver docker image

This Docker image is to be used by Upspin users to provision and upgrade
their upspinserver instances.

It should be routinely published by Upspin team members using the
upspin-deploy-gcp command. (We may automate this later.)

Change-Id: I819491f3f9ce2943c43a7bb4c4eda95a987fe385
Reviewed-on: https://upspin-review.googlesource.com/11560
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/cloud/docker/upspinserver/Dockerfile b/cloud/docker/upspinserver/Dockerfile
new file mode 100644
index 0000000..0cb7c48
--- /dev/null
+++ b/cloud/docker/upspinserver/Dockerfile
@@ -0,0 +1,4 @@
+FROM buildpack-deps:jessie-curl
+COPY bin/upspinserver-gcp /upspinserver
+ENTRYPOINT ["/upspinserver", "-serverconfig=/upspin/server", "-letscache=/upspin/letscache", "-https=:8443"]
+EXPOSE 8443
diff --git a/cloud/docker/upspinserver/README b/cloud/docker/upspinserver/README
new file mode 100644
index 0000000..b50ac28
--- /dev/null
+++ b/cloud/docker/upspinserver/README
@@ -0,0 +1,13 @@
+This directory contains a Dockerfile for running upspinserver-gcp.
+The Upspin team routinely publishes this Docker image to
+gcr.io/upspin-containers/upspinserver.
+
+To start a Google Compute Instance running the container, run this command:
+
+$ gcloud compute instances create $INSTANCE_NAME \
+	--image-family=cos-stable --image-project=cos-cloud \
+	--tags=https-server \
+	--zone=$ZONE \
+	--machine-type=n1-standard-1 \
+	--metadata-from-file=user-data=cloud-init.yaml
+
diff --git a/cloud/docker/upspinserver/cloud-init.yaml b/cloud/docker/upspinserver/cloud-init.yaml
new file mode 100644
index 0000000..c06b614
--- /dev/null
+++ b/cloud/docker/upspinserver/cloud-init.yaml
@@ -0,0 +1,29 @@
+#cloud-config
+
+users:
+- name: upspin
+  uid: 2000
+
+runcmd:
+- iptables -w -A INPUT -p tcp --dport 443 -j ACCEPT
+
+write_files:
+- path: /etc/systemd/system/upspinserver.service
+  permissions: 0644
+  owner: root
+  content: |
+    [Unit]
+    Description=An upspinserver container instance
+    Wants=gcr-online.target
+    After=gcr-online.target
+    [Service]
+    Environment="HOME=/home/upspin"
+    ExecStartPre=/usr/bin/docker-credential-gcr configure-docker
+    ExecStart=/usr/bin/docker run --rm -u=2000 --volume=/home/upspin:/upspin -p=443:8443 --name=upspinserver gcr.io/upspin-containers/upspinserver:latest
+    ExecStop=/usr/bin/docker stop upspinserver
+    ExecStopPost=/usr/bin/docker rm upspinserver
+
+runcmd:
+- systemctl daemon-reload
+- systemctl start upspinserver.service
+
diff --git a/cmd/upspin-deploy-gcp/main.go b/cmd/upspin-deploy-gcp/main.go
index 7b2cf02..ecb6171 100644
--- a/cmd/upspin-deploy-gcp/main.go
+++ b/cmd/upspin-deploy-gcp/main.go
@@ -77,6 +77,7 @@
 	all = flag.Bool("all", false, "Create/deploy/delete all servers")
 
 	releaseImage = flag.Bool("release-image", false, "Build release Docker image and exit")
+	serverImage  = flag.Bool("upspinserver-image", false, "Build upspinserver Docker image and exit")
 )
 
 func main() {
@@ -92,12 +93,21 @@
 	}
 	flag.Parse()
 
+	log.SetFlags(0)
+	log.SetPrefix("upspin-deploy-gcp: ")
+
+	if *serverImage {
+		if err := buildServerImage(); err != nil {
+			log.Fatal(err)
+		}
+		return
+	}
+
 	mustProvideFlag(project, "-project")
 	if *releaseImage {
 		err := cdbuild(repoPath("cloud/docker/release"), *project, "release", "")
 		if err != nil {
-			fmt.Fprintln(os.Stderr, err)
-			os.Exit(1)
+			log.Fatal(err)
 		}
 		return
 	}
@@ -133,14 +143,12 @@
 	}
 
 	if err := cfg.checkCredentials(); err != nil {
-		fmt.Fprintf(os.Stderr, "%v\n", err)
-		os.Exit(1)
+		log.Fatal(err)
 	}
 
 	if *delete != "" {
 		if *delete != *project {
-			fmt.Fprintf(os.Stderr, "error: -delete must equal -project\n\n")
-			os.Exit(1)
+			log.Fatal("error: -delete must equal -project")
 		}
 		if err := wrap("delete", cfg.Delete()); err != nil {
 			log.Fatal(err)
@@ -495,7 +503,7 @@
 
 	// Collect source code for server and its dependencies.
 	pkgPath := "gcp.upspin.io/cmd/" + server + "-gcp"
-	if err := c.copySource(dir, pkgPath); err != nil {
+	if err := copySource(dir, pkgPath); err != nil {
 		return err
 	}
 
@@ -635,11 +643,11 @@
 
 // copySource copies the source code for the specified package and all
 // its non-goroot dependencies to the specified workspace directory.
-func (c *Config) copySource(dir, pkgPath string) error {
+func copySource(dir, pkgPath string) error {
 	// Find all package dependencies.
 	cmd := exec.Command("go", "list", "-f", `{{join .Deps "\n"}}`, pkgPath)
 	cmd.Stderr = os.Stderr
-	cmd.Env = c.buildEnv()
+	cmd.Env = buildEnv()
 	out, err := cmd.Output()
 	if err != nil {
 		return err
@@ -650,7 +658,7 @@
 	args := []string{"list", "-f", `{{if not .Goroot}}{{.ImportPath}} {{.Dir}}{{end}}`, pkgPath}
 	cmd = exec.Command("go", append(args, deps...)...)
 	cmd.Stderr = os.Stderr
-	cmd.Env = c.buildEnv()
+	cmd.Env = buildEnv()
 	out, err = cmd.Output()
 	if err != nil {
 		return err
@@ -690,7 +698,7 @@
 	return nil
 }
 
-func (c *Config) buildEnv() (env []string) {
+func buildEnv() (env []string) {
 	for _, s := range os.Environ() {
 		switch {
 		case strings.HasPrefix(s, "GOOS="),
@@ -1204,3 +1212,27 @@
 func repoPath(suffix string) string {
 	return filepath.Join(build.Default.GOPATH, "src/gcp.upspin.io", suffix)
 }
+
+func buildServerImage() error {
+	const (
+		project = "upspin-containers"
+		pkgPath = "gcp.upspin.io/cmd/upspinserver-gcp"
+	)
+	//if err := cdbuild(repoPath("cloud/docker/cloudbuild"), project, "cloudbuild", ""); err != nil {
+	//	return err
+	//}
+	dir, err := ioutil.TempDir("", "upspinserver-image")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(dir)
+	err = cp(filepath.Join(dir, "Dockerfile"), repoPath("cloud/docker/upspinserver/Dockerfile"))
+	if err != nil {
+		return err
+	}
+	err = copySource(dir, pkgPath)
+	if err != nil {
+		return err
+	}
+	return cdbuild(dir, project, "upspinserver", pkgPath)
+}