diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/check.go golang-github-hashicorp-go-checkpoint-0.5.0/check.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/check.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/check.go 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,368 @@ +package checkpoint + +import ( + crand "crypto/rand" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "io/ioutil" + mrand "math/rand" + "net/http" + "net/url" + "os" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "time" + + "github.com/hashicorp/go-cleanhttp" +) + +var magicBytes = [4]byte{0x35, 0x77, 0x69, 0xFB} + +// CheckParams are the parameters for configuring a check request. +type CheckParams struct { + // Product and version are used to lookup the correct product and + // alerts for the proper version. The version is also used to perform + // a version check. + Product string + Version string + + // Arch and OS are used to filter alerts potentially only to things + // affecting a specific os/arch combination. If these aren't specified, + // they'll be automatically filled in. + Arch string + OS string + + // Signature is some random signature that should be stored and used + // as a cookie-like value. This ensures that alerts aren't repeated. + // If the signature is changed, repeat alerts may be sent down. The + // signature should NOT be anything identifiable to a user (such as + // a MAC address). It should be random. + // + // If SignatureFile is given, then the signature will be read from this + // file. If the file doesn't exist, then a random signature will + // automatically be generated and stored here. SignatureFile will be + // ignored if Signature is given. + Signature string + SignatureFile string + + // CacheFile, if specified, will cache the result of a check. The + // duration of the cache is specified by CacheDuration, and defaults + // to 48 hours if not specified. If the CacheFile is newer than the + // CacheDuration, than the Check will short-circuit and use those + // results. + // + // If the CacheFile directory doesn't exist, it will be created with + // permissions 0755. + CacheFile string + CacheDuration time.Duration + + // Force, if true, will force the check even if CHECKPOINT_DISABLE + // is set. Within HashiCorp products, this is ONLY USED when the user + // specifically requests it. This is never automatically done without + // the user's consent. + Force bool +} + +// CheckResponse is the response for a check request. +type CheckResponse struct { + Product string `json:"product"` + CurrentVersion string `json:"current_version"` + CurrentReleaseDate int `json:"current_release_date"` + CurrentDownloadURL string `json:"current_download_url"` + CurrentChangelogURL string `json:"current_changelog_url"` + ProjectWebsite string `json:"project_website"` + Outdated bool `json:"outdated"` + Alerts []*CheckAlert `json:"alerts"` +} + +// CheckAlert is a single alert message from a check request. +// +// These never have to be manually constructed, and are typically populated +// into a CheckResponse as a result of the Check request. +type CheckAlert struct { + ID int `json:"id"` + Date int `json:"date"` + Message string `json:"message"` + URL string `json:"url"` + Level string `json:"level"` +} + +// Check checks for alerts and new version information. +func Check(p *CheckParams) (*CheckResponse, error) { + if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" && !p.Force { + return &CheckResponse{}, nil + } + + // Set a default timeout of 3 sec for the check request (in milliseconds) + timeout := 3000 + if _, err := strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")); err == nil { + timeout, _ = strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")) + } + + // If we have a cached result, then use that + if r, err := checkCache(p.Version, p.CacheFile, p.CacheDuration); err != nil { + return nil, err + } else if r != nil { + defer r.Close() + return checkResult(r) + } + + var u url.URL + + if p.Arch == "" { + p.Arch = runtime.GOARCH + } + if p.OS == "" { + p.OS = runtime.GOOS + } + + // If we're given a SignatureFile, then attempt to read that. + signature := p.Signature + if p.Signature == "" && p.SignatureFile != "" { + var err error + signature, err = checkSignature(p.SignatureFile) + if err != nil { + return nil, err + } + } + + v := u.Query() + v.Set("version", p.Version) + v.Set("arch", p.Arch) + v.Set("os", p.OS) + v.Set("signature", signature) + + u.Scheme = "https" + u.Host = "checkpoint-api.hashicorp.com" + u.Path = fmt.Sprintf("/v1/check/%s", p.Product) + u.RawQuery = v.Encode() + + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "HashiCorp/go-checkpoint") + + client := cleanhttp.DefaultClient() + + // We use a short timeout since checking for new versions is not critical + // enough to block on if checkpoint is broken/slow. + client.Timeout = time.Duration(timeout) * time.Millisecond + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Unknown status: %d", resp.StatusCode) + } + + var r io.Reader = resp.Body + if p.CacheFile != "" { + // Make sure the directory holding our cache exists. + if err := os.MkdirAll(filepath.Dir(p.CacheFile), 0755); err != nil { + return nil, err + } + + // We have to cache the result, so write the response to the + // file as we read it. + f, err := os.Create(p.CacheFile) + if err != nil { + return nil, err + } + + // Write the cache header + if err := writeCacheHeader(f, p.Version); err != nil { + f.Close() + os.Remove(p.CacheFile) + return nil, err + } + + defer f.Close() + r = io.TeeReader(r, f) + } + + return checkResult(r) +} + +// CheckInterval is used to check for a response on a given interval duration. +// The interval is not exact, and checks are randomized to prevent a thundering +// herd. However, it is expected that on average one check is performed per +// interval. The returned channel may be closed to stop background checks. +func CheckInterval(p *CheckParams, interval time.Duration, cb func(*CheckResponse, error)) chan struct{} { + doneCh := make(chan struct{}) + + if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { + return doneCh + } + + go func() { + for { + select { + case <-time.After(randomStagger(interval)): + resp, err := Check(p) + cb(resp, err) + case <-doneCh: + return + } + } + }() + + return doneCh +} + +// randomStagger returns an interval that is between 3/4 and 5/4 of +// the given interval. The expected value is the interval. +func randomStagger(interval time.Duration) time.Duration { + stagger := time.Duration(mrand.Int63()) % (interval / 2) + return 3*(interval/4) + stagger +} + +func checkCache(current string, path string, d time.Duration) (io.ReadCloser, error) { + fi, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + // File doesn't exist, not a problem + return nil, nil + } + + return nil, err + } + + if d == 0 { + d = 48 * time.Hour + } + + if fi.ModTime().Add(d).Before(time.Now()) { + // Cache is busted, delete the old file and re-request. We ignore + // errors here because re-creating the file is fine too. + os.Remove(path) + return nil, nil + } + + // File looks good so far, open it up so we can inspect the contents. + f, err := os.Open(path) + if err != nil { + return nil, err + } + + // Check the signature of the file + var sig [4]byte + if err := binary.Read(f, binary.LittleEndian, sig[:]); err != nil { + f.Close() + return nil, err + } + if !reflect.DeepEqual(sig, magicBytes) { + // Signatures don't match. Reset. + f.Close() + return nil, nil + } + + // Check the version. If it changed, then rewrite + var length uint32 + if err := binary.Read(f, binary.LittleEndian, &length); err != nil { + f.Close() + return nil, err + } + data := make([]byte, length) + if _, err := io.ReadFull(f, data); err != nil { + f.Close() + return nil, err + } + if string(data) != current { + // Version changed, reset + f.Close() + return nil, nil + } + + return f, nil +} +func checkResult(r io.Reader) (*CheckResponse, error) { + var result CheckResponse + if err := json.NewDecoder(r).Decode(&result); err != nil { + return nil, err + } + return &result, nil +} + +func checkSignature(path string) (string, error) { + _, err := os.Stat(path) + if err == nil { + // The file exists, read it out + sigBytes, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + + // Split the file into lines + lines := strings.SplitN(string(sigBytes), "\n", 2) + if len(lines) > 0 { + return strings.TrimSpace(lines[0]), nil + } + } + + // If this isn't a non-exist error, then return that. + if !os.IsNotExist(err) { + return "", err + } + + // The file doesn't exist, so create a signature. + var b [16]byte + n := 0 + for n < 16 { + n2, err := crand.Read(b[n:]) + if err != nil { + return "", err + } + + n += n2 + } + signature := fmt.Sprintf( + "%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) + + // Make sure the directory holding our signature exists. + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return "", err + } + + // Write the signature + if err := ioutil.WriteFile(path, []byte(signature+"\n\n"+userMessage+"\n"), 0644); err != nil { + return "", err + } + + return signature, nil +} + +func writeCacheHeader(f io.Writer, v string) error { + // Write our signature first + if err := binary.Write(f, binary.LittleEndian, magicBytes); err != nil { + return err + } + + // Write out our current version length + length := uint32(len(v)) + if err := binary.Write(f, binary.LittleEndian, length); err != nil { + return err + } + + _, err := f.Write([]byte(v)) + return err +} + +// userMessage is suffixed to the signature file to provide feedback. +var userMessage = ` +This signature is a randomly generated UUID used to de-duplicate +alerts and version information. This signature is random, it is +not based on any personally identifiable information. To create +a new signature, you can simply delete this file at any time. +See the documentation for the software using Checkpoint for more +information on how to disable it. +` diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/checkpoint.go golang-github-hashicorp-go-checkpoint-0.5.0/checkpoint.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/checkpoint.go 2018-12-30 21:59:44.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/checkpoint.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,476 +0,0 @@ -// checkpoint is a package for checking version information and alerts -// for a HashiCorp product. -package checkpoint - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "io/ioutil" - mrand "math/rand" - "net/http" - "net/url" - "os" - "path/filepath" - "reflect" - "runtime" - "strconv" - "strings" - "time" - - "github.com/hashicorp/go-cleanhttp" - uuid "github.com/hashicorp/go-uuid" -) - -var magicBytes [4]byte = [4]byte{0x35, 0x77, 0x69, 0xFB} - -// ReportParams are the parameters for configuring a telemetry report. -type ReportParams struct { - // Signature is some random signature that should be stored and used - // as a cookie-like value. This ensures that alerts aren't repeated. - // If the signature is changed, repeat alerts may be sent down. The - // signature should NOT be anything identifiable to a user (such as - // a MAC address). It should be random. - // - // If SignatureFile is given, then the signature will be read from this - // file. If the file doesn't exist, then a random signature will - // automatically be generated and stored here. SignatureFile will be - // ignored if Signature is given. - Signature string `json:"signature"` - SignatureFile string `json:"-"` - - StartTime time.Time `json:"start_time"` - EndTime time.Time `json:"end_time"` - Arch string `json:"arch"` - OS string `json:"os"` - Payload interface{} `json:"payload,omitempty"` - Product string `json:"product"` - RunID string `json:"run_id"` - SchemaVersion string `json:"schema_version"` - Version string `json:"version"` -} - -func (i *ReportParams) signature() string { - signature := i.Signature - if i.Signature == "" && i.SignatureFile != "" { - var err error - signature, err = checkSignature(i.SignatureFile) - if err != nil { - return "" - } - } - return signature -} - -// Report sends telemetry information to checkpoint -func Report(ctx context.Context, r *ReportParams) error { - if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { - return nil - } - - req, err := ReportRequest(r) - if err != nil { - return err - } - - client := cleanhttp.DefaultClient() - resp, err := client.Do(req.WithContext(ctx)) - if err != nil { - return err - } - if resp.StatusCode != 201 { - return fmt.Errorf("Unknown status: %d", resp.StatusCode) - } - - return nil -} - -// ReportRequest creates a request object for making a report -func ReportRequest(r *ReportParams) (*http.Request, error) { - // Populate some fields automatically if we can - if r.RunID == "" { - uuid, err := uuid.GenerateUUID() - if err != nil { - return nil, err - } - r.RunID = uuid - } - if r.Arch == "" { - r.Arch = runtime.GOARCH - } - if r.OS == "" { - r.OS = runtime.GOOS - } - if r.Signature == "" { - r.Signature = r.signature() - } - - b, err := json.Marshal(r) - if err != nil { - return nil, err - } - - u := &url.URL{ - Scheme: "https", - Host: "checkpoint-api.hashicorp.com", - Path: fmt.Sprintf("/v1/telemetry/%s", r.Product), - } - - req, err := http.NewRequest("POST", u.String(), bytes.NewReader(b)) - if err != nil { - return nil, err - } - req.Header.Add("Accept", "application/json") - req.Header.Add("User-Agent", "HashiCorp/go-checkpoint") - - return req, nil -} - -// CheckParams are the parameters for configuring a check request. -type CheckParams struct { - // Product and version are used to lookup the correct product and - // alerts for the proper version. The version is also used to perform - // a version check. - Product string - Version string - - // Arch and OS are used to filter alerts potentially only to things - // affecting a specific os/arch combination. If these aren't specified, - // they'll be automatically filled in. - Arch string - OS string - - // Signature is some random signature that should be stored and used - // as a cookie-like value. This ensures that alerts aren't repeated. - // If the signature is changed, repeat alerts may be sent down. The - // signature should NOT be anything identifiable to a user (such as - // a MAC address). It should be random. - // - // If SignatureFile is given, then the signature will be read from this - // file. If the file doesn't exist, then a random signature will - // automatically be generated and stored here. SignatureFile will be - // ignored if Signature is given. - Signature string - SignatureFile string - - // CacheFile, if specified, will cache the result of a check. The - // duration of the cache is specified by CacheDuration, and defaults - // to 48 hours if not specified. If the CacheFile is newer than the - // CacheDuration, than the Check will short-circuit and use those - // results. - // - // If the CacheFile directory doesn't exist, it will be created with - // permissions 0755. - CacheFile string - CacheDuration time.Duration - - // Force, if true, will force the check even if CHECKPOINT_DISABLE - // is set. Within HashiCorp products, this is ONLY USED when the user - // specifically requests it. This is never automatically done without - // the user's consent. - Force bool -} - -// CheckResponse is the response for a check request. -type CheckResponse struct { - Product string - CurrentVersion string `json:"current_version"` - CurrentReleaseDate int `json:"current_release_date"` - CurrentDownloadURL string `json:"current_download_url"` - CurrentChangelogURL string `json:"current_changelog_url"` - ProjectWebsite string `json:"project_website"` - Outdated bool `json:"outdated"` - Alerts []*CheckAlert -} - -// CheckAlert is a single alert message from a check request. -// -// These never have to be manually constructed, and are typically populated -// into a CheckResponse as a result of the Check request. -type CheckAlert struct { - ID int - Date int - Message string - URL string - Level string -} - -// Check checks for alerts and new version information. -func Check(p *CheckParams) (*CheckResponse, error) { - if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" && !p.Force { - return &CheckResponse{}, nil - } - - // set a default timeout of 3 sec for the check request (in milliseconds) - timeout := 3000 - if _, err := strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")); err == nil { - timeout, _ = strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")) - } - - // If we have a cached result, then use that - if r, err := checkCache(p.Version, p.CacheFile, p.CacheDuration); err != nil { - return nil, err - } else if r != nil { - defer r.Close() - return checkResult(r) - } - - var u url.URL - - if p.Arch == "" { - p.Arch = runtime.GOARCH - } - if p.OS == "" { - p.OS = runtime.GOOS - } - - // If we're given a SignatureFile, then attempt to read that. - signature := p.Signature - if p.Signature == "" && p.SignatureFile != "" { - var err error - signature, err = checkSignature(p.SignatureFile) - if err != nil { - return nil, err - } - } - - v := u.Query() - v.Set("version", p.Version) - v.Set("arch", p.Arch) - v.Set("os", p.OS) - v.Set("signature", signature) - - u.Scheme = "https" - u.Host = "checkpoint-api.hashicorp.com" - u.Path = fmt.Sprintf("/v1/check/%s", p.Product) - u.RawQuery = v.Encode() - - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return nil, err - } - req.Header.Add("Accept", "application/json") - req.Header.Add("User-Agent", "HashiCorp/go-checkpoint") - - client := cleanhttp.DefaultClient() - - // We use a short timeout since checking for new versions is not critical - // enough to block on if checkpoint is broken/slow. - client.Timeout = time.Duration(timeout) * time.Millisecond - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Unknown status: %d", resp.StatusCode) - } - - var r io.Reader = resp.Body - if p.CacheFile != "" { - // Make sure the directory holding our cache exists. - if err := os.MkdirAll(filepath.Dir(p.CacheFile), 0755); err != nil { - return nil, err - } - - // We have to cache the result, so write the response to the - // file as we read it. - f, err := os.Create(p.CacheFile) - if err != nil { - return nil, err - } - - // Write the cache header - if err := writeCacheHeader(f, p.Version); err != nil { - f.Close() - os.Remove(p.CacheFile) - return nil, err - } - - defer f.Close() - r = io.TeeReader(r, f) - } - - return checkResult(r) -} - -// CheckInterval is used to check for a response on a given interval duration. -// The interval is not exact, and checks are randomized to prevent a thundering -// herd. However, it is expected that on average one check is performed per -// interval. The returned channel may be closed to stop background checks. -func CheckInterval(p *CheckParams, interval time.Duration, cb func(*CheckResponse, error)) chan struct{} { - doneCh := make(chan struct{}) - - if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { - return doneCh - } - - go func() { - for { - select { - case <-time.After(randomStagger(interval)): - resp, err := Check(p) - cb(resp, err) - case <-doneCh: - return - } - } - }() - - return doneCh -} - -// randomStagger returns an interval that is between 3/4 and 5/4 of -// the given interval. The expected value is the interval. -func randomStagger(interval time.Duration) time.Duration { - stagger := time.Duration(mrand.Int63()) % (interval / 2) - return 3*(interval/4) + stagger -} - -func checkCache(current string, path string, d time.Duration) (io.ReadCloser, error) { - fi, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - // File doesn't exist, not a problem - return nil, nil - } - - return nil, err - } - - if d == 0 { - d = 48 * time.Hour - } - - if fi.ModTime().Add(d).Before(time.Now()) { - // Cache is busted, delete the old file and re-request. We ignore - // errors here because re-creating the file is fine too. - os.Remove(path) - return nil, nil - } - - // File looks good so far, open it up so we can inspect the contents. - f, err := os.Open(path) - if err != nil { - return nil, err - } - - // Check the signature of the file - var sig [4]byte - if err := binary.Read(f, binary.LittleEndian, sig[:]); err != nil { - f.Close() - return nil, err - } - if !reflect.DeepEqual(sig, magicBytes) { - // Signatures don't match. Reset. - f.Close() - return nil, nil - } - - // Check the version. If it changed, then rewrite - var length uint32 - if err := binary.Read(f, binary.LittleEndian, &length); err != nil { - f.Close() - return nil, err - } - data := make([]byte, length) - if _, err := io.ReadFull(f, data); err != nil { - f.Close() - return nil, err - } - if string(data) != current { - // Version changed, reset - f.Close() - return nil, nil - } - - return f, nil -} - -func checkResult(r io.Reader) (*CheckResponse, error) { - var result CheckResponse - dec := json.NewDecoder(r) - if err := dec.Decode(&result); err != nil { - return nil, err - } - - return &result, nil -} - -func checkSignature(path string) (string, error) { - _, err := os.Stat(path) - if err == nil { - // The file exists, read it out - sigBytes, err := ioutil.ReadFile(path) - if err != nil { - return "", err - } - - // Split the file into lines - lines := strings.SplitN(string(sigBytes), "\n", 2) - if len(lines) > 0 { - return strings.TrimSpace(lines[0]), nil - } - } - - // If this isn't a non-exist error, then return that. - if !os.IsNotExist(err) { - return "", err - } - - // The file doesn't exist, so create a signature. - var b [16]byte - n := 0 - for n < 16 { - n2, err := rand.Read(b[n:]) - if err != nil { - return "", err - } - - n += n2 - } - signature := fmt.Sprintf( - "%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) - - // Make sure the directory holding our signature exists. - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return "", err - } - - // Write the signature - if err := ioutil.WriteFile(path, []byte(signature+"\n\n"+userMessage+"\n"), 0644); err != nil { - return "", err - } - - return signature, nil -} - -func writeCacheHeader(f io.Writer, v string) error { - // Write our signature first - if err := binary.Write(f, binary.LittleEndian, magicBytes); err != nil { - return err - } - - // Write out our current version length - var length uint32 = uint32(len(v)) - if err := binary.Write(f, binary.LittleEndian, length); err != nil { - return err - } - - _, err := f.Write([]byte(v)) - return err -} - -// userMessage is suffixed to the signature file to provide feedback. -var userMessage = ` -This signature is a randomly generated UUID used to de-duplicate -alerts and version information. This signature is random, it is -not based on any personally identifiable information. To create -a new signature, you can simply delete this file at any time. -See the documentation for the software using Checkpoint for more -information on how to disable it. -` diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/checkpoint_test.go golang-github-hashicorp-go-checkpoint-0.5.0/checkpoint_test.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/checkpoint_test.go 2018-12-30 21:59:44.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/checkpoint_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,249 +0,0 @@ -package checkpoint - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "strings" - "testing" - "time" -) - -func TestCheck(t *testing.T) { - expected := &CheckResponse{ - Product: "test", - CurrentVersion: "1.0", - CurrentReleaseDate: 0, - CurrentDownloadURL: "http://www.hashicorp.com", - CurrentChangelogURL: "http://www.hashicorp.com", - ProjectWebsite: "http://www.hashicorp.com", - Outdated: false, - Alerts: []*CheckAlert{}, - } - - actual, err := Check(&CheckParams{ - Product: "test", - Version: "1.0", - }) - - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestCheckTimeout(t *testing.T) { - os.Setenv("CHECKPOINT_TIMEOUT", "50") - defer os.Setenv("CHECKPOINT_TIMEOUT", "") - - expected := "Client.Timeout exceeded while awaiting headers" - - actual, err := Check(&CheckParams{ - Product: "test", - Version: "1.0", - }) - - if !strings.Contains(err.Error(), expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestCheck_disabled(t *testing.T) { - os.Setenv("CHECKPOINT_DISABLE", "1") - defer os.Setenv("CHECKPOINT_DISABLE", "") - - expected := &CheckResponse{} - - actual, err := Check(&CheckParams{ - Product: "test", - Version: "1.0", - }) - - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("expected %+v to equal %+v", actual, expected) - } -} - -func TestCheck_cache(t *testing.T) { - dir, err := ioutil.TempDir("", "checkpoint") - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := &CheckResponse{ - Product: "test", - CurrentVersion: "1.0", - CurrentReleaseDate: 0, - CurrentDownloadURL: "http://www.hashicorp.com", - CurrentChangelogURL: "http://www.hashicorp.com", - ProjectWebsite: "http://www.hashicorp.com", - Outdated: false, - Alerts: []*CheckAlert{}, - } - - var actual *CheckResponse - for i := 0; i < 5; i++ { - var err error - actual, err = Check(&CheckParams{ - Product: "test", - Version: "1.0", - CacheFile: filepath.Join(dir, "cache"), - }) - if err != nil { - t.Fatalf("err: %s", err) - } - } - - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestCheck_cacheNested(t *testing.T) { - dir, err := ioutil.TempDir("", "checkpoint") - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := &CheckResponse{ - Product: "test", - CurrentVersion: "1.0", - CurrentReleaseDate: 0, - CurrentDownloadURL: "http://www.hashicorp.com", - CurrentChangelogURL: "http://www.hashicorp.com", - ProjectWebsite: "http://www.hashicorp.com", - Outdated: false, - Alerts: []*CheckAlert{}, - } - - var actual *CheckResponse - for i := 0; i < 5; i++ { - var err error - actual, err = Check(&CheckParams{ - Product: "test", - Version: "1.0", - CacheFile: filepath.Join(dir, "nested", "cache"), - }) - if err != nil { - t.Fatalf("err: %s", err) - } - } - - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestCheckInterval(t *testing.T) { - expected := &CheckResponse{ - Product: "test", - CurrentVersion: "1.0", - CurrentReleaseDate: 0, - CurrentDownloadURL: "http://www.hashicorp.com", - CurrentChangelogURL: "http://www.hashicorp.com", - ProjectWebsite: "http://www.hashicorp.com", - Outdated: false, - Alerts: []*CheckAlert{}, - } - - params := &CheckParams{ - Product: "test", - Version: "1.0", - } - - calledCh := make(chan struct{}) - checkFn := func(actual *CheckResponse, err error) { - defer close(calledCh) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } - } - - doneCh := CheckInterval(params, 500*time.Millisecond, checkFn) - defer close(doneCh) - - select { - case <-calledCh: - case <-time.After(time.Second): - t.Fatalf("timeout") - } -} - -func TestCheckInterval_disabled(t *testing.T) { - os.Setenv("CHECKPOINT_DISABLE", "1") - defer os.Setenv("CHECKPOINT_DISABLE", "") - - params := &CheckParams{ - Product: "test", - Version: "1.0", - } - - calledCh := make(chan struct{}) - checkFn := func(actual *CheckResponse, err error) { - defer close(calledCh) - } - - doneCh := CheckInterval(params, 500*time.Millisecond, checkFn) - defer close(doneCh) - - select { - case <-calledCh: - t.Fatal("expected callback to not invoke") - case <-time.After(time.Second): - } -} - -func TestRandomStagger(t *testing.T) { - intv := 24 * time.Hour - min := 18 * time.Hour - max := 30 * time.Hour - for i := 0; i < 1000; i++ { - out := randomStagger(intv) - if out < min || out > max { - t.Fatalf("bad: %v", out) - } - } -} - -func TestReport_sendsRequest(t *testing.T) { - r := &ReportParams{ - Signature: "sig", - Product: "prod", - } - - req, err := ReportRequest(r) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !strings.HasSuffix(req.URL.Path, "/telemetry/prod") { - t.Fatalf("Expected url to have the product. Got %s", req.URL.String()) - } - - b, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatalf("err: %s", err) - } - - var p ReportParams - if err := json.Unmarshal(b, &p); err != nil { - t.Fatalf("err: %s", err) - } - - if p.Signature != "sig" { - t.Fatalf("Expected request body to have data from request. got %#v", p) - } -} diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/check_test.go golang-github-hashicorp-go-checkpoint-0.5.0/check_test.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/check_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/check_test.go 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,216 @@ +package checkpoint + +import ( + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + "time" +) + +func TestCheck(t *testing.T) { + expected := &CheckResponse{ + Product: "test", + CurrentVersion: "1.0", + CurrentReleaseDate: 0, + CurrentDownloadURL: "http://www.hashicorp.com", + CurrentChangelogURL: "http://www.hashicorp.com", + ProjectWebsite: "http://www.hashicorp.com", + Outdated: false, + Alerts: []*CheckAlert{}, + } + + actual, err := Check(&CheckParams{ + Product: "test", + Version: "1.0", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } +} + +func TestCheck_timeout(t *testing.T) { + os.Setenv("CHECKPOINT_TIMEOUT", "50") + defer os.Setenv("CHECKPOINT_TIMEOUT", "") + + expected := "Client.Timeout exceeded while awaiting headers" + + _, err := Check(&CheckParams{ + Product: "test", + Version: "1.0", + }) + + if err == nil || !strings.Contains(err.Error(), expected) { + t.Fatalf("expected a timeout error, got: %v", err) + } +} + +func TestCheck_disabled(t *testing.T) { + os.Setenv("CHECKPOINT_DISABLE", "1") + defer os.Setenv("CHECKPOINT_DISABLE", "") + + expected := &CheckResponse{} + + actual, err := Check(&CheckParams{ + Product: "test", + Version: "1.0", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } +} + +func TestCheck_cache(t *testing.T) { + dir, err := ioutil.TempDir("", "checkpoint") + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := &CheckResponse{ + Product: "test", + CurrentVersion: "1.0", + CurrentReleaseDate: 0, + CurrentDownloadURL: "http://www.hashicorp.com", + CurrentChangelogURL: "http://www.hashicorp.com", + ProjectWebsite: "http://www.hashicorp.com", + Outdated: false, + Alerts: []*CheckAlert{}, + } + + var actual *CheckResponse + for i := 0; i < 5; i++ { + var err error + actual, err = Check(&CheckParams{ + Product: "test", + Version: "1.0", + CacheFile: filepath.Join(dir, "cache"), + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } +} + +func TestCheck_cacheNested(t *testing.T) { + dir, err := ioutil.TempDir("", "checkpoint") + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := &CheckResponse{ + Product: "test", + CurrentVersion: "1.0", + CurrentReleaseDate: 0, + CurrentDownloadURL: "http://www.hashicorp.com", + CurrentChangelogURL: "http://www.hashicorp.com", + ProjectWebsite: "http://www.hashicorp.com", + Outdated: false, + Alerts: []*CheckAlert{}, + } + + var actual *CheckResponse + for i := 0; i < 5; i++ { + var err error + actual, err = Check(&CheckParams{ + Product: "test", + Version: "1.0", + CacheFile: filepath.Join(dir, "nested", "cache"), + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } +} + +func TestCheckInterval(t *testing.T) { + expected := &CheckResponse{ + Product: "test", + CurrentVersion: "1.0", + CurrentReleaseDate: 0, + CurrentDownloadURL: "http://www.hashicorp.com", + CurrentChangelogURL: "http://www.hashicorp.com", + ProjectWebsite: "http://www.hashicorp.com", + Outdated: false, + Alerts: []*CheckAlert{}, + } + + params := &CheckParams{ + Product: "test", + Version: "1.0", + } + + calledCh := make(chan struct{}) + checkFn := func(actual *CheckResponse, err error) { + defer close(calledCh) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } + } + + doneCh := CheckInterval(params, 500*time.Millisecond, checkFn) + defer close(doneCh) + + select { + case <-calledCh: + case <-time.After(1250 * time.Millisecond): + t.Fatalf("timeout") + } +} + +func TestCheckInterval_disabled(t *testing.T) { + os.Setenv("CHECKPOINT_DISABLE", "1") + defer os.Setenv("CHECKPOINT_DISABLE", "") + + params := &CheckParams{ + Product: "test", + Version: "1.0", + } + + calledCh := make(chan struct{}) + checkFn := func(actual *CheckResponse, err error) { + defer close(calledCh) + } + + doneCh := CheckInterval(params, 500*time.Millisecond, checkFn) + defer close(doneCh) + + select { + case <-calledCh: + t.Fatal("expected callback to not invoke") + case <-time.After(time.Second): + } +} + +func TestRandomStagger(t *testing.T) { + intv := 24 * time.Hour + min := 18 * time.Hour + max := 30 * time.Hour + for i := 0; i < 1000; i++ { + out := randomStagger(intv) + if out < min || out > max { + t.Fatalf("unexpected value: %v", out) + } + } +} diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/changelog golang-github-hashicorp-go-checkpoint-0.5.0/debian/changelog --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/changelog 2019-01-01 10:30:35.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/changelog 2021-02-10 07:13:59.000000000 +0000 @@ -1,3 +1,28 @@ +golang-github-hashicorp-go-checkpoint (0.5.0-1) unstable; urgency=medium + + [ Debian Janitor (Jelmer Vernooij) ] + * Bump debhelper from old 11 to 12. + * Set debhelper-compat version in Build-Depends. + * Set upstream metadata fields: Bug-Database, Bug-Submit. + * Update standards version to 4.5.0, no changes needed. + + [ Anthony Fok ] + * Revert debian/watch to track released version + * New upstream version 0.5.0 + * debian/control: + - Apply "cme fix dpkg" fixes: + + Organize debian/control fields + + Bump Standards-Version to 4.5.1 (no change) + - Change Section from devel to golang + - Bump debhelper dependency to "Build-Depends: debhelper-compat (= 13)" + - Add "Rules-Requires-Root: no" + - Add minimum version to dependencies + * debian/gbp.conf: Set debian-branch to debian/sid for DEP-14 conformance + * Disable debian/patches/timeout.patch + * Refresh debian/patches/enable.patch + + -- Anthony Fok Wed, 10 Feb 2021 00:13:59 -0700 + golang-github-hashicorp-go-checkpoint (0.0~git20171009.1545e56-2) unstable; urgency=medium * Remove old debian/tests/* in favour of autopkgtest-pkg-go (autodep8) diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/compat golang-github-hashicorp-go-checkpoint-0.5.0/debian/compat --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/compat 2019-01-01 00:52:47.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/control golang-github-hashicorp-go-checkpoint-0.5.0/debian/control --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/control 2019-01-01 10:23:25.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/control 2021-02-10 06:50:28.000000000 +0000 @@ -1,30 +1,31 @@ Source: golang-github-hashicorp-go-checkpoint -Section: devel -Priority: optional Maintainer: Debian Go Packaging Team Uploaders: Tianon Gravi , Tim Potter , Anthony Fok +Section: golang +Testsuite: autopkgtest-pkg-go +Priority: optional Build-Depends: ca-certificates, - debhelper (>= 11~), + debhelper-compat (= 13), dh-golang, golang-any, - golang-github-hashicorp-go-cleanhttp-dev, - golang-github-hashicorp-go-uuid-dev, -Standards-Version: 4.3.0 -Homepage: https://github.com/hashicorp/go-checkpoint + golang-github-hashicorp-go-cleanhttp-dev (>= 0.5.0), + golang-github-hashicorp-go-uuid-dev (>= 1.0.0) +Standards-Version: 4.5.1 Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-github-hashicorp-go-checkpoint Vcs-Git: https://salsa.debian.org/go-team/packages/golang-github-hashicorp-go-checkpoint.git +Homepage: https://github.com/hashicorp/go-checkpoint +Rules-Requires-Root: no XS-Go-Import-Path: github.com/hashicorp/go-checkpoint -Testsuite: autopkgtest-pkg-go Package: golang-github-hashicorp-go-checkpoint-dev Architecture: all Depends: ca-certificates, - golang-github-hashicorp-go-cleanhttp-dev, - golang-github-hashicorp-go-uuid-dev, + golang-github-hashicorp-go-cleanhttp-dev (>= 0.5.0), + golang-github-hashicorp-go-uuid-dev (>= 1.0.0), ${misc:Depends}, - ${shlibs:Depends}, + ${shlibs:Depends} Description: Go Checkpoint Client Checkpoint is an internal service at Hashicorp that is used to check version information, broadcoast security bulletins, etc. diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/gbp.conf golang-github-hashicorp-go-checkpoint-0.5.0/debian/gbp.conf --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/gbp.conf 2019-01-01 00:52:47.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/gbp.conf 2021-02-10 06:40:07.000000000 +0000 @@ -1,2 +1,4 @@ [DEFAULT] +debian-branch = debian/sid +dist = DEP14 pristine-tar = True diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/patches/enable.patch golang-github-hashicorp-go-checkpoint-0.5.0/debian/patches/enable.patch --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/patches/enable.patch 2019-01-01 00:52:47.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/patches/enable.patch 2021-02-10 07:13:51.000000000 +0000 @@ -15,9 +15,9 @@ **Note:** This repository is probably useless outside of internal HashiCorp use. It is open source for disclosure and because our open source projects must be able to link to it. ---- a/checkpoint.go -+++ b/checkpoint.go -@@ -201,7 +201,7 @@ +--- a/check.go ++++ b/check.go +@@ -94,7 +94,7 @@ // Check checks for alerts and new version information. func Check(p *CheckParams) (*CheckResponse, error) { @@ -26,7 +26,7 @@ return &CheckResponse{}, nil } -@@ -305,7 +305,7 @@ +@@ -200,7 +200,7 @@ func CheckInterval(p *CheckParams, interval time.Duration, cb func(*CheckResponse, error)) chan struct{} { doneCh := make(chan struct{}) @@ -35,9 +35,9 @@ return doneCh } ---- a/checkpoint_test.go -+++ b/checkpoint_test.go -@@ -11,6 +11,10 @@ +--- a/check_test.go ++++ b/check_test.go +@@ -10,6 +10,10 @@ "time" ) @@ -48,7 +48,7 @@ func TestCheck(t *testing.T) { expected := &CheckResponse{ Product: "test", -@@ -54,8 +58,8 @@ +@@ -52,8 +56,8 @@ } func TestCheck_disabled(t *testing.T) { @@ -59,7 +59,7 @@ expected := &CheckResponse{} -@@ -183,8 +187,8 @@ +@@ -180,8 +184,8 @@ } func TestCheckInterval_disabled(t *testing.T) { @@ -70,3 +70,49 @@ params := &CheckParams{ Product: "test", +--- a/telemetry.go ++++ b/telemetry.go +@@ -55,7 +55,7 @@ + + // Report sends telemetry information to checkpoint + func Report(ctx context.Context, r *ReportParams) error { +- if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { ++ if enabled := os.Getenv("CHECKPOINT_ENABLE"); enabled == "" { + return nil + } + +--- a/versions.go ++++ b/versions.go +@@ -38,7 +38,7 @@ + + // Versions returns the version constrains for a given service and product. + func Versions(p *VersionsParams) (*VersionsResponse, error) { +- if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" && !p.Force { ++ if enabled := os.Getenv("CHECKPOINT_ENABLE"); enabled == "" && !p.Force { + return &VersionsResponse{}, nil + } + +--- a/versions_test.go ++++ b/versions_test.go +@@ -7,6 +7,10 @@ + "testing" + ) + ++func init() { ++ os.Setenv("CHECKPOINT_ENABLE", "1") ++} ++ + func TestVersions(t *testing.T) { + t.Skip("endpoint does not exist yet") + +@@ -52,8 +56,8 @@ + func TestVersions_disabled(t *testing.T) { + t.Skip("endpoint does not exist yet") + +- os.Setenv("CHECKPOINT_DISABLE", "1") +- defer os.Setenv("CHECKPOINT_DISABLE", "") ++ os.Setenv("CHECKPOINT_ENABLE", "") ++ defer os.Setenv("CHECKPOINT_ENABLE", "1") + + expected := &CheckResponse{} + diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/patches/series golang-github-hashicorp-go-checkpoint-0.5.0/debian/patches/series --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/patches/series 2019-01-01 00:52:47.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/patches/series 2021-02-10 07:13:46.000000000 +0000 @@ -1,2 +1,2 @@ -timeout.patch +#timeout.patch enable.patch diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/upstream/metadata golang-github-hashicorp-go-checkpoint-0.5.0/debian/upstream/metadata --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/upstream/metadata 2021-02-10 06:25:47.000000000 +0000 @@ -0,0 +1,3 @@ +--- +Bug-Database: https://github.com/hashicorp/go-checkpoint/issues +Bug-Submit: https://github.com/hashicorp/go-checkpoint/issues/new diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/watch golang-github-hashicorp-go-checkpoint-0.5.0/debian/watch --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/debian/watch 2019-01-01 10:30:08.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/debian/watch 2021-02-10 06:38:56.000000000 +0000 @@ -1,4 +1,4 @@ version=4 -opts="mode=git, pgpmode=none" \ -https://github.com/hashicorp/go-checkpoint \ -HEAD debian +opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%golang-github-hashicorp-go-checkpoint-$1.tar.gz%" \ + https://github.com/hashicorp/go-checkpoint/tags \ + (?:.*?/)?v?(\d[\d.]*)\.tar\.gz diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/go.mod golang-github-hashicorp-go-checkpoint-0.5.0/go.mod --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/go.mod 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/go.mod 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,6 @@ +module github.com/hashicorp/go-checkpoint + +require ( + github.com/hashicorp/go-cleanhttp v0.5.0 + github.com/hashicorp/go-uuid v1.0.0 +) diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/go.sum golang-github-hashicorp-go-checkpoint-0.5.0/go.sum --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/go.sum 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/go.sum 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,4 @@ +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/telemetry.go golang-github-hashicorp-go-checkpoint-0.5.0/telemetry.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/telemetry.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/telemetry.go 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,118 @@ +package checkpoint + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "runtime" + "time" + + "github.com/hashicorp/go-cleanhttp" + uuid "github.com/hashicorp/go-uuid" +) + +// ReportParams are the parameters for configuring a telemetry report. +type ReportParams struct { + // Signature is some random signature that should be stored and used + // as a cookie-like value. This ensures that alerts aren't repeated. + // If the signature is changed, repeat alerts may be sent down. The + // signature should NOT be anything identifiable to a user (such as + // a MAC address). It should be random. + // + // If SignatureFile is given, then the signature will be read from this + // file. If the file doesn't exist, then a random signature will + // automatically be generated and stored here. SignatureFile will be + // ignored if Signature is given. + Signature string `json:"signature"` + SignatureFile string `json:"-"` + + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time"` + Arch string `json:"arch"` + OS string `json:"os"` + Payload interface{} `json:"payload,omitempty"` + Product string `json:"product"` + RunID string `json:"run_id"` + SchemaVersion string `json:"schema_version"` + Version string `json:"version"` +} + +func (i *ReportParams) signature() string { + signature := i.Signature + if i.Signature == "" && i.SignatureFile != "" { + var err error + signature, err = checkSignature(i.SignatureFile) + if err != nil { + return "" + } + } + return signature +} + +// Report sends telemetry information to checkpoint +func Report(ctx context.Context, r *ReportParams) error { + if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { + return nil + } + + req, err := ReportRequest(r) + if err != nil { + return err + } + + client := cleanhttp.DefaultClient() + resp, err := client.Do(req.WithContext(ctx)) + if err != nil { + return err + } + if resp.StatusCode != 201 { + return fmt.Errorf("Unknown status: %d", resp.StatusCode) + } + + return nil +} + +// ReportRequest creates a request object for making a report +func ReportRequest(r *ReportParams) (*http.Request, error) { + // Populate some fields automatically if we can + if r.RunID == "" { + uuid, err := uuid.GenerateUUID() + if err != nil { + return nil, err + } + r.RunID = uuid + } + if r.Arch == "" { + r.Arch = runtime.GOARCH + } + if r.OS == "" { + r.OS = runtime.GOOS + } + if r.Signature == "" { + r.Signature = r.signature() + } + + b, err := json.Marshal(r) + if err != nil { + return nil, err + } + + u := &url.URL{ + Scheme: "https", + Host: "checkpoint-api.hashicorp.com", + Path: fmt.Sprintf("/v1/telemetry/%s", r.Product), + } + + req, err := http.NewRequest("POST", u.String(), bytes.NewReader(b)) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "HashiCorp/go-checkpoint") + + return req, nil +} diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/telemetry_test.go golang-github-hashicorp-go-checkpoint-0.5.0/telemetry_test.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/telemetry_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/telemetry_test.go 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,33 @@ +package checkpoint + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestReport_sendsRequest(t *testing.T) { + expected := &ReportParams{ + Signature: "sig", + Product: "prod", + } + + req, err := ReportRequest(expected) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer req.Body.Close() + + if !strings.HasSuffix(req.URL.Path, "/telemetry/prod") { + t.Fatalf("expected url to include the product, got %s", req.URL.String()) + } + + var actual ReportParams + if err := json.NewDecoder(req.Body).Decode(&actual); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if actual.Signature != expected.Signature { + t.Fatalf("expected %#v, got %#v", expected, actual) + } +} diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/versions.go golang-github-hashicorp-go-checkpoint-0.5.0/versions.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/versions.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/versions.go 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,90 @@ +package checkpoint + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "strconv" + "time" + + "github.com/hashicorp/go-cleanhttp" +) + +// VersionsParams are the parameters for a versions request. +type VersionsParams struct { + // Service is used to lookup the correct service. + Service string + + // Product is used to filter the version contraints. + Product string + + // Force, if true, will force the check even if CHECKPOINT_DISABLE + // is set. Within HashiCorp products, this is ONLY USED when the user + // specifically requests it. This is never automatically done without + // the user's consent. + Force bool +} + +// VersionsResponse is the response for a versions request. +type VersionsResponse struct { + Service string `json:"service"` + Product string `json:"product"` + Minimum string `json:"minimum"` + Maximum string `json:"maximum"` + Excluding []string `json:"excluding"` +} + +// Versions returns the version constrains for a given service and product. +func Versions(p *VersionsParams) (*VersionsResponse, error) { + if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" && !p.Force { + return &VersionsResponse{}, nil + } + + // Set a default timeout of 1 sec for the versions request (in milliseconds) + timeout := 1000 + if _, err := strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")); err == nil { + timeout, _ = strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")) + } + + v := url.Values{} + v.Set("product", p.Product) + + u := &url.URL{ + Scheme: "https", + Host: "checkpoint-api.hashicorp.com", + Path: fmt.Sprintf("/v1/versions/%s", p.Service), + RawQuery: v.Encode(), + } + + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "HashiCorp/go-checkpoint") + + client := cleanhttp.DefaultClient() + + // We use a short timeout since checking for new versions is not critical + // enough to block on if checkpoint is broken/slow. + client.Timeout = time.Duration(timeout) * time.Millisecond + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Unknown status: %d", resp.StatusCode) + } + + result := &VersionsResponse{} + if err := json.NewDecoder(resp.Body).Decode(result); err != nil { + return nil, err + } + + return result, nil +} diff -Nru golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/versions_test.go golang-github-hashicorp-go-checkpoint-0.5.0/versions_test.go --- golang-github-hashicorp-go-checkpoint-0.0~git20171009.1545e56/versions_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-go-checkpoint-0.5.0/versions_test.go 2019-01-24 19:13:39.000000000 +0000 @@ -0,0 +1,71 @@ +package checkpoint + +import ( + "os" + "reflect" + "strings" + "testing" +) + +func TestVersions(t *testing.T) { + t.Skip("endpoint does not exist yet") + + expected := &VersionsResponse{ + Service: "test.v1", + Product: "test", + Minimum: "1.0", + Excluding: []string{"1.3"}, + Maximum: "2.0", + } + + actual, err := Versions(&VersionsParams{ + Service: "test.v1", + Product: "test", + }) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } +} + +func TestVersions_timeout(t *testing.T) { + t.Skip("endpoint does not exist yet") + + os.Setenv("CHECKPOINT_TIMEOUT", "50") + defer os.Setenv("CHECKPOINT_TIMEOUT", "") + + expected := "Client.Timeout exceeded while awaiting headers" + + _, err := Versions(&VersionsParams{ + Service: "test.v1", + Product: "test", + }) + + if err == nil || !strings.Contains(err.Error(), expected) { + t.Fatalf("expected a timeout error, got: %v", err) + } +} + +func TestVersions_disabled(t *testing.T) { + t.Skip("endpoint does not exist yet") + + os.Setenv("CHECKPOINT_DISABLE", "1") + defer os.Setenv("CHECKPOINT_DISABLE", "") + + expected := &CheckResponse{} + + actual, err := Versions(&VersionsParams{ + Service: "test.v1", + Product: "test", + }) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %#v, got: %#v", expected, actual) + } +}