Merge branch 'equinix-update' of https://github.com/dimgal1/libcloud into dimgal1-equinix-update
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 51e0aa1..c6dfc5f 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -6,7 +6,7 @@
   pull_request:
     branches: [ trunk ]
   schedule:
-    - cron: '23 15 * * 3'
+    - cron: '0 3 * * *'
 
 jobs:
   analyze:
diff --git a/CHANGES.rst b/CHANGES.rst
index a99a8bd..f4d6301 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,19 @@
 Changelog
 =========
 
+Changes in Apache Libcloud 3.3.2
+--------------------------------
+
+Storage
+~~~~~~~
+
+- [Azure Blobs] Enable the Azure storage driver to be used with
+  Azure Government, Azure China, and Azure Private Link by setting
+  the driver host argument to the endpoint suffix for the environment.
+
+  Reported by Melissa Kersh - @mkcello96
+  (GITHUB-1551)
+
 Changes in Apache Libcloud 3.3.1
 --------------------------------
 
diff --git a/doap_libcloud.rdf b/doap_libcloud.rdf
index 5baa43a..321db1a 100644
--- a/doap_libcloud.rdf
+++ b/doap_libcloud.rdf
@@ -483,6 +483,13 @@
         <revision>v3.3.0</revision>
       </Version>
   </release>
+  <release>
+      <Version>
+        <name>3.3.1</name>
+        <created>2021-01-25</created>
+        <revision>v3.3.1</revision>
+      </Version>
+  </release>
 
   <repository>
       <SVNRepository>
diff --git a/docs/conf.py b/docs/conf.py
index 4a2517a..18c52b7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -68,9 +68,10 @@
 # built documents.
 #
 # The short X.Y version.
-version = '3.3.1'
+version = '3.3.2-dev'
+
 # The full version, including alpha/beta/rc tags.
-release = '3.3.1'
+release = '3.3.2-dev'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/examples/storage/azure/instantiate_gov.py b/docs/examples/storage/azure/instantiate_gov.py
new file mode 100644
index 0000000..46a6c01
--- /dev/null
+++ b/docs/examples/storage/azure/instantiate_gov.py
@@ -0,0 +1,8 @@
+from libcloud.storage.types import Provider
+from libcloud.storage.providers import get_driver
+
+cls = get_driver(Provider.AZURE_BLOBS)
+
+driver = cls(key='your storage account name',
+             secret='your access key',
+             host='blob.core.usgovcloudapi.net')
diff --git a/docs/storage/drivers/azure_blobs.rst b/docs/storage/drivers/azure_blobs.rst
index 824889c..956b3f1 100644
--- a/docs/storage/drivers/azure_blobs.rst
+++ b/docs/storage/drivers/azure_blobs.rst
@@ -33,6 +33,19 @@
 .. literalinclude:: /examples/storage/azure/instantiate.py
    :language: python
 
+Connecting to Azure Government
+------------------------------
+
+To target an `Azure Government`_ storage account, you can instantiate the driver
+by setting a custom storage host argument as shown below.
+
+.. literalinclude:: /examples/storage/azure/instantiate_gov.py
+   :language: python
+
+Setting a custom host argument can also be leveraged to customize the blob
+endpoint and connect to a storage account in `Azure China`_ or
+`Azure Private Link`_.
+
 Connecting to self-hosted Azure Storage implementations
 -------------------------------------------------------
 
@@ -51,3 +64,6 @@
 .. _`BlockBlobStorage accounts`: https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview#blockblobstorage-accounts
 .. _`Azurite storage emulator`: https://github.com/Azure/Azurite
 .. _`Azure Blob Storage on IoT Edge`: https://docs.microsoft.com/en-us/azure/iot-edge/how-to-store-data-blob
+.. _`Azure Government`: https://docs.microsoft.com/en-us/azure/azure-government/documentation-government-developer-guide
+.. _`Azure China`: https://docs.microsoft.com/en-us/azure/china/resources-developer-guide
+.. _`Azure Private Link`: https://docs.microsoft.com/en-us/azure/private-link/private-link-overview
diff --git a/libcloud/__init__.py b/libcloud/__init__.py
index 5165fda..f9f1710 100644
--- a/libcloud/__init__.py
+++ b/libcloud/__init__.py
@@ -41,7 +41,7 @@
     'enable_debug'
 ]
 
-__version__ = '3.3.1'
+__version__ = '3.3.2-dev'
 
 
 def enable_debug(fo):
diff --git a/libcloud/common/base.py b/libcloud/common/base.py
index 48b0e22..b944107 100644
--- a/libcloud/common/base.py
+++ b/libcloud/common/base.py
@@ -984,19 +984,6 @@
         self.key = key
         self.secret = secret
         self.secure = secure
-        args = [self.key]
-
-        if self.secret is not None:
-            args.append(self.secret)
-
-        args.append(secure)
-
-        if host is not None:
-            args.append(host)
-
-        if port is not None:
-            args.append(port)
-
         self.api_version = api_version
         self.region = region
 
@@ -1005,8 +992,23 @@
                             'retry_delay': kwargs.pop('retry_delay', None),
                             'backoff': kwargs.pop('backoff', None),
                             'proxy_url': kwargs.pop('proxy_url', None)})
-        self.connection = self.connectionCls(*args, **conn_kwargs)
 
+        args = [self.key]
+
+        if self.secret is not None:
+            args.append(self.secret)
+
+        args.append(secure)
+
+        host = conn_kwargs.pop('host', None) or host
+
+        if host is not None:
+            args.append(host)
+
+        if port is not None:
+            args.append(port)
+
+        self.connection = self.connectionCls(*args, **conn_kwargs)
         self.connection.driver = self
         self.connection.connect()
 
