diff --git a/go.mod b/go.mod index 4e2db354..19818122 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/replicatedhq/replicated-sdk go 1.20 require ( + github.com/blang/semver v3.5.1+incompatible github.com/cenkalti/backoff/v4 v4.2.0 github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index 74d16dbf..32026dd9 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= diff --git a/pkg/apiserver/bootstrap.go b/pkg/apiserver/bootstrap.go index bbd86420..b7e8bf3f 100644 --- a/pkg/apiserver/bootstrap.go +++ b/pkg/apiserver/bootstrap.go @@ -10,6 +10,7 @@ import ( "github.com/replicatedhq/replicated-sdk/pkg/integration" "github.com/replicatedhq/replicated-sdk/pkg/k8sutil" sdklicense "github.com/replicatedhq/replicated-sdk/pkg/license" + "github.com/replicatedhq/replicated-sdk/pkg/logger" "github.com/replicatedhq/replicated-sdk/pkg/store" "github.com/replicatedhq/replicated-sdk/pkg/upstream" upstreamtypes "github.com/replicatedhq/replicated-sdk/pkg/upstream/types" @@ -132,5 +133,14 @@ func bootstrap(params APIServerParams) error { return errors.Wrap(err, "failed to start heartbeat") } + // this is at the end of the bootstrap function so that it doesn't re-run on retry + if !util.IsAirgap() { + go func() { + if err := util.WarnOnOutdatedSDKVersion(); err != nil { + logger.Infof("Failed to check if running an outdated sdk version: %v", err) + } + }() + } + return nil } diff --git a/pkg/util/replicated.go b/pkg/util/replicated.go index a7dbe327..0e0c18c6 100644 --- a/pkg/util/replicated.go +++ b/pkg/util/replicated.go @@ -2,9 +2,18 @@ package util import ( "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "text/tabwriter" + "github.com/blang/semver" "github.com/pkg/errors" + "github.com/replicatedhq/replicated-sdk/pkg/buildversion" "github.com/replicatedhq/replicated-sdk/pkg/k8sutil" + "github.com/replicatedhq/replicated-sdk/pkg/logger" "github.com/segmentio/ksuid" corev1 "k8s.io/api/core/v1" kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" @@ -59,3 +68,72 @@ func GenerateIDs(namespace string) (string, string, error) { return replicatedID, appID, nil } + +func WarnOnOutdatedSDKVersion() error { + currSemver, err := semver.ParseTolerant(buildversion.Version()) + if err != nil { + logger.Infof("Not checking for outdated sdk version because the current version (%s) is not a valid semver", buildversion.Version()) + return nil + } + + latestVersion, err := getLatestSDKVersion() + if err != nil { + return errors.Wrap(err, "failed to get latest sdk version") + } + + latestSemver, err := semver.ParseTolerant(latestVersion) + if err != nil { + return errors.Wrap(err, "failed to parse cli semver") + } + + if currSemver.LT(latestSemver) { + minWidth := 0 + tabWidth := 0 + padding := 0 + padChar := byte('!') + + w := tabwriter.NewWriter(os.Stderr, minWidth, tabWidth, padding, padChar, tabwriter.TabIndent) + defer w.Flush() + + fmtColumns := "%s\t%s\t%s\n" + fmt.Fprintf(w, fmtColumns, "", "", "") + fmt.Fprintf(w, fmtColumns, "!", "", "!") + fmt.Fprintf(w, fmtColumns, "!", fmt.Sprintf(" You are running an outdated version of the Replicated SDK (%s). The latest version is %s. ", buildversion.Version(), latestVersion), "!") + fmt.Fprintf(w, fmtColumns, "!", "", "!") + fmt.Fprintf(w, fmtColumns, "", "", "") + } + + return nil +} + +func getLatestSDKVersion() (string, error) { + resp, err := http.Get("https://api.github.com/repos/replicatedhq/replicated-sdk/tags") + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to retrieve tags: %s", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", errors.Wrap(err, "failed to read response body") + } + + type GitHubTag struct { + Name string `json:"name"` + } + var tags []GitHubTag + err = json.Unmarshal(body, &tags) + if err != nil { + return "", errors.Wrap(err, "failed to unmarshal response body") + } + + if len(tags) == 0 { + return "", fmt.Errorf("no tags found") + } + + return tags[0].Name, nil +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 5cdf5cc8..70f7fd05 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,11 +1,9 @@ package util -import "fmt" - type ActionableError struct { Message string } func (e ActionableError) Error() string { - return fmt.Sprintf("%s", e.Message) + return e.Message }