Repository: containrrr/watchtower (go)

Issue:
Use a unique header for Watchtower 
**Is your feature request related to a problem? Please describe.**
As someone working at Docker, it would be awesome to understand how many people are using watch tower to call Docker Hub, so I can share some numbers back with the community about the impact they are having 

**Describe the solution you'd like**
Update the user agent to be unique so we can differentiate the traffic 

**Describe alternatives you've considered**
Not many :) 

**Additional context**
We know there are a bunch of calls to 'containrrr' but how many more don't hit that in a given day or are going to other repos makes it hard to judge. :) Let me know if there are questions 


Pull request description:
Custom User Agent
Set a custom `User-Agent` header in the http client requests.
The value would be `Watchtower/1.3.0` (with the appropriate version of course).

Fixes #986 

Patch:
diff --git a/build.sh b/build.sh
index 47d2b5c..304786d 100644
--- a/build.sh
+++ b/build.sh
@@ -2,4 +2,4 @@
 
 VERSION=$(git describe --tags)
 echo "Building $VERSION..."
-go build -o watchtower -ldflags "-X github.com/containrrr/watchtower/cmd.version=$VERSION"
+go build -o watchtower -ldflags "-X github.com/containrrr/watchtower/internal/meta.Version=$VERSION"
diff --git a/cmd/root.go b/cmd/root.go
index e4c24b3..cf752ae 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"github.com/containrrr/watchtower/internal/meta"
 	"math"
 	"os"
 	"os/signal"
@@ -39,7 +40,6 @@ var (
 	rollingRestart bool
 	scope          string
 	// Set on build using ldflags
-	version = "v0.0.0-unknown"
 )
 
 var rootCmd = NewRootCommand()
@@ -273,7 +273,7 @@ func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) {
 			notifs = "Using notifications: " + notifList
 		}
 
-		log.Info("Watchtower ", version, "\n", notifs, "\n", filtering, "\n", schedMessage)
+		log.Info("Watchtower ", meta.Version, "\n", notifs, "\n", filtering, "\n", schedMessage)
 		if log.IsLevelEnabled(log.TraceLevel) {
 			log.Warn("trace level enabled: log will include sensitive information as credentials and tokens")
 		}
diff --git a/goreleaser.yml b/goreleaser.yml
index 3f5e95d..1904d5e 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -10,7 +10,7 @@ build:
     - arm
     - arm64
   ldflags:
-    - -s -w -X github.com/containrrr/watchtower/cmd.version={{.Version}}
+    - -s -w -X github.com/containrrr/watchtower/internal/meta.Version={{.Version}}
 archives:
   - 
     name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
diff --git a/internal/meta/meta.go b/internal/meta/meta.go
new file mode 100644
index 0000000..1571291
--- /dev/null
+++ b/internal/meta/meta.go
@@ -0,0 +1,13 @@
+package meta
+
+var (
+	// Version is the compile-time set version of Watchtower
+	Version = "v0.0.0-unknown"
+
+	// UserAgent is the http client identifier derived from Version
+	UserAgent string
+)
+
+func init() {
+	UserAgent = "Watchtower/" + Version
+}
diff --git a/pkg/registry/digest/digest.go b/pkg/registry/digest/digest.go
index 4634688..858bf33 100644
--- a/pkg/registry/digest/digest.go
+++ b/pkg/registry/digest/digest.go
@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/containrrr/watchtower/internal/meta"
 	"github.com/containrrr/watchtower/pkg/registry/auth"
 	"github.com/containrrr/watchtower/pkg/registry/manifest"
 	"github.com/containrrr/watchtower/pkg/types"
@@ -86,6 +87,7 @@ func GetDigest(url string, token string) (string, error) {
 	client := &http.Client{Transport: tr}
 
 	req, _ := http.NewRequest("HEAD", url, nil)
+	req.Header.Set("User-Agent", meta.UserAgent)
 
 	if token != "" {
 		logrus.WithField("token", token).Trace("Setting request token")


Test patch:
diff --git a/pkg/registry/digest/digest_test.go b/pkg/registry/digest/digest_test.go
index 0de6025..0321c1f 100644
--- a/pkg/registry/digest/digest_test.go
+++ b/pkg/registry/digest/digest_test.go
@@ -7,6 +7,8 @@ import (
 	wtTypes "github.com/containrrr/watchtower/pkg/types"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/ghttp"
+	"net/http"
 	"os"
 	"testing"
 	"time"
@@ -18,14 +20,16 @@ func TestDigest(t *testing.T) {
 	RunSpecs(GinkgoT(), "Digest Suite")
 }
 
-var DockerHubCredentials = &wtTypes.RegistryCredentials{
-	Username: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_DH_USERNAME"),
-	Password: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_DH_PASSWORD"),
-}
-var GHCRCredentials = &wtTypes.RegistryCredentials{
-	Username: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_GH_USERNAME"),
-	Password: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_GH_PASSWORD"),
-}
+var (
+	DockerHubCredentials = &wtTypes.RegistryCredentials{
+		Username: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_DH_USERNAME"),
+		Password: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_DH_PASSWORD"),
+	}
+	GHCRCredentials = &wtTypes.RegistryCredentials{
+		Username: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_GH_USERNAME"),
+		Password: os.Getenv("CI_INTEGRATION_TEST_REGISTRY_GH_PASSWORD"),
+	}
+)
 
 func SkipIfCredentialsEmpty(credentials *wtTypes.RegistryCredentials, fn func()) func() {
 	if credentials.Username == "" {
@@ -84,4 +88,32 @@ var _ = Describe("Digests", func() {
 			}),
 		)
 	})
+	When("sending a HEAD request", func() {
+		var server *ghttp.Server
+		BeforeEach(func() {
+			server = ghttp.NewServer()
+		})
+		AfterEach(func() {
+			server.Close()
+		})
+		It("should use a custom user-agent", func() {
+			server.AppendHandlers(
+				ghttp.CombineHandlers(
+					ghttp.VerifyHeader(http.Header{
+						"User-Agent": []string{"Watchtower/v0.0.0-unknown"},
+					}),
+					ghttp.RespondWith(http.StatusOK, "", http.Header{
+						digest.ContentDigestHeader: []string{
+							mockDigest,
+						},
+					}),
+				),
+			)
+			dig, err := digest.GetDigest(server.URL(), "token")
+			println(dig)
+			Expect(server.ReceivedRequests()).Should(HaveLen(1))
+			Expect(err).NotTo(HaveOccurred())
+			Expect(dig).To(Equal(mockDigest))
+		})
+	})
 })