services/device/device: have publish create new envelope versions

Instead of always using version "0", publish now uploads envelopes at a
new version each time. The version is based on the timestamp.

The envelope from the latest existing version is transfered over to the
new version (keeping with the flag/package preservation behavior of the
old publish command).

Prod and staging need to be updated to fetch updated envelopes from
unversioned object names before checking this in.

MultiPart: 1/2

Change-Id: Id1e059f9bb11c58ea61a9878698b182d29da31b2
diff --git a/services/device/device/publish.go b/services/device/device/publish.go
index 5862d86..f7d84a0 100644
--- a/services/device/device/publish.go
+++ b/services/device/device/publish.go
@@ -140,8 +140,7 @@
 	// TODO(caprita): use the profile detection machinery and/or let user
 	// specify the profile by hand.
 	profile := fmt.Sprintf("%s-%s", goosFlag, goarchFlag)
-	// TODO(caprita): use a label e.g. "prod" instead of "0".
-	appVON := naming.Join(applicationService, envelopeName, "0")
+	appVON := naming.Join(applicationService, envelopeName)
 	appClient := repository.ApplicationClient(appVON)
 	envelope, err := appClient.Match(ctx, []string{profile})
 	if verror.ErrorID(err) == verror.ErrNoExist.ID {
@@ -173,8 +172,16 @@
 		envelope.Binary.Signature = security.Signature{}
 		envelope.Publisher = security.Blessings{}
 	}
-
-	if err := appClient.Put(ctx, profile, envelope, true); err != nil {
+	appVON = naming.Join(appVON, timestamp)
+	appClient = repository.ApplicationClient(appVON)
+	if err := appClient.Put(ctx, profile, envelope, false); err != nil {
+		// NOTE(caprita): We don't retry if an envelope already exists
+		// at the versioned name, as we do when uploading binaries.  In
+		// the case of binaries, it's likely that the same binary is
+		// uploaded more than once in a given second, due to apps
+		// sharing the same binary.  The scenarios where the same app is
+		// published repeatedly in a short time-frame are expected to be
+		// rare, and the operator can retry manually in such cases.
 		return err
 	}
 	fmt.Fprintf(env.Stdout, "Published %q\n", appVON)
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index 5940bcc..78a7795 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -350,14 +350,14 @@
 	// Allow publishers to create and update envelopes
 	deviceBin.Run("acl", "set", appDName, "root/a", "Read,Write,Resolve")
 
-	sampleAppName := appDName + "/testapp/0"
+	sampleAppName := appDName + "/testapp"
 	appPubName := "testbinaryd"
 	appEnvelopeFilename := filepath.Join(workDir, "app.envelope")
 	appEnvelope := fmt.Sprintf("{\"Title\":\"BINARYD\", \"Args\":[\"--name=%s\", \"--root-dir=./binstore\", \"--v23.tcp.address=127.0.0.1:0\", \"--http=127.0.0.1:0\"], \"Binary\":{\"File\":%q}, \"Env\":[]}", appPubName, sampleAppBinName)
 	ioutil.WriteFile(appEnvelopeFilename, []byte(appEnvelope), 0666)
 	defer os.Remove(appEnvelopeFilename)
 
-	output := applicationBin.Run("put", sampleAppName, deviceProfile, appEnvelopeFilename)
+	output := applicationBin.Run("put", sampleAppName+"/0", deviceProfile, appEnvelopeFilename)
 	if got, want := output, fmt.Sprintf("Application envelope added for profile %s.", deviceProfile); got != want {
 		i.Fatalf("got %q, want %q", got, want)
 	}