diff --git a/AUTHORS b/AUTHORS index 79123b38d..30bc71ae7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,3 +45,4 @@ List of contributors, in chronological order: * Lorenzo Bolla (https://github.com/lbolla) * Benj Fassbind (https://github.com/randombenj) * Markus Muellner (https://github.com/mmianl) +* Chuan Liu (https://github.com/chuan) diff --git a/azure/public.go b/azure/public.go index 21cef5fe0..cf1a7b205 100644 --- a/azure/public.go +++ b/azure/public.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "io" + "net" "net/http" "net/url" "os" @@ -29,20 +30,47 @@ var ( _ aptly.PublishedStorage = (*PublishedStorage)(nil) ) +func isEmulatorEndpoint(endpoint string) bool { + if h, _, err := net.SplitHostPort(endpoint); err == nil { + endpoint = h + } + if endpoint == "localhost" { + return true + } + // For IPv6, there could be case where SplitHostPort fails for cannot finding port. + // In this case, eliminate the '[' and ']' in the URL. + // For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732 + if endpoint[0] == '[' && endpoint[len(endpoint)-1] == ']' { + endpoint = endpoint[1 : len(endpoint)-1] + } + return net.ParseIP(endpoint) != nil +} + // NewPublishedStorage creates published storage from Azure storage credentials -func NewPublishedStorage(accountName, accountKey, container, prefix string) (*PublishedStorage, error) { +func NewPublishedStorage(accountName, accountKey, container, prefix, endpoint string) (*PublishedStorage, error) { credential, err := azblob.NewSharedKeyCredential(accountName, accountKey) if err != nil { return nil, err } - containerURL, err := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net/%s", accountName, container)) + if endpoint == "" { + endpoint = "blob.core.windows.net" + } + + var url *url.URL + if isEmulatorEndpoint(endpoint) { + url, err = url.Parse(fmt.Sprintf("http://%s/%s/%s", endpoint, accountName, container)) + } else { + url, err = url.Parse(fmt.Sprintf("https://%s.%s/%s", accountName, endpoint, container)) + } if err != nil { return nil, err } + containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{})) + result := &PublishedStorage{ - container: azblob.NewContainerURL(*containerURL, azblob.NewPipeline(credential, azblob.PipelineOptions{})), + container: containerURL, prefix: prefix, } diff --git a/azure/public_test.go b/azure/public_test.go index fcd28363c..79abfed23 100644 --- a/azure/public_test.go +++ b/azure/public_test.go @@ -15,8 +15,8 @@ import ( ) type PublishedStorageSuite struct { - accountName, accountKey string - storage, prefixedStorage *PublishedStorage + accountName, accountKey, endpoint string + storage, prefixedStorage *PublishedStorage } var _ = Suite(&PublishedStorageSuite{}) @@ -55,6 +55,7 @@ func (s *PublishedStorageSuite) SetUpSuite(c *C) { println(" 2. AZURE_STORAGE_ACCESS_KEY") c.Skip("AZURE_STORAGE_ACCESS_KEY not set.") } + s.endpoint = os.Getenv("AZURE_STORAGE_ENDPOINT") } func (s *PublishedStorageSuite) SetUpTest(c *C) { @@ -63,13 +64,13 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { var err error - s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "") + s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint) c.Assert(err, IsNil) cnt := s.storage.container _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer) c.Assert(err, IsNil) - s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix) + s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint) c.Assert(err, IsNil) } @@ -249,7 +250,7 @@ func (s *PublishedStorageSuite) TestRemoveDirsPlus(c *C) { list, err := s.storage.Filelist("") c.Check(err, IsNil) - c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a b", "lala/a+b", "lala/c", "testa"}) + c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a b", "lala/a+b", "lala/c", "testa"}) } func (s *PublishedStorageSuite) TestRenameFile(c *C) { @@ -354,7 +355,7 @@ func (s *PublishedStorageSuite) TestSymLink(c *C) { c.Check(err, IsNil) c.Check(link, Equals, "a/b") - c.Skip("copy not available in s3test") + c.Skip("copy not available in azure test") } func (s *PublishedStorageSuite) TestFileExists(c *C) { diff --git a/context/context.go b/context/context.go index 55a09acdb..8ee3ebf92 100644 --- a/context/context.go +++ b/context/context.go @@ -417,7 +417,7 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto var err error publishedStorage, err = azure.NewPublishedStorage( - params.AccountName, params.AccountKey, params.Container, params.Prefix) + params.AccountName, params.AccountKey, params.Container, params.Prefix, params.Endpoint) if err != nil { Fatal(err) } diff --git a/man/aptly.1 b/man/aptly.1 index ee217f0e6..8ae5523c4 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -104,6 +104,7 @@ Configuration file is stored in JSON format (default values shown below): "accountKey": "", "container": "repo", "prefix": "" + "endpoint": "blob.core.windows.net" } } } diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl index cf3458b2d..bc6bce872 100644 --- a/man/aptly.1.ronn.tmpl +++ b/man/aptly.1.ronn.tmpl @@ -96,6 +96,7 @@ Configuration file is stored in JSON format (default values shown below): "accountKey": "", "container": "repo", "prefix": "" + "endpoint": "blob.core.windows.net" } } } diff --git a/utils/config.go b/utils/config.go index 6c8482f5d..33e172791 100644 --- a/utils/config.go +++ b/utils/config.go @@ -82,6 +82,7 @@ type AzurePublishRoot struct { AccountKey string `json:"accountKey"` Container string `json:"container"` Prefix string `json:"prefix"` + Endpoint string `json:"endpoint"` } // Config is configuration for aptly, shared by all modules diff --git a/utils/config_test.go b/utils/config_test.go index 9b08599a7..6cb171820 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -124,7 +124,8 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { " \"accountName\": \"\",\n"+ " \"accountKey\": \"\",\n"+ " \"container\": \"repo\",\n"+ - " \"prefix\": \"\"\n"+ + " \"prefix\": \"\",\n"+ + " \"endpoint\": \"\"\n"+ " }\n"+ " },\n"+ " \"AsyncAPI\": false,\n"+