github.com/coreos/mantle@v0.13.0/sdk/version.go (about)

     1  // Copyright 2015 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sdk
    16  
    17  import (
    18  	"bufio"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"strings"
    25  	"time"
    26  )
    27  
    28  const (
    29  	coreosId = "{E96281A6-D1AF-4BDE-9A0A-97B76E56DC57}"
    30  )
    31  
    32  type Versions struct {
    33  	Version    string
    34  	VersionID  string
    35  	BuildID    string
    36  	SDKVersion string
    37  }
    38  
    39  func unquote(s string) string {
    40  	if len(s) < 2 {
    41  		return s
    42  	}
    43  	for _, q := range []byte{'\'', '"'} {
    44  		if s[0] == q && s[len(s)-1] == q {
    45  			return s[1 : len(s)-1]
    46  		}
    47  	}
    48  	return s
    49  }
    50  
    51  func parseVersions(f *os.File, prefix string) (ver Versions, err error) {
    52  	scanner := bufio.NewScanner(f)
    53  	for scanner.Scan() {
    54  		line := strings.SplitN(scanner.Text(), "=", 2)
    55  		if len(line) != 2 {
    56  			continue
    57  		}
    58  		switch line[0] {
    59  		case prefix + "VERSION":
    60  			ver.Version = unquote(line[1])
    61  		case prefix + "VERSION_ID":
    62  			ver.VersionID = unquote(line[1])
    63  		case prefix + "BUILD_ID":
    64  			ver.BuildID = unquote(line[1])
    65  		case prefix + "SDK_VERSION":
    66  			ver.SDKVersion = unquote(line[1])
    67  		}
    68  	}
    69  	if err = scanner.Err(); err != nil {
    70  		return
    71  	}
    72  
    73  	if ver.VersionID == "" {
    74  		err = fmt.Errorf("Missing %sVERSION_ID in %s", prefix, f.Name())
    75  	} else if !strings.HasPrefix(ver.Version, ver.VersionID) {
    76  		err = fmt.Errorf("Invalid %sVERSION in %s", prefix, f.Name())
    77  	}
    78  
    79  	return
    80  }
    81  
    82  func OSRelease(root string) (ver Versions, err error) {
    83  	f, err := os.Open(filepath.Join(root, "usr/lib/os-release"))
    84  	if err != nil {
    85  		return
    86  	}
    87  	defer f.Close()
    88  
    89  	return parseVersions(f, "")
    90  }
    91  
    92  func VersionsFromDir(dir string) (ver Versions, err error) {
    93  	f, err := os.Open(filepath.Join(dir, "version.txt"))
    94  	if err != nil {
    95  		return
    96  	}
    97  	defer f.Close()
    98  
    99  	ver, err = parseVersions(f, "COREOS_")
   100  	if ver.SDKVersion == "" {
   101  		err = fmt.Errorf("Missing COREOS_SDK_VERSION in %s", f.Name())
   102  	}
   103  
   104  	return
   105  }
   106  
   107  func VersionsFromManifest() (Versions, error) {
   108  	return VersionsFromDir(filepath.Join(RepoRoot(), ".repo", "manifests"))
   109  }
   110  
   111  func versionsFromRemoteRepoMaybeVerify(url, branch string, verify bool) (ver Versions, err error) {
   112  	// git clone cannot be given a full ref path, instead it explicitly checks
   113  	// under both refs/heads/<name> and refs/tags/<name>, in that order.
   114  	if strings.HasPrefix(branch, "refs/") {
   115  		if strings.HasPrefix(branch, "refs/heads/") {
   116  			branch = strings.TrimPrefix(branch, "refs/heads/")
   117  		} else if strings.HasPrefix(branch, "refs/tags/") {
   118  			branch = strings.TrimPrefix(branch, "refs/tags/")
   119  		} else {
   120  			err = fmt.Errorf("SDK version cannot be detected for %q", branch)
   121  			return
   122  		}
   123  	}
   124  
   125  	tmp, err := ioutil.TempDir("", "")
   126  	if err != nil {
   127  		return
   128  	}
   129  	defer os.RemoveAll(tmp)
   130  
   131  	clone := exec.Command("git", "clone", "-q", "--depth=1", "--single-branch", "-b", branch, url, tmp)
   132  	clone.Stderr = os.Stderr
   133  	if err = clone.Run(); err != nil {
   134  		err = fmt.Errorf("'git clone %s' failed: %q", branch, err)
   135  		return
   136  	}
   137  
   138  	if verify {
   139  		tag := exec.Command("git", "-C", tmp, "tag", "-v", branch)
   140  		tag.Stderr = os.Stderr
   141  		if err = tag.Run(); err != nil {
   142  			err = fmt.Errorf("'git tag --verify %s' failed: %q", branch, err)
   143  			return
   144  		}
   145  	}
   146  
   147  	return VersionsFromDir(tmp)
   148  }
   149  
   150  func VersionsFromRemoteRepo(url, branch string) (ver Versions, err error) {
   151  	return versionsFromRemoteRepoMaybeVerify(url, branch, false)
   152  }
   153  
   154  func VersionsFromSignedRemoteRepo(url, branch string) (ver Versions, err error) {
   155  	return versionsFromRemoteRepoMaybeVerify(url, branch, true)
   156  }
   157  
   158  func GetDefaultAppId() string {
   159  	// This is a function in case the id needs to be configurable.
   160  	return coreosId
   161  }
   162  
   163  const (
   164  	CoreOSEpoch = 1372636800
   165  )
   166  
   167  // GetCoreOSAge returns the number of days since the CoreOS epoch.
   168  func GetCoreOSAge() int64 {
   169  	return int64(time.Since(time.Unix(CoreOSEpoch, 0)) / (86400 * time.Second))
   170  }