...

Source file src/github.com/linkerd/linkerd2/pkg/version/channels.go

Documentation: github.com/linkerd/linkerd2/pkg/version

     1  package version
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  
    11  	"github.com/linkerd/linkerd2/pkg/util"
    12  )
    13  
    14  // Channels provides an interface to interact with a set of release channels.
    15  // This module is also responsible for online retrieval of the latest release
    16  // versions.
    17  type Channels struct {
    18  	array []channelVersion
    19  }
    20  
    21  var (
    22  	// CheckURL provides an online endpoint for Linkerd's version checks
    23  	CheckURL = "https://versioncheck.linkerd.io/version.json"
    24  )
    25  
    26  // NewChannels is used primarily for testing, it returns a Channels struct that
    27  // mimic a GetLatestVersions response.
    28  func NewChannels(channel string) (Channels, error) {
    29  	cv, err := parseChannelVersion(channel)
    30  	if err != nil {
    31  		return Channels{}, err
    32  	}
    33  
    34  	return Channels{
    35  		array: []channelVersion{cv},
    36  	}, nil
    37  }
    38  
    39  // Match validates whether the given version string:
    40  // 1) is a well-formed channel-version string, for example: "edge-19.1.2"
    41  // 2) references a known channel
    42  // 3) matches the version in the known channel
    43  func (c Channels) Match(actualVersion string) error {
    44  	if actualVersion == "" {
    45  		return errors.New("actual version is empty")
    46  	}
    47  
    48  	if c.Empty() {
    49  		return errors.New("unable to determine version channel")
    50  	}
    51  
    52  	actual, err := parseChannelVersion(actualVersion)
    53  	if err != nil {
    54  		return fmt.Errorf("failed to parse actual version: %w", err)
    55  	}
    56  
    57  	for _, cv := range c.array {
    58  		if cv.updateChannel() == actual.updateChannel() {
    59  			return match(cv.String(), actualVersion)
    60  		}
    61  	}
    62  
    63  	return fmt.Errorf("unsupported version channel: %s", actualVersion)
    64  }
    65  
    66  // Determines whether there are any release channels stored in the struct.
    67  func (c Channels) Empty() bool {
    68  	return len(c.array) == 0
    69  }
    70  
    71  // GetLatestVersions performs an online request to check for the latest Linkerd
    72  // release channels.
    73  func GetLatestVersions(ctx context.Context, uuid string, source string) (Channels, error) {
    74  	url := fmt.Sprintf("%s?version=%s&uuid=%s&source=%s", CheckURL, Version, uuid, source)
    75  	return getLatestVersions(ctx, http.DefaultClient, url)
    76  }
    77  
    78  func getLatestVersions(ctx context.Context, client *http.Client, url string) (Channels, error) {
    79  	req, err := http.NewRequest("GET", url, nil)
    80  	if err != nil {
    81  		return Channels{}, err
    82  	}
    83  
    84  	rsp, err := client.Do(req.WithContext(ctx))
    85  	if err != nil {
    86  		var dnsError *net.DNSError
    87  		if errors.As(err, &dnsError) {
    88  			return Channels{}, fmt.Errorf("failed to resolve version check server: %s", url)
    89  		}
    90  		return Channels{}, err
    91  	}
    92  	defer rsp.Body.Close()
    93  
    94  	if rsp.StatusCode != 200 {
    95  		return Channels{}, fmt.Errorf("unexpected versioncheck response: %s", rsp.Status)
    96  	}
    97  
    98  	bytes, err := util.ReadAllLimit(rsp.Body, util.MB)
    99  	if err != nil {
   100  		return Channels{}, err
   101  	}
   102  
   103  	var versionRsp map[string]string
   104  	err = json.Unmarshal(bytes, &versionRsp)
   105  	if err != nil {
   106  		return Channels{}, err
   107  	}
   108  
   109  	channels := Channels{}
   110  	for c, v := range versionRsp {
   111  		cv, err := parseChannelVersion(v)
   112  		if err != nil {
   113  			return Channels{}, fmt.Errorf("unexpected versioncheck response: %w", err)
   114  		}
   115  
   116  		if c != cv.updateChannel() {
   117  			return Channels{}, fmt.Errorf("unexpected versioncheck response: channel in %s does not match %s", cv, c)
   118  		}
   119  
   120  		channels.array = append(channels.array, cv)
   121  	}
   122  
   123  	return channels, nil
   124  }
   125  

View as plain text