Skip to content

Commit f967afe

Browse files
authored
Merge pull request #84 from Azure/dev
Dev
2 parents b646244 + 412a6a8 commit f967afe

30 files changed

+760
-334
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ FROM golang:1.10
33
ENV GOPATH /go
44
ENV PATH ${GOPATH}/bin:$PATH
55
RUN go get -u github.com/golang/dep/cmd/dep
6-
RUN go get -u github.com/golang/lint/golint
6+
RUN go get -u golang.org/x/lint/golint
77

88
# Prepare enviroment for OSX cross compilation.
99
# These steps are referenced from https://github.com/karalabe/xgo/blob/master/docker/base/Dockerfile (licensed with MIT)

Gopkg.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
[[constraint]]
1010
name = "github.com/Azure/azure-storage-file-go"
11-
version = "0.2.0"
11+
branch = "dev"
1212

1313
[[constraint]]
1414
name = "github.com/Azure/go-autorest"

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ build: setup ## build binaries for the project
2929
# the environment variables need to be passed into the container explicitly
3030
GOARCH=amd64 GOOS=linux $(call with_docker,go build -o "azcopy_linux_amd64",-e GOARCH -e GOOS)
3131
GOARCH=amd64 GOOS=windows $(call with_docker,go build -o "azcopy_windows_amd64.exe",-e GOARCH -e GOOS)
32+
GOARCH=386 GOOS=windows $(call with_docker,go build -o "azcopy_windows_386.exe",-e GOARCH -e GOOS)
3233

3334
build-osx: setup ## build osx binary specially, as it's using CGO
3435
CC=o64-clang CXX=o64-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 $(call with_docker,go build -o "azcopy_darwin_amd64",-e CC -e CXX -e GOOS -e GOARCH -e CGO_ENABLED)

cmd/cancel.go

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func init() {
8888
if err != nil {
8989
glcm.Exit("failed to perform copy command due to error "+err.Error(), common.EExitCode.Error())
9090
}
91+
92+
glcm.Exit("", common.EExitCode.Success())
9193
},
9294
// hide features not relevant to BFS
9395
// TODO remove after preview release.

cmd/copy.go

+30-9
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ type rawCopyCmdArgs struct {
8888
acl string
8989
logVerbosity string
9090
cancelFromStdin bool
91+
// list of blobTypes to exclude while enumerating the transfer
92+
excludeBlobType string
9193
}
9294

9395
// validates and transform raw input into cooked input
@@ -230,6 +232,21 @@ func (raw rawCopyCmdArgs) cook() (cookedCopyCmdArgs, error) {
230232
return cooked, fmt.Errorf("content-type, content-encoding or metadata is set while copying from sevice to service")
231233
}
232234
}
235+
236+
// If the user has provided some input with excludeBlobType flag, parse the input.
237+
if len(raw.excludeBlobType) > 0 {
238+
// Split the string using delimeter ';' and parse the individual blobType
239+
blobTypes := strings.Split(raw.excludeBlobType, ";")
240+
for _, blobType := range blobTypes {
241+
var eBlobType common.BlobType
242+
err := eBlobType.Parse(blobType)
243+
if err != nil {
244+
return cooked, fmt.Errorf("error parsing the excludeBlobType %s provided with excludeBlobTypeFlag ", blobType)
245+
}
246+
cooked.excludeBlobType = append(cooked.excludeBlobType, eBlobType.ToAzBlobType())
247+
}
248+
}
249+
233250
return cooked, nil
234251
}
235252