diff --git a/libcloud/storage/drivers/azure_blobs.py b/libcloud/storage/drivers/azure_blobs.py
index 3393784..6c65c51 100644
--- a/libcloud/storage/drivers/azure_blobs.py
+++ b/libcloud/storage/drivers/azure_blobs.py
@@ -68,6 +68,9 @@
 )
 
 AZURE_STORAGE_HOST_SUFFIX = 'blob.core.windows.net'
+AZURE_STORAGE_HOST_SUFFIX_CHINA = 'blob.core.chinacloudapi.cn'
+AZURE_STORAGE_HOST_SUFFIX_GOVERNMENT = 'blob.core.usgovcloudapi.net'
+AZURE_STORAGE_HOST_SUFFIX_PRIVATELINK = 'privatelink.blob.core.windows.net'
 
 AZURE_STORAGE_CDN_URL_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
 
@@ -173,11 +176,12 @@
     these deployments, the parameter ``account_prefix`` must be set on the
     connection. This is done by instantiating the driver with arguments such
     as ``host='somewhere.tld'`` and ``key='theaccount'``. To specify a custom
-    host without an account prefix, e.g. for use-cases where the custom host
-    implements an auditing proxy or similar, the driver can be instantiated
-    with ``host='theaccount.somewhere.tld'`` and ``key=''``.
+    host without an account prefix, e.g. to connect to Azure Government or
+    Azure China, the driver can be instantiated with the appropriate storage
+    endpoint suffix, e.g. ``host='blob.core.usgovcloudapi.net'`` and
+    ``key='theaccount'``.
 
-    :param account_prefix: Optional prefix identifying the sotrage account.
+    :param account_prefix: Optional prefix identifying the storage account.
                            Used when connecting to a custom deployment of the
                            storage service like Azurite or IoT Edge Storage.
     :type account_prefix: ``str``
@@ -206,7 +210,7 @@
 
     def __init__(self, key, secret=None, secure=True, host=None, port=None,
                  **kwargs):
-        self._host_argument_set = bool(host)
+        self._host = host
 
         # B64decode() this key and keep it, so that we don't have to do
         # so for every request. Minor performance improvement
@@ -217,15 +221,34 @@
                                                       port=port, **kwargs)
 
     def _ex_connection_class_kwargs(self):
-        result = {}
+        # if the user didn't provide a custom host value, assume we're
+        # targeting the default Azure Storage endpoints
+        if self._host is None:
+            return {'host': '%s.%s' % (self.key, AZURE_STORAGE_HOST_SUFFIX)}
 
-        # host argument has precedence
-        if not self._host_argument_set:
-            result['host'] = '%s.%s' % (self.key, AZURE_STORAGE_HOST_SUFFIX)
+        # connecting to a special storage region like Azure Government or
+        # Azure China requires setting a custom storage endpoint but we
+        # still use the same scheme to identify a specific account as for
+        # the standard storage endpoint
+        try:
+            host_suffix = next(
+                host_suffix
+                for host_suffix in (
+                    AZURE_STORAGE_HOST_SUFFIX_CHINA,
+                    AZURE_STORAGE_HOST_SUFFIX_GOVERNMENT,
+                    AZURE_STORAGE_HOST_SUFFIX_PRIVATELINK,
+                )
+                if self._host.endswith(host_suffix)
+            )
+        except StopIteration:
+            pass
         else:
-            result['account_prefix'] = self.key
+            return {'host': '%s.%s' % (self.key, host_suffix)}
 
-        return result
+        # if the host isn't targeting one of the special storage regions, it
+        # must be pointing to Azurite or IoT Edge Storage so switch to prefix
+        # identification
+        return {'account_prefix': self.key}
 
     def _xml_to_container(self, node):
         """
diff --git a/libcloud/test/storage/test_azure_blobs.py b/libcloud/test/storage/test_azure_blobs.py
index 9d3eb3c..19dfcee 100644
--- a/libcloud/test/storage/test_azure_blobs.py
+++ b/libcloud/test/storage/test_azure_blobs.py
@@ -935,6 +935,35 @@
         self.assertEqual(host2, 'fakeaccount2.blob.core.windows.net')
         self.assertEqual(host3, 'test.foo.bar.com')
 
+    def test_storage_driver_host_govcloud(self):
+        driver1 = self.driver_type(
+            'fakeaccount1', 'deadbeafcafebabe==',
+            host='blob.core.usgovcloudapi.net')
+        driver2 = self.driver_type(
+            'fakeaccount2', 'deadbeafcafebabe==',
+            host='fakeaccount2.blob.core.usgovcloudapi.net')
+
+        host1 = driver1.connection.host
+        host2 = driver2.connection.host
+        account_prefix_1 = driver1.connection.account_prefix
+        account_prefix_2 = driver2.connection.account_prefix
+
+        self.assertEqual(host1, 'fakeaccount1.blob.core.usgovcloudapi.net')
+        self.assertEqual(host2, 'fakeaccount2.blob.core.usgovcloudapi.net')
+        self.assertIsNone(account_prefix_1)
+        self.assertIsNone(account_prefix_2)
+
+    def test_storage_driver_host_azurite(self):
+        driver = self.driver_type(
+            'fakeaccount1', 'deadbeafcafebabe==',
+            host='localhost', port=10000, secure=False)
+
+        host = driver.connection.host
+        account_prefix = driver.connection.account_prefix
+
+        self.assertEqual(host, 'localhost')
+        self.assertEqual(account_prefix, 'fakeaccount1')
+
 
 class AzuriteBlobsTests(AzureBlobsTests):
     driver_args = STORAGE_AZURITE_BLOBS_PARAMS