@ -22,7 +22,6 @@ import (
"io/ioutil"
"io/ioutil"
"net/http"
"net/http"
"os"
"os"
"os/exec"
"path/filepath"
"path/filepath"
"runtime"
"runtime"
"strings"
"strings"
@ -68,61 +67,89 @@ const (
minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime . GOOS + "-" + runtime . GOARCH + "/"
minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime . GOOS + "-" + runtime . GOARCH + "/"
)
)
func getCurrentReleaseTime ( minioVersion , minioBinaryPath string ) ( releaseTime time . Time , err error ) {
var (
if releaseTime , err = time . Parse ( time . RFC3339 , minioVersion ) ; err == nil {
// Newer official download info URLs appear earlier below.
return releaseTime , err
minioReleaseInfoURLs = [ ] string {
minioReleaseURL + "minio.sha256sum" ,
minioReleaseURL + "minio.shasum" ,
}
}
)
if ! filepath . IsAbs ( minioBinaryPath ) {
// minioVersionToReleaseTime - parses a standard official release
// Make sure to look for the absolute path of the binary.
// Minio version string.
minioBinaryPath , err = exec . LookPath ( minioBinaryPath )
//
if err != nil {
// An official binary's version string is the release time formatted
return releaseTime , err
// with RFC3339 (in UTC) - e.g. `2017-09-29T19:16:56Z`
}
func minioVersionToReleaseTime ( version string ) ( releaseTime time . Time , err error ) {
return time . Parse ( time . RFC3339 , version )
}
// releaseTimeToReleaseTag - converts a time to a string formatted as
// an official Minio release tag.
//
// An official minio release tag looks like:
// `RELEASE.2017-09-29T19-16-56Z`
func releaseTimeToReleaseTag ( releaseTime time . Time ) string {
return "RELEASE." + releaseTime . Format ( minioReleaseTagTimeLayout )
}
// releaseTagToReleaseTime - reverse of `releaseTimeToReleaseTag()`
func releaseTagToReleaseTime ( releaseTag string ) ( releaseTime time . Time , err error ) {
tagTimePart := strings . TrimPrefix ( releaseTag , "RELEASE." )
if tagTimePart == releaseTag {
return releaseTime , fmt . Errorf ( "%s is not a valid release tag" , releaseTag )
}
return time . Parse ( minioReleaseTagTimeLayout , tagTimePart )
}
// getModTime - get the file modification time of `path`
func getModTime ( path string ) ( t time . Time , err error ) {
// Convert to absolute path
absPath , err := filepath . Abs ( path )
if err != nil {
return t , fmt . Errorf ( "Unable to get absolute path of %s. %s" , path , err )
}
}
// Looks like version is minio non-standard, we use minio binary's ModTime as release time.
// Get Stat info
fi , err := osStat ( minioBinaryPath )
fi , err := osStat ( abs Path)
if err != nil {
if err != nil {
err = fmt . Errorf ( "Unable to get ModTime of %s. %s" , minioBinaryPath , err )
return t , fmt . Errorf ( "Unable to get ModTime of %s. %s" , absPath , err )
} else {
releaseTime = fi . ModTime ( ) . UTC ( )
}
}
return releaseTime , err
// Return the ModTime
return fi . ModTime ( ) . UTC ( ) , nil
}
}
// GetCurrentReleaseTime - returns this process's release time. If it is official minio version,
// GetCurrentReleaseTime - returns this process's release time. If it
// parsed version is returned else minio binary's mod time is returned.
// is official minio version, parsed version is returned else minio
// binary's mod time is returned.
func GetCurrentReleaseTime ( ) ( releaseTime time . Time , err error ) {
func GetCurrentReleaseTime ( ) ( releaseTime time . Time , err error ) {
return getCurrentReleaseTime ( Version , os . Args [ 0 ] )
if releaseTime , err = minioVersionToReleaseTime ( Version ) ; err == nil {
return releaseTime , err
}
// Looks like version is minio non-standard, we use minio
// binary's ModTime as release time:
return getModTime ( os . Args [ 0 ] )
}
}
// Check if we are indeed inside docker.
// IsDocker - returns if the environment minio is running in docker or
// not. The check is a simple file existence check.
//
// https://github.com/moby/moby/blob/master/daemon/initlayer/setup_unix.go#L25
// https://github.com/moby/moby/blob/master/daemon/initlayer/setup_unix.go#L25
//
//
// "/.dockerenv": "file",
// "/.dockerenv": "file",
//
//
func isDocker ( dockerEnvFile string ) ( ok bool , err error ) {
func IsDocker ( ) bool {
_ , err = os . Stat ( dockerEnvFile )
_ , err := osStat ( "/.dockerenv" )
if err != nil {
if os . IsNotExist ( err ) {
if os . IsNotExist ( err ) {
return false
err = nil
}
return false , err
}
}
return true , nil
}
// IsDocker - returns if the environment minio is running
// Log error, as we will not propagate it to caller
// is docker or not.
func IsDocker ( ) bool {
found , err := isDocker ( "/.dockerenv" )
// We don't need to fail for this check, log
// an error and return false.
errorIf ( err , "Error in docker check." )
errorIf ( err , "Error in docker check." )
return found
return err == nil
}
}
// IsDCOS returns true if minio is running in DCOS.
// IsDCOS returns true if minio is running in DCOS.
@ -147,9 +174,12 @@ func IsKubernetes() bool {
func getHelmVersion ( helmInfoFilePath string ) string {
func getHelmVersion ( helmInfoFilePath string ) string {
// Read the file exists.
// Read the file exists.
helmInfoFile , err := os . Open ( helmInfoFilePath )
helmInfoFile , err := os . Open ( helmInfoFilePath )
// Log errors and return "" as Minio can be deployed without Helm charts as well.
if err != nil {
if err != nil && ! os . IsNotExist ( err ) {
// Log errors and return "" as Minio can be deployed
errorIf ( err , "Unable to read %s" , helmInfoFilePath )
// without Helm charts as well.
if ! os . IsNotExist ( err ) {
errorIf ( err , "Unable to read %s" , helmInfoFilePath )
}
return ""
return ""
}
}
@ -165,46 +195,55 @@ func getHelmVersion(helmInfoFilePath string) string {
return ""
return ""
}
}
func isSourceBuild ( minioVersion string ) bool {
// IsSourceBuild - returns if this binary is a non-official build from
_ , err := time . Parse ( time . RFC3339 , minioVersion )
// source code.
return err != nil
}
// IsSourceBuild - returns if this binary is made from source or not.
func IsSourceBuild ( ) bool {
func IsSourceBuild ( ) bool {
return isSourceBuild ( Version )
_ , err := minioVersionToReleaseTime ( Version )
return err != nil
}
}
// DO NOT CHANGE USER AGENT STYLE.
// DO NOT CHANGE USER AGENT STYLE.
// The style should be
// The style should be
//
//
// Minio (<OS>; <ARCH>[; dcos][; kubernetes][; docker][; source]) Minio/<VERSION> Minio/<RELEASE-TAG> Minio/<COMMIT-ID> [Minio/univers-<PACKAGE_NAME >]
// Minio (<OS>; <ARCH>[; <MODE>][; dcos][; kubernetes][; docker][; source]) Minio/<VERSION> Minio/<RELEASE-TAG> Minio/<COMMIT-ID> [Minio/universe-<PACKAGE-NAME>] [Minio/helm-<HELM-VERSION >]
//
//
// For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues.
// Any change here should be discussed by opening an issue at
// https://github.com/minio/minio/issues.
func getUserAgent ( mode string ) string {
func getUserAgent ( mode string ) string {
userAgent := "Minio (" + runtime . GOOS + "; " + runtime . GOARCH
userAgentParts := [ ] string { }
// Helper function to concisely append a pair of strings to a
// the user-agent slice.
uaAppend := func ( p , q string ) {
userAgentParts = append ( userAgentParts , p , q )
}
uaAppend ( "Minio (" , runtime . GOOS )
uaAppend ( "; " , runtime . GOARCH )
if mode != "" {
if mode != "" {
userAgent += "; " + mode
uaAppend ( "; " , mode )
}
}
if IsDCOS ( ) {
if IsDCOS ( ) {
userAgent += "; dcos"
uaAppend ( "; " , "dcos" )
}
}
if IsKubernetes ( ) {
if IsKubernetes ( ) {
userAgent += "; kubernetes"
uaAppend ( "; " , "kubernetes" )
}
}
if IsDocker ( ) {
if IsDocker ( ) {
userAgent += "; docker"
uaAppend ( "; " , "docker" )
}
}
if IsSourceBuild ( ) {
if IsSourceBuild ( ) {
userAgent += "; source"
uaAppend ( "; " , "source" )
}
}
userAgent += ") Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID
uaAppend ( ") Minio/" , Version )
uaAppend ( " Minio/" , ReleaseTag )
uaAppend ( " Minio/" , CommitID )
if IsDCOS ( ) {
if IsDCOS ( ) {
universePkgVersion := os . Getenv ( "MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION" )
universePkgVersion := os . Getenv ( "MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION" )
// On DC/OS environment try to the get universe package version.
// On DC/OS environment try to the get universe package version.
if universePkgVersion != "" {
if universePkgVersion != "" {
userAgent += " Minio/" + "universe-" + universePkgVersion
uaAppend ( " Minio/universe-" , universePkgVersion )
}
}
}
}
@ -212,17 +251,17 @@ func getUserAgent(mode string) string {
// In Kubernetes environment, try to fetch the helm package version
// In Kubernetes environment, try to fetch the helm package version
helmChartVersion := getHelmVersion ( "/podinfo/labels" )
helmChartVersion := getHelmVersion ( "/podinfo/labels" )
if helmChartVersion != "" {
if helmChartVersion != "" {
userAgent += " Minio/" + "helm-" + helmChartVersion
uaAppend ( " Minio/helm-" , helmChartVersion )
}
}
}
}
return userAgent
return strings . Join ( userAgentParts , "" )
}
}
func downloadReleaseData ( releaseChecksumURL string , timeout time . Duration , mode string ) ( data string , err error ) {
func downloadReleaseURL ( releaseChecksumURL string , timeout time . Duration , mode string ) ( content string , err error ) {
req , err := http . NewRequest ( "GET" , releaseChecksumURL , nil )
req , err := http . NewRequest ( "GET" , releaseChecksumURL , nil )
if err != nil {
if err != nil {
return data , err
return content , err
}
}
req . Header . Set ( "User-Agent" , getUserAgent ( mode ) )
req . Header . Set ( "User-Agent" , getUserAgent ( mode ) )
@ -236,34 +275,43 @@ func downloadReleaseData(releaseChecksumURL string, timeout time.Duration, mode
resp , err := client . Do ( req )
resp , err := client . Do ( req )
if err != nil {
if err != nil {
return data , err
return content , err
}
}
if resp == nil {
if resp == nil {
return data , fmt . Errorf ( "No response from server to download URL %s" , releaseChecksumURL )
return content , fmt . Errorf ( "No response from server to download URL %s" , releaseChecksumURL )
}
}
defer resp . Body . Close ( )
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
if resp . StatusCode != http . StatusOK {
return data , fmt . Errorf ( "Error downloading URL %s. Response: %v" , releaseChecksumURL , resp . Status )
return content , fmt . Errorf ( "Error downloading URL %s. Response: %v" , releaseChecksumURL , resp . Status )
}
}
data Bytes, err := ioutil . ReadAll ( resp . Body )
content Bytes, err := ioutil . ReadAll ( resp . Body )
if err != nil {
if err != nil {
return data , fmt . Errorf ( "Error reading response. %s" , err )
return content , fmt . Errorf ( "Error reading response. %s" , err )
}
}
data = string ( dataBytes )
return string ( contentBytes ) , err
return data , err
}
}
// DownloadReleaseData - downloads release data from minio official server.
// DownloadReleaseData - downloads release data from minio official server.
func DownloadReleaseData ( timeout time . Duration , mode string ) ( data string , err error ) {
func DownloadReleaseData ( timeout time . Duration , mode string ) ( data string , err error ) {
data , err = downloadReleaseData ( minioReleaseURL + "minio.shasum" , timeout , mode )
for _ , url := range minioReleaseInfoURLs {
if err == nil {
data , err = downloadReleaseURL ( url , timeout , mode )
return data , nil
if err == nil {
return data , err
}
}
}
return downloadReleaseData ( minioReleaseURL + "minio.sha256sum" , timeout , mode )
return data , fmt . Errorf ( "Failed to fetch release URL - last error: %s" , err )
}
}
// parseReleaseData - parses release info file content fetched from
// official minio download server.
//
// The expected format is a single line with two words like:
//
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z
//
// The second word must be `minio.` appended to a standard release tag.
func parseReleaseData ( data string ) ( releaseTime time . Time , err error ) {
func parseReleaseData ( data string ) ( releaseTime time . Time , err error ) {
fields := strings . Fields ( data )
fields := strings . Fields ( data )
if len ( fields ) != 2 {
if len ( fields ) != 2 {
@ -272,19 +320,20 @@ func parseReleaseData(data string) (releaseTime time.Time, err error) {
}
}
releaseInfo := fields [ 1 ]
releaseInfo := fields [ 1 ]
if fields = strings . Split ( releaseInfo , "." ) ; len ( fields ) != 3 {
fields = strings . SplitN ( releaseInfo , "." , 2 )
if len ( fields ) != 2 {
err = fmt . Errorf ( "Unknown release information `%s`" , releaseInfo )
err = fmt . Errorf ( "Unknown release information `%s`" , releaseInfo )
return releaseTime , err
return releaseTime , err
}
}
if fields [ 0 ] != "minio" {
if ! ( fields [ 0 ] == "minio" && fields [ 1 ] == "RELEASE" ) {
err = fmt . Errorf ( "Unknown release `%s`" , releaseInfo )
err = fmt . Errorf ( "Unknown release '%s'" , releaseInfo )
return releaseTime , err
return releaseTime , err
}
}
releaseTime , err = time . Parse ( minioReleaseTagTimeLayout , fields [ 2 ] )
releaseTime , err = releaseTagToReleaseTime ( fields [ 1 ] )
if err != nil {
if err != nil {
err = fmt . Errorf ( "Unknown release time format. %s" , err )
err = fmt . Errorf ( "Unknown release tag format. %s" , err )
}
}
return releaseTime , err
return releaseTime , err
@ -307,7 +356,7 @@ const (
mesosDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-dc-os"
mesosDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-dc-os"
)
)
func getDownloadURL ( buildDate time . Time ) ( downloadURL string ) {
func getDownloadURL ( releaseTag string ) ( downloadURL string ) {
// Check if we are in DCOS environment, return
// Check if we are in DCOS environment, return
// deployment guide for update procedures.
// deployment guide for update procedures.
if IsDCOS ( ) {
if IsDCOS ( ) {
@ -323,11 +372,10 @@ func getDownloadURL(buildDate time.Time) (downloadURL string) {
// Check if we are docker environment, return docker update command
// Check if we are docker environment, return docker update command
if IsDocker ( ) {
if IsDocker ( ) {
// Construct release tag name.
// Construct release tag name.
rTag := "RELEASE." + buildDate . Format ( minioReleaseTagTimeLayout )
return fmt . Sprintf ( "docker pull minio/minio:%s" , releaseTag )
return fmt . Sprintf ( "docker pull minio/minio:%s" , rTag )
}
}
// For binary only installations, then we just show binary download link .
// For binary only installations, we return link to the latest binary .
if runtime . GOOS == "windows" {
if runtime . GOOS == "windows" {
return minioReleaseURL + "minio.exe"
return minioReleaseURL + "minio.exe"
}
}
@ -335,9 +383,7 @@ func getDownloadURL(buildDate time.Time) (downloadURL string) {
return minioReleaseURL + "minio"
return minioReleaseURL + "minio"
}
}
func getUpdateInfo ( timeout time . Duration , mode string ) ( older time . Duration ,
func getUpdateInfo ( timeout time . Duration , mode string ) ( older time . Duration , downloadURL string , err error ) {
downloadURL string , err error ) {
var currentReleaseTime , latestReleaseTime time . Time
var currentReleaseTime , latestReleaseTime time . Time
currentReleaseTime , err = GetCurrentReleaseTime ( )
currentReleaseTime , err = GetCurrentReleaseTime ( )
if err != nil {
if err != nil {
@ -351,7 +397,7 @@ func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration,
if latestReleaseTime . After ( currentReleaseTime ) {
if latestReleaseTime . After ( currentReleaseTime ) {
older = latestReleaseTime . Sub ( currentReleaseTime )
older = latestReleaseTime . Sub ( currentReleaseTime )
downloadURL = getDownloadURL ( latestReleaseTime )
downloadURL = getDownloadURL ( releaseTimeToReleaseTag ( latestReleaseTime ) )
}
}
return older , downloadURL , nil
return older , downloadURL , nil