2019-04-03 20:13:39 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package version
|
|
|
|
|
|
|
|
import (
|
2019-10-21 11:50:59 +01:00
|
|
|
"bytes"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha256"
|
2019-10-16 09:16:59 +01:00
|
|
|
"encoding/hex"
|
2019-04-03 20:13:39 +01:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2019-10-21 11:50:59 +01:00
|
|
|
"math/big"
|
|
|
|
"reflect"
|
2019-04-03 20:13:39 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
"github.com/blang/semver"
|
2019-07-02 16:28:06 +01:00
|
|
|
"github.com/zeebo/errs"
|
2019-04-10 07:04:24 +01:00
|
|
|
|
|
|
|
"storj.io/storj/pkg/pb"
|
2019-10-21 11:50:59 +01:00
|
|
|
"storj.io/storj/pkg/storj"
|
2019-04-03 20:13:39 +01:00
|
|
|
)
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
const quote = byte('"')
|
2019-10-16 09:16:59 +01:00
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
var (
|
2019-10-16 09:16:59 +01:00
|
|
|
// VerError is the error class for version-related errors.
|
|
|
|
VerError = errs.Class("version error")
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
// the following fields are set by linker flags. if any of them
|
|
|
|
// are set and fail to parse, the program will fail to start
|
|
|
|
buildTimestamp string // unix seconds since epoch
|
|
|
|
buildCommitHash string
|
|
|
|
buildVersion string // semantic version format
|
|
|
|
buildRelease string // true/false
|
|
|
|
|
|
|
|
// Build is a struct containing all relevant build information associated with the binary
|
|
|
|
Build Info
|
|
|
|
)
|
|
|
|
|
|
|
|
// Info is the versioning information for a binary
|
|
|
|
type Info struct {
|
2019-04-10 07:38:26 +01:00
|
|
|
// sync/atomic cache
|
|
|
|
commitHashCRC uint32
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
|
|
|
CommitHash string `json:"commitHash,omitempty"`
|
|
|
|
Version SemVer `json:"version"`
|
|
|
|
Release bool `json:"release,omitempty"`
|
|
|
|
}
|
|
|
|
|
2019-10-24 21:24:28 +01:00
|
|
|
// SemVer represents a semantic version.
|
2019-10-21 11:50:59 +01:00
|
|
|
// TODO: replace with semver.Version
|
2019-04-03 20:13:39 +01:00
|
|
|
type SemVer struct {
|
2019-10-21 11:50:59 +01:00
|
|
|
semver.Version
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
|
2019-10-24 21:24:28 +01:00
|
|
|
// OldSemVer represents a semantic version.
|
|
|
|
// NB: this will be deprecated in favor of `SemVer`; these structs marshal to JSON differently.
|
|
|
|
type OldSemVer struct {
|
|
|
|
Major int64 `json:"major"`
|
|
|
|
Minor int64 `json:"minor"`
|
|
|
|
Patch int64 `json:"patch"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllowedVersions provides the Minimum SemVer per Service.
|
2019-10-16 09:16:59 +01:00
|
|
|
// TODO: I don't think this name is representative of what this struct now holds.
|
2019-04-03 20:13:39 +01:00
|
|
|
type AllowedVersions struct {
|
2019-10-24 21:24:28 +01:00
|
|
|
Satellite OldSemVer
|
|
|
|
Storagenode OldSemVer
|
|
|
|
Uplink OldSemVer
|
|
|
|
Gateway OldSemVer
|
|
|
|
Identity OldSemVer
|
2019-09-11 15:01:36 +01:00
|
|
|
|
|
|
|
Processes Processes `json:"processes"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Processes describes versions for each binary.
|
2019-10-16 09:16:59 +01:00
|
|
|
// TODO: this name is inconsistent with the versioncontrol server pkg's analogue, `Versions`.
|
2019-09-11 15:01:36 +01:00
|
|
|
type Processes struct {
|
2019-10-31 12:27:53 +00:00
|
|
|
Satellite Process `json:"satellite"`
|
|
|
|
Storagenode Process `json:"storagenode"`
|
|
|
|
StoragenodeUpdater Process `json:"storagenode-updater"`
|
|
|
|
Uplink Process `json:"uplink"`
|
|
|
|
Gateway Process `json:"gateway"`
|
|
|
|
Identity Process `json:"identity"`
|
2019-09-11 15:01:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process versions for specific binary.
|
|
|
|
type Process struct {
|
|
|
|
Minimum Version `json:"minimum"`
|
|
|
|
Suggested Version `json:"suggested"`
|
2019-10-16 09:16:59 +01:00
|
|
|
Rollout Rollout `json:"rollout"`
|
2019-09-11 15:01:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Version represents version and download URL for binary.
|
|
|
|
type Version struct {
|
|
|
|
Version string `json:"version"`
|
|
|
|
URL string `json:"url"`
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
|
2019-10-16 09:16:59 +01:00
|
|
|
// Rollout represents the state of a version rollout.
|
|
|
|
type Rollout struct {
|
|
|
|
Seed RolloutBytes `json:"seed"`
|
|
|
|
Cursor RolloutBytes `json:"cursor"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// RolloutBytes implements json un/marshalling using hex de/encoding.
|
|
|
|
type RolloutBytes [32]byte
|
2019-04-03 20:13:39 +01:00
|
|
|
|
2019-10-16 09:16:59 +01:00
|
|
|
// MarshalJSON hex-encodes RolloutBytes and pre/appends JSON string literal quotes.
|
|
|
|
func (rb RolloutBytes) MarshalJSON() ([]byte, error) {
|
2019-10-21 11:50:59 +01:00
|
|
|
zeroRolloutBytes := RolloutBytes{}
|
|
|
|
if bytes.Equal(rb[:], zeroRolloutBytes[:]) {
|
|
|
|
return []byte{quote, quote}, nil
|
|
|
|
}
|
|
|
|
|
2019-10-16 09:16:59 +01:00
|
|
|
hexBytes := make([]byte, hex.EncodedLen(len(rb)))
|
|
|
|
hex.Encode(hexBytes, rb[:])
|
|
|
|
encoded := append([]byte{quote}, hexBytes...)
|
|
|
|
encoded = append(encoded, quote)
|
|
|
|
return encoded, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON drops the JSON string literal quotes and hex-decodes RolloutBytes .
|
|
|
|
func (rb *RolloutBytes) UnmarshalJSON(b []byte) error {
|
|
|
|
if _, err := hex.Decode(rb[:], b[1:len(b)-1]); err != nil {
|
|
|
|
return VerError.Wrap(err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-04-03 20:13:39 +01:00
|
|
|
|
|
|
|
// NewSemVer parses a given version and returns an instance of SemVer or
|
|
|
|
// an error if unable to parse the version.
|
2019-10-21 11:50:59 +01:00
|
|
|
func NewSemVer(v string) (SemVer, error) {
|
|
|
|
ver, err := semver.ParseTolerant(v)
|
2019-04-03 20:13:39 +01:00
|
|
|
if err != nil {
|
2019-10-16 09:16:59 +01:00
|
|
|
return SemVer{}, VerError.Wrap(err)
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
return SemVer{
|
|
|
|
Version: ver,
|
|
|
|
}, nil
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
|
2019-10-24 21:24:28 +01:00
|
|
|
// NewOldSemVer parses a given version and returns an instance of OldSemVer or
|
|
|
|
// an error if unable to parse the version.
|
|
|
|
func NewOldSemVer(v string) (OldSemVer, error) {
|
|
|
|
ver, err := NewSemVer(v)
|
|
|
|
if err != nil {
|
|
|
|
return OldSemVer{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return OldSemVer{
|
|
|
|
Major: int64(ver.Major),
|
|
|
|
Minor: int64(ver.Minor),
|
|
|
|
Patch: int64(ver.Patch),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-09-05 22:10:05 +01:00
|
|
|
// Compare compare two versions, return -1 if compared version is greater, 0 if equal and 1 if less.
|
|
|
|
func (sem *SemVer) Compare(version SemVer) int {
|
2019-10-21 11:50:59 +01:00
|
|
|
return sem.Version.Compare(version.Version)
|
2019-09-05 22:10:05 +01:00
|
|
|
}
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
// String converts the SemVer struct to a more easy to handle string
|
|
|
|
func (sem *SemVer) String() (version string) {
|
|
|
|
return fmt.Sprintf("v%d.%d.%d", sem.Major, sem.Minor, sem.Patch)
|
|
|
|
}
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
// IsZero checks if the semantic version is its zero value.
|
|
|
|
func (sem SemVer) IsZero() bool {
|
|
|
|
return reflect.ValueOf(sem).IsZero()
|
|
|
|
}
|
|
|
|
|
2019-10-25 12:24:23 +01:00
|
|
|
func (old OldSemVer) String() string {
|
|
|
|
return fmt.Sprintf("v%d.%d.%d", old.Major, old.Minor, old.Patch)
|
|
|
|
}
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
// SemVer converts a version struct into a semantic version struct.
|
|
|
|
func (ver *Version) SemVer() (SemVer, error) {
|
|
|
|
return NewSemVer(ver.Version)
|
|
|
|
}
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
// New creates Version_Info from a json byte array
|
|
|
|
func New(data []byte) (v Info, err error) {
|
|
|
|
err = json.Unmarshal(data, &v)
|
2019-10-16 09:16:59 +01:00
|
|
|
return v, VerError.Wrap(err)
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
// IsZero checks if the version struct is its zero value.
|
|
|
|
func (info Info) IsZero() bool {
|
|
|
|
return reflect.ValueOf(info).IsZero()
|
|
|
|
}
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
// Marshal converts the existing Version Info to any json byte array
|
2019-10-21 11:50:59 +01:00
|
|
|
func (info Info) Marshal() ([]byte, error) {
|
|
|
|
data, err := json.Marshal(info)
|
2019-10-16 09:16:59 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, VerError.Wrap(err)
|
|
|
|
}
|
|
|
|
return data, nil
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
|
2019-04-10 07:04:24 +01:00
|
|
|
// Proto converts an Info struct to a pb.NodeVersion
|
|
|
|
// TODO: shouldn't we just use pb.NodeVersion everywhere? gogoproto will let
|
|
|
|
// us make it match Info.
|
2019-10-21 11:50:59 +01:00
|
|
|
func (info Info) Proto() (*pb.NodeVersion, error) {
|
2019-04-10 07:04:24 +01:00
|
|
|
return &pb.NodeVersion{
|
2019-10-21 11:50:59 +01:00
|
|
|
Version: info.Version.String(),
|
|
|
|
CommitHash: info.CommitHash,
|
|
|
|
Timestamp: info.Timestamp,
|
|
|
|
Release: info.Release,
|
2019-04-10 07:04:24 +01:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-10-21 11:50:59 +01:00
|
|
|
// PercentageToCursor calculates the cursor value for the given percentage of nodes which should update.
|
|
|
|
func PercentageToCursor(pct int) RolloutBytes {
|
|
|
|
// NB: convert the max value to a number, multiply by the percentage, convert back.
|
|
|
|
var maxInt, maskInt big.Int
|
|
|
|
var maxBytes RolloutBytes
|
|
|
|
for i := 0; i < len(maxBytes); i++ {
|
|
|
|
maxBytes[i] = 255
|
|
|
|
}
|
|
|
|
maxInt.SetBytes(maxBytes[:])
|
|
|
|
maskInt.Div(maskInt.Mul(&maxInt, big.NewInt(int64(pct))), big.NewInt(100))
|
|
|
|
|
|
|
|
var cursor RolloutBytes
|
|
|
|
copy(cursor[:], maskInt.Bytes())
|
|
|
|
|
|
|
|
return cursor
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShouldUpdate checks if for the the given rollout state, a user with the given nodeID should update.
|
|
|
|
func ShouldUpdate(rollout Rollout, nodeID storj.NodeID) bool {
|
|
|
|
hash := hmac.New(sha256.New, rollout.Seed[:])
|
|
|
|
_, err := hash.Write(nodeID[:])
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return bytes.Compare(hash.Sum(nil), rollout.Cursor[:]) <= 0
|
|
|
|
}
|
|
|
|
|
2019-04-03 20:13:39 +01:00
|
|
|
func init() {
|
|
|
|
if buildVersion == "" && buildTimestamp == "" && buildCommitHash == "" && buildRelease == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
timestamp, err := strconv.ParseInt(buildTimestamp, 10, 64)
|
|
|
|
if err != nil {
|
2019-10-16 09:16:59 +01:00
|
|
|
panic(VerError.Wrap(err))
|
2019-04-03 20:13:39 +01:00
|
|
|
}
|
|
|
|
Build = Info{
|
|
|
|
Timestamp: time.Unix(timestamp, 0),
|
|
|
|
CommitHash: buildCommitHash,
|
|
|
|
Release: strings.ToLower(buildRelease) == "true",
|
|
|
|
}
|
|
|
|
|
|
|
|
sv, err := NewSemVer(buildVersion)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:28:06 +01:00
|
|
|
Build.Version = sv
|
2019-04-03 20:13:39 +01:00
|
|
|
|
|
|
|
if Build.Timestamp.Unix() == 0 || Build.CommitHash == "" {
|
|
|
|
Build.Release = false
|
|
|
|
}
|
|
|
|
}
|