linkerd2/pkg/version/channels.go

113 lines
2.8 KiB
Go

package version
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
// Channels provides an interface to interact with a set of release channels.
// This module is also responsible for online retrieval of the latest release
// versions.
type Channels struct {
array []channelVersion
}
const (
versionCheckURL = "https://versioncheck.linkerd.io/version.json?version=%s&uuid=%s&source=%s"
)
// NewChannels is used primarily for testing, it returns a Channels struct that
// mimic a GetLatestVersions response.
func NewChannels(channel string) (Channels, error) {
cv, err := parseChannelVersion(channel)
if err != nil {
return Channels{}, err
}
return Channels{
array: []channelVersion{cv},
}, nil
}
// Match validates whether the given version string:
// 1) is a well-formed channel-version string, for example: "edge-19.1.2"
// 2) references a known channel
// 3) matches the version in the known channel
func (c Channels) Match(actualVersion string) error {
if actualVersion == "" {
return errors.New("actual version is empty")
}
actual, err := parseChannelVersion(actualVersion)
if err != nil {
return fmt.Errorf("failed to parse actual version: %s", err)
}
for _, cv := range c.array {
if cv.channel == actual.channel {
return match(cv.String(), actualVersion)
}
}
return fmt.Errorf("unsupported version channel: %s", actualVersion)
}
// GetLatestVersions performs an online request to check for the latest Linkerd
// release channels.
func GetLatestVersions(uuid string, source string) (Channels, error) {
url := fmt.Sprintf(versionCheckURL, Version, uuid, source)
return getLatestVersions(http.DefaultClient, url, uuid, source)
}
func getLatestVersions(client *http.Client, url string, uuid string, source string) (Channels, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return Channels{}, err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rsp, err := client.Do(req.WithContext(ctx))
if err != nil {
return Channels{}, err
}
defer rsp.Body.Close()
if rsp.StatusCode != 200 {
return Channels{}, fmt.Errorf("unexpected versioncheck response: %s", rsp.Status)
}
bytes, err := ioutil.ReadAll(rsp.Body)
if err != nil {
return Channels{}, err
}
var versionRsp map[string]string
err = json.Unmarshal(bytes, &versionRsp)
if err != nil {
return Channels{}, err
}
channels := Channels{}
for c, v := range versionRsp {
cv, err := parseChannelVersion(v)
if err != nil {
return Channels{}, fmt.Errorf("unexpected versioncheck response: %s", err)
}
if c != cv.channel {
return Channels{}, fmt.Errorf("unexpected versioncheck response: channel in %s does not match %s", cv, c)
}
channels.array = append(channels.array, cv)
}
return channels, nil
}