diff --git a/cmd/main.go b/cmd/main.go index 31d2d8047..994e77d80 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -145,14 +145,13 @@ func checkMainSyntax(c *cli.Context) { func checkUpdate() { // Do not print update messages, if quiet flag is set. if !globalQuiet { - updateMsg, _, err := getReleaseUpdate(minioUpdateStableURL, 1*time.Second) + older, downloadURL, err := getUpdateInfo(1 * time.Second) if err != nil { - // Ignore any errors during getReleaseUpdate(), possibly - // because of network errors. + // Its OK to ignore any errors during getUpdateInfo() here. return } - if updateMsg.Update { - console.Println(updateMsg) + if older > time.Duration(0) { + console.Println(colorizeUpdateMessage(downloadURL, older)) } } } diff --git a/cmd/update-main.go b/cmd/update-main.go index b301c1520..4198506d3 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package cmd import ( "bytes" - "errors" + "fmt" "io/ioutil" "net/http" "os" @@ -36,7 +36,16 @@ var updateCmd = cli.Command{ Name: "update", Usage: "Check for a new software update.", Action: mainUpdate, - Flags: globalFlags, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "help, h", + Usage: "Show this help.", + }, + cli.BoolFlag{ + Name: "quiet", + Usage: "Disable any update messages.", + }, + }, CustomHelpTemplate: `Name: minio {{.Name}} - {{.Usage}} @@ -46,238 +55,215 @@ USAGE: FLAGS: {{range .Flags}}{{.}} {{end}} -EXAMPLES: - 1. Check for any new official release. - $ minio {{.Name}} -`, -} - -// update URL endpoints. -const ( - minioUpdateStableURL = "https://dl.minio.io/server/minio/release" -) +EXIT STATUS: + 0 - You are already running the most recent version. + 1 - New update is available. + -1 - Error in getting update information. -// updateMessage container to hold update messages. -type updateMessage struct { - Update bool `json:"update"` - Download string `json:"downloadURL"` - NewerThan time.Duration `json:"newerThan"` +VERSION: + ` + Version + `{{"\n"}}`, } -// String colorized update message. -func (u updateMessage) String() string { - if !u.Update { - updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc() - return updateMessage("You are already running the most recent version of ‘minio’.") - } - msg := colorizeUpdateMessage(u.Download, u.NewerThan) - return msg -} +const releaseTagTimeLayout = "2006-01-02T15-04-05Z" -func parseReleaseData(data string) (time.Time, error) { - releaseStr := strings.Fields(data) - if len(releaseStr) < 2 { - return time.Time{}, errors.New("Update data malformed") - } - releaseDate := releaseStr[1] - releaseDateSplits := strings.SplitN(releaseDate, ".", 3) - if len(releaseDateSplits) < 3 { - return time.Time{}, (errors.New("Update data malformed")) - } - if releaseDateSplits[0] != globalMinioDefaultOwnerID { - return time.Time{}, (errors.New("Update data malformed, missing minio tag")) - } - // "OFFICIAL" tag is still kept for backward compatibility. - // We should remove this for the next release. - if releaseDateSplits[1] != "RELEASE" && releaseDateSplits[1] != "OFFICIAL" { - return time.Time{}, (errors.New("Update data malformed, missing RELEASE tag")) - } - dateSplits := strings.SplitN(releaseDateSplits[2], "T", 2) - if len(dateSplits) < 2 { - return time.Time{}, (errors.New("Update data malformed, not in modified RFC3359 form")) +const minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/" + +func getCurrentReleaseTime(minioVersion, minioBinaryPath string) (releaseTime time.Time, err error) { + if releaseTime, err = time.Parse(time.RFC3339, minioVersion); err == nil { + return releaseTime, err } - dateSplits[1] = strings.Replace(dateSplits[1], "-", ":", -1) - date := strings.Join(dateSplits, "T") - parsedDate, err := time.Parse(time.RFC3339, date) + // Looks like version is minio non-standard, we use minio binary's ModTime as release time. + fi, err := os.Stat(minioBinaryPath) if err != nil { - return time.Time{}, err + err = fmt.Errorf("Unable to get ModTime of %s. %s", minioBinaryPath, err) + } else { + releaseTime = fi.ModTime().UTC() } - return parsedDate, nil + + return releaseTime, err } -// User Agent should always following the below style. -// Please open an issue to discuss any new changes here. -// -// Minio (OS; ARCH) APP/VER APP/VER -var ( - userAgentSuffix = "Minio/" + Version + " " + "Minio/" + ReleaseTag + " " + "Minio/" + CommitID -) +// GetCurrentReleaseTime - returns this process's release time. If it is official minio version, +// parsed version is returned else minio binary's mod time is returned. +func GetCurrentReleaseTime() (releaseTime time.Time, err error) { + return getCurrentReleaseTime(Version, os.Args[0]) +} -// Check if the operating system is a docker container. -func isDocker() bool { - cgroup, err := ioutil.ReadFile("/proc/self/cgroup") - if err != nil && !os.IsNotExist(err) { - errorIf(err, "Unable to read `cgroup` file.") +func isDocker(cgroupFile string) (bool, error) { + cgroup, err := ioutil.ReadFile(cgroupFile) + if os.IsNotExist(err) { + err = nil } - return bytes.Contains(cgroup, []byte("docker")) + return bytes.Contains(cgroup, []byte("docker")), err } -// Check if the minio server binary was built with source. -func isSourceBuild() bool { - return Version == goGetTag -} - -// Fetch the current version of the Minio server binary. -func getCurrentMinioVersion() (current time.Time, err error) { - // For development builds we check for binary modTime - // to validate against latest minio server release. - if Version != goGetTag { - // Parse current minio version into RFC3339. - current, err = time.Parse(time.RFC3339, Version) - if err != nil { - return time.Time{}, err - } - return current, nil - } // else { - // For all development builds through `go get`. - // fall back to looking for version of the build - // date of the binary itself. - var fi os.FileInfo - fi, err = os.Stat(os.Args[0]) +// IsDocker - returns if the environment is docker or not. +func IsDocker() bool { + found, err := isDocker("/proc/self/cgroup") if err != nil { - return time.Time{}, err + console.Fatalf("Error in docker check: %s", err) } - return fi.ModTime(), nil + + return found +} + +func isSourceBuild(minioVersion string) bool { + _, err := time.Parse(time.RFC3339, minioVersion) + return err != nil } -// verify updates for releases. -func getReleaseUpdate(updateURL string, duration time.Duration) (updateMsg updateMessage, errMsg string, err error) { - // Construct a new update url. - newUpdateURLPrefix := updateURL + "/" + runtime.GOOS + "-" + runtime.GOARCH - newUpdateURL := newUpdateURLPrefix + "/minio.shasum" +// IsSourceBuild - returns if this binary is made from source or not. +func IsSourceBuild() bool { + return isSourceBuild(Version) +} - // Get the downloadURL. - var downloadURL string - if isDocker() { - downloadURL = "docker pull minio/minio" - } else { - switch runtime.GOOS { - case globalWindowsOSName: - // For windows. - downloadURL = newUpdateURLPrefix + "/minio.exe" - default: - // For all other operating systems. - downloadURL = newUpdateURLPrefix + "/minio" - } +// DO NOT CHANGE USER AGENT STYLE. +// The style should be +// Minio (; [; docker][; source]) Minio/ Minio/ Minio/ +// +// For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues. +func getUserAgent() string { + userAgent := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH + if IsDocker() { + userAgent += "; docker" + } + if IsSourceBuild() { + userAgent += "; source" } + userAgent += ") " + " Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID + + return userAgent +} - // Initialize update message. - updateMsg = updateMessage{ - Download: downloadURL, +func downloadReleaseData(releaseChecksumURL string, timeout time.Duration) (data string, err error) { + req, err := http.NewRequest("GET", releaseChecksumURL, nil) + if err != nil { + return data, err } + req.Header.Set("User-Agent", getUserAgent()) - // Instantiate a new client with 3 sec timeout. client := &http.Client{ - Timeout: duration, + Timeout: timeout, } - current, err := getCurrentMinioVersion() + resp, err := client.Do(req) if err != nil { - errMsg = "Unable to fetch the current version of Minio server." - return + return data, err + } + if resp == nil { + return data, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL) } - // Initialize new request. - req, err := http.NewRequest("GET", newUpdateURL, nil) + if resp.StatusCode != http.StatusOK { + return data, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status) + } + + dataBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return + return data, fmt.Errorf("Error reading response. %s", err) } - userAgentPrefix := func() string { - prefix := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH - // if its a source build. - if isSourceBuild() { - if isDocker() { - prefix = prefix + "; " + "docker; source) " - } else { - prefix = prefix + "; " + "source) " - } - return prefix - } // else { not source. - if isDocker() { - prefix = prefix + "; " + "docker) " - } else { - prefix = prefix + ") " - } - return prefix - }() + data = string(dataBytes) + return data, err +} - // Set user agent. - req.Header.Set("User-Agent", userAgentPrefix+" "+userAgentSuffix) +// DownloadReleaseData - downloads release data from minio official server. +func DownloadReleaseData(timeout time.Duration) (data string, err error) { + return downloadReleaseData(minioReleaseURL+"minio.shasum", timeout) +} - // Fetch new update. - resp, err := client.Do(req) - if err != nil { - return +func parseReleaseData(data string) (releaseTime time.Time, err error) { + fields := strings.Fields(data) + if len(fields) != 2 { + err = fmt.Errorf("Unknown release data `%s`", data) + return releaseTime, err } - // Verify if we have a valid http response i.e http.StatusOK. - if resp != nil { - if resp.StatusCode != http.StatusOK { - errMsg = "Failed to retrieve update notice." - err = errors.New("http status : " + resp.Status) - return - } + releaseInfo := fields[1] + if fields = strings.Split(releaseInfo, "."); len(fields) != 3 { + err = fmt.Errorf("Unknown release information `%s`", releaseInfo) + return releaseTime, err } - // Read the response body. - updateBody, err := ioutil.ReadAll(resp.Body) + if !(fields[0] == "minio" && fields[1] == "RELEASE") { + err = fmt.Errorf("Unknown release '%s'", releaseInfo) + return releaseTime, err + } + + releaseTime, err = time.Parse(releaseTagTimeLayout, fields[2]) + if err != nil { + err = fmt.Errorf("Unknown release time format. %s", err) + } + + return releaseTime, err +} + +func getLatestReleaseTime(timeout time.Duration) (releaseTime time.Time, err error) { + data, err := DownloadReleaseData(timeout) if err != nil { - errMsg = "Failed to retrieve update notice. Please try again later." - return + return releaseTime, err } - errMsg = "Failed to retrieve update notice. Please try again later. Please report this issue at https://github.com/minio/minio/issues" + return parseReleaseData(data) +} + +func getDownloadURL() (downloadURL string) { + if IsDocker() { + return "docker pull minio/minio" + } + + if runtime.GOOS == "windows" { + return minioReleaseURL + "minio.exe" + } + + return minioReleaseURL + "minio" +} - // Parse the date if its valid. - latest, err := parseReleaseData(string(updateBody)) +func getUpdateInfo(timeout time.Duration) (older time.Duration, downloadURL string, err error) { + currentReleaseTime, err := GetCurrentReleaseTime() if err != nil { - return + return older, downloadURL, err } - // Verify if the date is not zero. - if latest.IsZero() { - err = errors.New("Release date cannot be zero. Please report this issue at https://github.com/minio/minio/issues") - return + latestReleaseTime, err := getLatestReleaseTime(timeout) + if err != nil { + return older, downloadURL, err } - // Is the update latest?. - if latest.After(current) { - updateMsg.Update = true - updateMsg.NewerThan = latest.Sub(current) + if latestReleaseTime.After(currentReleaseTime) { + older = latestReleaseTime.Sub(currentReleaseTime) + downloadURL = getDownloadURL() } - // Return update message. - return updateMsg, "", nil + return older, downloadURL, nil } -// main entry point for update command. func mainUpdate(ctx *cli.Context) { - // Initialization routine, such as config loading, enable logging, .. - minioInit(ctx) + if len(ctx.Args()) != 0 { + cli.ShowCommandHelpAndExit(ctx, "update", -1) + } + + quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet") + quietPrintln := func(args ...interface{}) { + if !quiet { + console.Println(args...) + } + } + + older, downloadURL, err := getUpdateInfo(10 * time.Second) + if err != nil { + quietPrintln(err) + os.Exit(-1) + } - if globalQuiet { - return + if older != time.Duration(0) { + quietPrintln(colorizeUpdateMessage(downloadURL, older)) + os.Exit(1) } - // Check for update. - var updateMsg updateMessage - var errMsg string - var err error - var secs = time.Second * 3 - updateMsg, errMsg, err = getReleaseUpdate(minioUpdateStableURL, secs) - fatalIf(err, errMsg) - console.Println(updateMsg) + colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc() + quietPrintln(colorSprintf("You are already running the most recent version of ‘minio’.")) + os.Exit(0) } diff --git a/cmd/update-main_nix_test.go b/cmd/update-main_nix_test.go deleted file mode 100644 index 11e1a0b77..000000000 --- a/cmd/update-main_nix_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// +build linux darwin dragonfly freebsd netbsd openbsd - -/* - * Minio Cloud Storage, (C) 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "fmt" - "net/http" - "net/http/httptest" - "reflect" - "runtime" - "testing" - "time" -) - -// Validate when release versions are properly set. -func TestReleaseUpdateVersion(t *testing.T) { - Version = "2016-10-06T00:08:32Z" - ReleaseTag = "RELEASE.2016-10-06T00-08-32Z" - CommitID = "d1c38ba8f0b3aecdf9b932c087dd65c21eebac33" - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z") - })) - userAgentSuffix = "Minio/" + Version + " " + "Minio/" + ReleaseTag + " " + "Minio/" + CommitID - defer ts.Close() - testCases := []struct { - updateURL string - updateMsg updateMessage - errMsg string - shouldPass bool - }{ - { - updateURL: ts.URL, - updateMsg: updateMessage{ - Download: ts.URL + "/" + runtime.GOOS + "-" + runtime.GOARCH + "/minio", - Update: true, - NewerThan: 90487000000000, - }, - errMsg: "", - shouldPass: true, - }, - } - - // Validates all the errors reported. - for i, testCase := range testCases { - updateMsg, errMsg, err := getReleaseUpdate(testCase.updateURL, time.Second*1) - if testCase.shouldPass && err != nil { - t.Errorf("Test %d: Unable to fetch release update %s", i+1, err) - } - if errMsg != testCase.errMsg { - t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.errMsg, errMsg) - } - if !reflect.DeepEqual(updateMsg, testCase.updateMsg) { - t.Errorf("Test %d: Expected %#v, got %#v", i+1, testCase.updateMsg, updateMsg) - } - } -} - -func TestReleaseUpdate(t *testing.T) { - Version = "DEVELOPMENT.GOGET" - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, client") - })) - defer ts.Close() - testCases := []struct { - updateURL string - updateMsg updateMessage - errMsg string - shouldPass bool - }{ - { - updateURL: ts.URL, - updateMsg: updateMessage{ - Download: ts.URL + "/" + runtime.GOOS + "-" + runtime.GOARCH + "/minio", - }, - errMsg: "Failed to retrieve update notice. Please try again later. Please report this issue at https://github.com/minio/minio/issues", - shouldPass: false, - }, - } - - // Validates all the errors reported. - for i, testCase := range testCases { - updateMsg, errMsg, err := getReleaseUpdate(testCase.updateURL, time.Second*1) - if testCase.shouldPass && err != nil { - t.Errorf("Test %d: Unable to fetch release update %s", i+1, err) - } - if errMsg != testCase.errMsg { - t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.errMsg, errMsg) - } - if !reflect.DeepEqual(updateMsg, testCase.updateMsg) { - t.Errorf("Test %d: Expected %#v, got %#v", i+1, testCase.updateMsg, updateMsg) - } - } -} diff --git a/cmd/update-main_test.go b/cmd/update-main_test.go new file mode 100644 index 000000000..364e698c5 --- /dev/null +++ b/cmd/update-main_test.go @@ -0,0 +1,275 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "runtime" + "testing" + "time" +) + +func TestGetCurrentReleaseTime(t *testing.T) { + minioVersion1 := time.Now().UTC().Format(time.RFC3339) + releaseTime1, _ := time.Parse(time.RFC3339, minioVersion1) + + minioVersion2 := "DEVELOPMENT.GOGET" + tmpfile, err := ioutil.TempFile("", "get-current-release-time-testcase") + if err != nil { + t.Fatalf("Unable to create temporary file. %s", err) + } + defer os.Remove(tmpfile.Name()) + minioBinaryPath2 := tmpfile.Name() + fi, err := tmpfile.Stat() + if err != nil { + t.Fatalf("Unable to get temporary file info. %s", err) + } + if err = tmpfile.Close(); err != nil { + t.Fatalf("Unable to create temporary file. %s", err) + } + releaseTime2 := fi.ModTime().UTC() + + errorMessage1 := "Unable to get ModTime of . stat : no such file or directory" + if runtime.GOOS == "windows" { + errorMessage1 = "Unable to get ModTime of . Lstat : The system cannot find the path specified." + } + + errorMessage2 := "Unable to get ModTime of non-existing-file. stat non-existing-file: no such file or directory" + if runtime.GOOS == "windows" { + errorMessage2 = "Unable to get ModTime of non-existing-file. GetFileAttributesEx non-existing-file: The system cannot find the file specified." + } + + testCases := []struct { + minioVersion string + minioBinaryPath string + expectedResult time.Time + expectedErr error + }{ + {minioVersion1, "", releaseTime1, nil}, + {minioVersion1, minioBinaryPath2, releaseTime1, nil}, + {minioVersion2, minioBinaryPath2, releaseTime2, nil}, + {"junk", minioBinaryPath2, releaseTime2, nil}, + {"3.2.0", minioBinaryPath2, releaseTime2, nil}, + {minioVersion2, "", time.Time{}, fmt.Errorf(errorMessage1)}, + {"junk", "non-existing-file", time.Time{}, fmt.Errorf(errorMessage2)}, + {"3.2.0", "non-existing-file", time.Time{}, fmt.Errorf(errorMessage2)}, + } + + if runtime.GOOS == "linux" { + testCases = append(testCases, struct { + minioVersion string + minioBinaryPath string + expectedResult time.Time + expectedErr error + }{"3.2a", "/proc/1/cwd", time.Time{}, fmt.Errorf("Unable to get ModTime of /proc/1/cwd. stat /proc/1/cwd: permission denied")}) + } + + for _, testCase := range testCases { + result, err := getCurrentReleaseTime(testCase.minioVersion, testCase.minioBinaryPath) + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + } else if err == nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + + if !testCase.expectedResult.Equal(result) { + t.Fatalf("result: expected: %v, got: %v", testCase.expectedResult, result) + } + } +} + +func TestIsDocker(t *testing.T) { + createTempFile := func(content string) string { + tmpfile, err := ioutil.TempFile("", "isdocker-testcase") + if err != nil { + t.Fatalf("Unable to create temporary file. %s", err) + } + if _, err = tmpfile.Write([]byte(content)); err != nil { + t.Fatalf("Unable to create temporary file. %s", err) + } + if err = tmpfile.Close(); err != nil { + t.Fatalf("Unable to create temporary file. %s", err) + } + return tmpfile.Name() + } + + filename1 := createTempFile(`11:pids:/user.slice/user-1000.slice/user@1000.service +10:blkio:/ +9:hugetlb:/ +8:perf_event:/ +7:cpuset:/ +6:devices:/user.slice +5:net_cls,net_prio:/ +4:cpu,cpuacct:/ +3:memory:/user/bala/0 +2:freezer:/user/bala/0 +1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service +`) + defer os.Remove(filename1) + filename2 := createTempFile(`14:name=systemd:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +13:pids:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +12:hugetlb:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +11:net_prio:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +10:perf_event:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +9:net_cls:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +8:freezer:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +7:devices:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +6:memory:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +5:blkio:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +4:cpuacct:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +3:cpu:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +2:cpuset:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 +1:name=openrc:/docker +`) + defer os.Remove(filename2) + + testCases := []struct { + filename string + expectedResult bool + expectedErr error + }{ + {"", false, nil}, + {"/tmp/non-existing-file", false, nil}, + {filename1, false, nil}, + {filename2, true, nil}, + } + + if runtime.GOOS == "linux" { + testCases = append(testCases, struct { + filename string + expectedResult bool + expectedErr error + }{"/proc/1/cwd", false, fmt.Errorf("open /proc/1/cwd: permission denied")}) + } + + for _, testCase := range testCases { + result, err := isDocker(testCase.filename) + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + } else if err == nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + + if testCase.expectedResult != result { + t.Fatalf("result: expected: %v, got: %v", testCase.expectedResult, result) + } + } +} + +func TestIsSourceBuild(t *testing.T) { + testCases := []struct { + minioVersion string + expectedResult bool + }{ + {time.Now().UTC().Format(time.RFC3339), false}, + {"DEVELOPMENT.GOGET", true}, + {"junk", true}, + {"3.2.4", true}, + } + + for _, testCase := range testCases { + result := isSourceBuild(testCase.minioVersion) + if testCase.expectedResult != result { + t.Fatalf("expected: %v, got: %v", testCase.expectedResult, result) + } + } +} + +func TestDownloadReleaseData(t *testing.T) { + httpServer1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + defer httpServer1.Close() + httpServer2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z") + })) + defer httpServer2.Close() + httpServer3 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "", http.StatusNotFound) + })) + defer httpServer3.Close() + + testCases := []struct { + releaseChecksumURL string + expectedResult string + expectedErr error + }{ + {httpServer1.URL, "", nil}, + {httpServer2.URL, "fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", nil}, + {httpServer3.URL, "", fmt.Errorf("Error downloading URL " + httpServer3.URL + ". Response: 404 Not Found")}, + } + + for _, testCase := range testCases { + result, err := downloadReleaseData(testCase.releaseChecksumURL, 1*time.Second) + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + } else if err == nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + + if testCase.expectedResult != result { + t.Fatalf("result: expected: %v, got: %v", testCase.expectedResult, result) + } + } +} + +func TestParseReleaseData(t *testing.T) { + releaseTime, _ := time.Parse(releaseTagTimeLayout, "2016-10-07T01-16-39Z") + testCases := []struct { + data string + expectedResult time.Time + expectedErr error + }{ + {"more than two fields", time.Time{}, fmt.Errorf("Unknown release data `more than two fields`")}, + {"more than", time.Time{}, fmt.Errorf("Unknown release information `than`")}, + {"more than.two.fields", time.Time{}, fmt.Errorf("Unknown release 'than.two.fields'")}, + {"more minio.RELEASE.fields", time.Time{}, fmt.Errorf(`Unknown release time format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)}, + {"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, nil}, + {"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, nil}, + } + + for _, testCase := range testCases { + result, err := parseReleaseData(testCase.data) + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + } else if err == nil { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected: %v, got: %v", testCase.expectedErr, err) + } + + if !testCase.expectedResult.Equal(result) { + t.Fatalf("result: expected: %v, got: %v", testCase.expectedResult, result) + } + } +} diff --git a/cmd/update-main_windows_test.go b/cmd/update-main_windows_test.go deleted file mode 100644 index 9900be556..000000000 --- a/cmd/update-main_windows_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// +build windows - -/* - * Minio Cloud Storage, (C) 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "fmt" - "net/http" - "net/http/httptest" - "reflect" - "runtime" - "testing" - "time" -) - -// Validate when release versions are properly set. -func TestReleaseUpdateVersion(t *testing.T) { - Version = "2016-10-06T00:08:32Z" - ReleaseTag = "RELEASE.2016-10-06T00-08-32Z" - CommitID = "d1c38ba8f0b3aecdf9b932c087dd65c21eebac33" - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z") - })) - userAgentSuffix = "Minio/" + Version + " " + "Minio/" + ReleaseTag + " " + "Minio/" + CommitID - defer ts.Close() - testCases := []struct { - updateURL string - updateMsg updateMessage - errMsg string - shouldPass bool - }{ - { - updateURL: ts.URL, - updateMsg: updateMessage{ - Download: ts.URL + "/" + runtime.GOOS + "-" + runtime.GOARCH + "/minio.exe", - Update: true, - NewerThan: 90487000000000, - }, - errMsg: "", - shouldPass: true, - }, - } - - // Validates all the errors reported. - for i, testCase := range testCases { - updateMsg, errMsg, err := getReleaseUpdate(testCase.updateURL, time.Second*1) - if testCase.shouldPass && err != nil { - t.Errorf("Test %d: Unable to fetch release update %s", i+1, err) - } - if errMsg != testCase.errMsg { - t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.errMsg, errMsg) - } - if !reflect.DeepEqual(updateMsg, testCase.updateMsg) { - t.Errorf("Test %d: Expected %#v, got %#v", i+1, testCase.updateMsg, updateMsg) - } - } -} - -func TestReleaseUpdate(t *testing.T) { - Version = "DEVELOPMENT.GOGET" - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, client") - })) - defer ts.Close() - testCases := []struct { - updateURL string - updateMsg updateMessage - errMsg string - shouldPass bool - }{ - { - updateURL: ts.URL, - updateMsg: updateMessage{ - Download: ts.URL + "/" + runtime.GOOS + "-" + runtime.GOARCH + "/minio.exe", - }, - errMsg: "Failed to retrieve update notice. Please try again later. Please report this issue at https://github.com/minio/minio/issues", - shouldPass: false, - }, - } - - // Validates all the errors reported. - for i, testCase := range testCases { - updateMsg, errMsg, err := getReleaseUpdate(testCase.updateURL, time.Second*1) - if testCase.shouldPass && err != nil { - t.Errorf("Test %d: Unable to fetch release update %s", i+1, err) - } - if errMsg != testCase.errMsg { - t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.errMsg, errMsg) - } - if !reflect.DeepEqual(updateMsg, testCase.updateMsg) { - t.Errorf("Test %d: Expected %#v, got %#v", i+1, testCase.updateMsg, updateMsg) - } - } -} diff --git a/cmd/update-notifier_test.go b/cmd/update-notifier_test.go index b6c83e567..ff6a6408d 100644 --- a/cmd/update-notifier_test.go +++ b/cmd/update-notifier_test.go @@ -24,12 +24,11 @@ import ( // Tests update notifier string builder. func TestUpdateNotifier(t *testing.T) { - updateMsg := minioUpdateStableURL - colorUpdateMsg := colorizeUpdateMessage(updateMsg, time.Duration(72*time.Hour)) - if strings.Index(colorUpdateMsg, "minutes") == -1 { + colorUpdateMsg := colorizeUpdateMessage(minioReleaseURL, time.Duration(72*time.Hour)) + if !strings.Contains(colorUpdateMsg, "minutes") { t.Fatal("Duration string not found in colorized update message", colorUpdateMsg) } - if strings.Index(colorUpdateMsg, updateMsg) == -1 { - t.Fatal("Update message not found in colorized update message", updateMsg) + if !strings.Contains(colorUpdateMsg, minioReleaseURL) { + t.Fatal("Update message not found in colorized update message", minioReleaseURL) } }