@@ -251,7 +268,9 @@ type cookedCopyCmdArgs struct {
251268
forceWrite bool
252269

253270
// options from flags
254-
blockSize uint32
271+
blockSize uint32
272+
// list of blobTypes to exclude while enumerating the transfer
273+
excludeBlobType []azblob.BlobType
255274
blockBlobTier common.BlockBlobTier
256275
pageBlobTier common.PageBlobTier
257276
metadata string
@@ -414,13 +433,14 @@ func (cca *cookedCopyCmdArgs) processRedirectionUpload(blobUrl string, blockSize
414433
func (cca *cookedCopyCmdArgs) processCopyJobPartOrders() (err error) {
415434
// initialize the fields that are constant across all job part orders
416435
jobPartOrder := common.CopyJobPartOrderRequest{
417-
JobID: cca.jobID,
418-
FromTo: cca.fromTo,
419-
ForceWrite: cca.forceWrite,
420-
Priority: common.EJobPriority.Normal(),
421-
LogLevel: cca.logVerbosity,
422-
Include: cca.include,
423-
Exclude: cca.exclude,
436+
JobID: cca.jobID,
437+
FromTo: cca.fromTo,
438+
ForceWrite: cca.forceWrite,
439+
Priority: common.EJobPriority.Normal(),
440+
LogLevel: cca.logVerbosity,
441+
Include: cca.include,
442+
Exclude: cca.exclude,
443+
ExcludeBlobType: cca.excludeBlobType,
424444
BlobAttributes: common.BlobTransferAttributes{
425445
BlockSizeInBytes: cca.blockSize,
426446
ContentType: cca.contentType,
@@ -867,7 +887,8 @@ Copy an entire account with SAS:
867887
cpCmd.PersistentFlags().BoolVar(&raw.forceWrite, "overwrite", true, "overwrite the conflicting files/blobs at the destination if this flag is set to true.")
868888
cpCmd.PersistentFlags().BoolVar(&raw.recursive, "recursive", false, "look into sub-directories recursively when uploading from local file system.")
869889
cpCmd.PersistentFlags().StringVar(&raw.fromTo, "fromTo", "", "optionally specifies the source destination combination. For Example: LocalBlob, BlobLocal, LocalBlobFS.")
870-
890+
cpCmd.PersistentFlags().StringVar(&raw.excludeBlobType, "excludeBlobType", "", "optionally specifies the type of blob (BlockBlob/ PageBlob/ AppendBlob) to exclude when copying blobs from Container / Account. Use of "+
891+
"this flag is not applicable for copying data from non azure-service to service. More than one blob should be separated by ';' ")
871892
// options change how the transfers are performed
872893
cpCmd.PersistentFlags().StringVar(&raw.output, "output", "text", "format of the command's output, the choices include: text, json.")
873894
cpCmd.PersistentFlags().StringVar(&raw.logVerbosity, "log-level", "INFO", "define the log verbosity for the log file, available levels: DEBUG, INFO, WARNING, ERROR, PANIC, and FATAL.")

cmd/copyBlobToNEnumerator.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ func (e *copyBlobToNEnumerator) addTransfersFromContainer(ctx context.Context, s
171171
if !gCopyUtil.matchBlobNameAgainstPattern(blobNamePattern, blobItem.Name, cca.recursive) {
172172
return false
173173
}
174-
175174
includeExcludeMatchPath := common.IffString(includExcludeContainer,
176175
azblob.NewBlobURLParts(srcContainerURL.URL()).ContainerName+"/"+blobItem.Name,
177176
blobItem.Name)
@@ -184,7 +183,12 @@ func (e *copyBlobToNEnumerator) addTransfersFromContainer(ctx context.Context, s
184183
if gCopyUtil.resourceShouldBeExcluded(parentSourcePath, e.Exclude, includeExcludeMatchPath) {
185184
return false
186185
}
187-
186+
// check if blobType of the current blob is present in the list of blob type to exclude.
187+
for _, blobType := range e.ExcludeBlobType {
188+
if blobItem.Properties.BlobType == blobType {
189+
return false
190+
}
191+
}
188192
return true
189193
}
190194

cmd/jobsResume.go

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ Resume the existing job with the given job ID.`,
150150
if err != nil {
151151
glcm.Exit(fmt.Sprintf("failed to perform resume command due to error: %s", err.Error()), common.EExitCode.Error())
152152
}
153+
glcm.Exit("", common.EExitCode.Success())
153154
},
154155
}
155156

cmd/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func init() {
4444
Aliases: []string{"ls"},
4545
Short: "List the entities in a given resource",
4646
Long: `List the entities in a given resource. Only Blob containers are supported at the moment.`,
47-
Example: "azcopy container list [containerURL]",
47+
Example: "azcopy list [containerURL]",
4848
Args: func(cmd *cobra.Command, args []string) error {
4949
// the listContainer command requires necessarily to have an argument
5050

cmd/pause.go

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func init() {
4949
},
5050
Run: func(cmd *cobra.Command, args []string) {
5151
HandlePauseCommand(commandLineInput)
52+
glcm.Exit("", common.EExitCode.Success())
5253
},
5354
// hide features not relevant to BFS
5455
// TODO remove after preview release

cmd/root.go

+89-2
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,24 @@
2121
package cmd
2222

2323
import (
24+
"bytes"
25+
"context"
2426
"github.com/Azure/azure-storage-azcopy/common"
27+
"github.com/Azure/azure-storage-blob-go/2018-03-28/azblob"
2528
"github.com/spf13/cobra"
29+
"net/url"
30+
"os"
31+
"strings"
32+
"time"
2633
)
2734

2835
var azcopyAppPathFolder string
2936

3037
// rootCmd represents the base command when called without any subcommands
3138
var rootCmd = &cobra.Command{
32-
Use: "azcopy",
33-
Short: "AzCopy is a command line tool that moves data into/out of Azure Storage.",
39+
Version: common.AzcopyVersion, // will enable the user to see the version info in the standard posix way: --version
40+
Use: "azcopy",
41+
Short: "AzCopy is a command line tool that moves data into/out of Azure Storage.",
3442
Long: "AzCopy " + common.AzcopyVersion +
3543
`
3644
Project URL: github.com/Azure/azure-storage-azcopy
@@ -40,6 +48,14 @@ To report issues or to learn more about the tool, go to github.com/Azure/azure-s
4048
4149
The general format of the commands is: 'azcopy [command] [arguments] --[flag-name]=[flag-value]'.
4250
`,
51+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
52+
// spawn a routine to fetch and compare the local application's version against the latest version available
53+
// if there's a newer version that can be used, then write the suggestion to stderr
54+
// however if this takes too long the message won't get printed
55+
// Note: this function is only triggered for non-help commands
56+
go detectNewVersion()
57+
58+
},
4359
}
4460

4561
// hold a pointer to the global lifecycle controller so that commands could output messages and exit properly
@@ -52,5 +68,76 @@ func Execute(azsAppPathFolder string) {
5268

5369
if err := rootCmd.Execute(); err != nil {
5470
glcm.Exit(err.Error(), common.EExitCode.Error())
71+
} else {
72+
// our commands all control their own life explicitly with the lifecycle manager
73+
// only help commands reach this point
74+
// execute synchronously before exiting
75+
detectNewVersion()
76+
}
77+
}
78+
79+
func detectNewVersion() {
80+
const versionMetadataUrl = "https://aka.ms/azcopyv10-version-metadata"
81+
82+
// step 0: check the Stderr before checking version
83+
_, err := os.Stderr.Stat()
84+
if err != nil {
85+
return
86+
}
87+
88+
// step 1: initialize pipeline
89+
p := azblob.NewPipeline(azblob.NewAnonymousCredential(), azblob.PipelineOptions{
90+
Retry: azblob.RetryOptions{
91+
Policy: azblob.RetryPolicyExponential,
92+
MaxTries: 1, // try a single time, if network is not available, just fail fast
93+
TryTimeout: time.Second * 3, // don't wait for too long
94+
RetryDelay: downloadRetryDelay,
95+
MaxRetryDelay: downloadMaxRetryDelay,
96+
},
97+
Telemetry: azblob.TelemetryOptions{
98+
Value: common.UserAgent,
99+
},
100+
})
101+
102+
// step 2: parse source url
103+
u, err := url.Parse(versionMetadataUrl)
104+
if err != nil {
105+
return
106+
}
107+
108+
// step 3: start download
109+
blobURL := azblob.NewBlobURL(*u, p)
110+
blobStream, err := blobURL.Download(context.TODO(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false)
111+
if err != nil {
112+
return
113+
}
114+
115+
blobBody := blobStream.Body(azblob.RetryReaderOptions{MaxRetryRequests: downloadMaxTries})
116+
defer blobBody.Close()
117+
118+
// step 4: read newest version str
119+
buf := new(bytes.Buffer)
120+
n, err := buf.ReadFrom(blobBody)
121+
if n == 0 || err != nil {
122+
return
123+
}
124+
// only take the first line, in case the version metadata file is upgraded in the future
125+
remoteVersion := strings.Split(buf.String(), "\n")[0]
126+
127+
// step 5: compare remote version to local version to see if there's a newer AzCopy
128+
v1, err := NewVersion(common.AzcopyVersion)
129+
if err != nil {
130+
return
131+
}
132+
v2, err := NewVersion(remoteVersion)
133+
if err != nil {
134+
return
135+
}
136+
137+
if v1.OlderThan(*v2) {
138+
executablePathSegments := strings.Split(strings.Replace(os.Args[0], "\\", "/", -1), "/")
139+
executableName := executablePathSegments[len(executablePathSegments)-1]
140+
// print to stderr instead of stdout, in case the output is in other formats
141+
glcm.Error(executableName + ": A newer version " + remoteVersion + " is available to download\n")
55142
}
56143
}

cmd/versionChecker.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright © 2017 Microsoft <[email protected]>
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package cmd
22+
23+
import (
24+
"errors"
25+
"strconv"
26+
"strings"
27+
)
28+
29+
type Version struct {
30+
segments []int64
31+
preview bool
32+
original string
33+
}
34+
35+
// To keep the code simple, we assume we only use a simple subset of semantic versions.
36+
// Namely, the version is either a normal stable version, or a pre-release version with '-preview' attached.
37+
// Examples: 10.1.0, 11.2.0-preview
38+
func NewVersion(raw string) (*Version, error) {
39+
const standardError = "invalid version string"
40+
41+
rawSegments := strings.Split(raw, ".")
42+
if len(rawSegments) != 3 {
43+
return nil, errors.New(standardError)
44+
}
45+
46+
v := &Version{segments: make([]int64, 3), original: raw}
47+
for i, str := range rawSegments {
48+
if strings.Contains(str, "-") {
49+
if i != 2 {
50+
return nil, errors.New(standardError)
51+
}
52+
v.preview = true
53+
str = strings.Split(str, "-")[0]
54+
}
55+
56+
val, err := strconv.ParseInt(str, 10, 64)
57+
if err != nil {
58+
return nil, errors.New("cannot version string")
59+
}
60+
v.segments[i] = val
61+
}
62+
63+
return v, nil
64+
}
65+
66+
// compare this version (v) to another version (v2)
67+
// return -1 if v is smaller/older than v2
68+
// return 0 if v is equal to v2
69+
// return 1 if v is bigger/newer than v2
70+
func (v Version) compare(v2 Version) int {
71+
// short-circuit if the two version have the exact same raw string, no need to compare
72+
if v.original == v2.original {
73+
return 0
74+
}
75+
76+
// compare the major/minor/patch version
77+
// if v has a bigger number, it is newer
78+
for i, num := range v.segments {
79+
if num > v2.segments[i] {
80+
return 1
81+
} else if num < v2.segments[i] {
82+
return -1
83+
}
84+
}
85+
86+
// if both or neither versions are previews, then they are equal
87+
// usually this shouldn't happen since we already checked whether the two versions have equal raw string
88+
// however, it is entirely possible that we have new kinds of pre-release versions that this code is not parsing correctly
89+
// in this case we consider both pre-release version equal anyways
90+
if (v.preview && v2.preview) || (!v.preview && !v2.preview) {
91+
return 0
92+
} else if v.preview && !v2.preview {
93+
return -1
94+
}
95+
96+
return 1
97+
}
98+
99+
// detect if version v is older than v2
100+
func (v Version) OlderThan(v2 Version) bool {
101+
return v.compare(v2) == -1
102+
}
103+
104+
// detect if version v is newer than v2
105+
func (v Version) NewerThan(v2 Version) bool {
106+
return v.compare(v2) == 1
107+
}

0 commit comments

Comments
 (0)