cmd/make.go (161 lines of code) (raw):
// Copyright © 2017 Microsoft <wastore@microsoft.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package cmd
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake"
"net/url"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake/datalakeerror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake/filesystem"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/fileerror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/share"
"errors"
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/Azure/azure-storage-azcopy/v10/ste"
"github.com/spf13/cobra"
)
// holds raw input from user
type rawMakeCmdArgs struct {
resourceToCreate string
quota uint32
}
// parse raw input
func (raw rawMakeCmdArgs) cook() (cookedMakeCmdArgs, error) {
parsedURL, err := url.Parse(raw.resourceToCreate)
if err != nil {
return cookedMakeCmdArgs{}, err
}
if strings.Count(parsedURL.Path, "/") > 1 {
return cookedMakeCmdArgs{}, fmt.Errorf("please provide a valid top-level(ex: File System or Container) resource URL")
}
// resourceLocation could be unknown at this stage, it will be handled by the caller
return cookedMakeCmdArgs{
resourceURL: *parsedURL,
resourceLocation: InferArgumentLocation(raw.resourceToCreate),
quota: int32(raw.quota),
}, nil
}
// holds processed/actionable args
type cookedMakeCmdArgs struct {
resourceURL url.URL
resourceLocation common.Location
quota int32 // quota is in GB
}
func (cookedArgs cookedMakeCmdArgs) process() (err error) {
ctx := context.WithValue(context.TODO(), ste.ServiceAPIVersionOverride, ste.DefaultServiceApiVersion)
resourceStringParts, err := SplitResourceString(cookedArgs.resourceURL.String(), cookedArgs.resourceLocation)
if err != nil {
return err
}
if err := common.VerifyIsURLResolvable(resourceStringParts.Value); cookedArgs.resourceLocation.IsRemote() && err != nil {
return fmt.Errorf("failed to resolve target: %w", err)
}
credentialInfo, _, err := GetCredentialInfoForLocation(ctx, cookedArgs.resourceLocation, resourceStringParts, false, common.CpkOptions{})
if err != nil {
return err
}
var reauthTok *common.ScopedAuthenticator
if at, ok := credentialInfo.OAuthTokenInfo.TokenCredential.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
reauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}
// Note : trailing dot is only applicable to file operations anyway, so setting this to false
options := createClientOptions(common.AzcopyCurrentJobLogger, nil, reauthTok)
resourceURL := cookedArgs.resourceURL.String()
cred := credentialInfo.OAuthTokenInfo.TokenCredential
switch cookedArgs.resourceLocation {
case common.ELocation.BlobFS():
var filesystemClient *filesystem.Client
if credentialInfo.CredentialType.IsAzureOAuth() {
filesystemClient, err = filesystem.NewClient(resourceURL, cred, &filesystem.ClientOptions{ClientOptions: options})
} else if credentialInfo.CredentialType.IsSharedKey() {
var sharedKeyCred *azdatalake.SharedKeyCredential
sharedKeyCred, err = common.GetDatalakeSharedKeyCredential()
if err != nil {
return err
}
filesystemClient, err = filesystem.NewClientWithSharedKeyCredential(resourceURL, sharedKeyCred, &filesystem.ClientOptions{ClientOptions: options})
} else {
filesystemClient, err = filesystem.NewClientWithNoCredential(resourceURL, &filesystem.ClientOptions{ClientOptions: options})
}
if err != nil {
return err
}
if _, err = filesystemClient.Create(ctx, nil); err != nil {
// print a nicer error message if container already exists
if datalakeerror.HasCode(err, datalakeerror.FileSystemAlreadyExists) {
return fmt.Errorf("the filesystem already exists")
} else if datalakeerror.HasCode(err, datalakeerror.ResourceNotFound) {
return fmt.Errorf("please specify a valid filesystem URL with corresponding credentials")
}
// print the ugly error if unexpected
return err
}
case common.ELocation.Blob():
// TODO : Ensure it is a container URL here and fail early?
var containerClient *container.Client
if credentialInfo.CredentialType.IsAzureOAuth() {
containerClient, err = container.NewClient(resourceURL, cred, &container.ClientOptions{ClientOptions: options})
} else {
containerClient, err = container.NewClientWithNoCredential(resourceURL, &container.ClientOptions{ClientOptions: options})
}
if err != nil {
return err
}
if _, err = containerClient.Create(ctx, nil); err != nil {
// print a nicer error message if container already exists
if bloberror.HasCode(err, bloberror.ContainerAlreadyExists) {
return fmt.Errorf("the container already exists")
} else if bloberror.HasCode(err, bloberror.ResourceNotFound) {
return fmt.Errorf("please specify a valid container URL with corresponding credentials")
}
// print the ugly error if unexpected
return err
}
case common.ELocation.File():
var shareClient *share.Client
shareClient, err = share.NewClientWithNoCredential(resourceURL, &share.ClientOptions{ClientOptions: options})
if err != nil {
return err
}
quota := &cookedArgs.quota
if quota != nil && *quota == 0 {
quota = nil
}
if _, err = shareClient.Create(ctx, &share.CreateOptions{Quota: quota}); err != nil {
// print a nicer error message if share already exists
if fileerror.HasCode(err, fileerror.ShareAlreadyExists) {
return fmt.Errorf("the file share already exists")
} else if fileerror.HasCode(err, fileerror.ResourceNotFound) {
return fmt.Errorf("please specify a valid share URL with corresponding credentials")
}
// print the ugly error if unexpected
return err
}
default:
return fmt.Errorf("operation not supported, cannot create resource %s type at the moment", cookedArgs.resourceURL.String())
}
return nil
}
func init() {
rawArgs := rawMakeCmdArgs{}
// makeCmd represents the mkdir command, but targets the service side
makeCmd := &cobra.Command{
Use: "make [resourceURL]",
Aliases: []string{"mk", "mkdir"},
SuggestFor: []string{"mak", "makeCmd"},
Short: makeCmdShortDescription,
Long: makeCmdLongDescription,
Example: makeCmdExample,
Args: func(cmd *cobra.Command, args []string) error {
// verify that there is exactly one argument
if len(args) != 1 {
return errors.New("please provide the resource URL as the only argument")
}
rawArgs.resourceToCreate = args[0]
return nil
},
Run: func(cmd *cobra.Command, args []string) {
cookedArgs, err := rawArgs.cook()
if err != nil {
glcm.Error(err.Error())
}
err = cookedArgs.process()
if err != nil {
glcm.Error(err.Error())
}
glcm.Exit(func(format common.OutputFormat) string {
return "Successfully created the resource."
}, common.EExitCode.Success())
},
}
makeCmd.PersistentFlags().Uint32Var(&rawArgs.quota, "quota-gb", 0, "Specifies the maximum size of the share in gigabytes (GiB), 0 means you accept the file service's default quota.")
rootCmd.AddCommand(makeCmd)
}