Repository GCS plugin new client library (#30168)
This does away with the deprecated `com.google.api-client:google-api-client:1.23` and replaces it with `com.google.cloud:google-cloud-storage:1.28.0`. It also changes security permissions for the repository-gcs plugin.
This commit is contained in:
parent
d1c28c60fc
commit
801973fa9f
|
@ -84,11 +84,7 @@ A service account file looks like this:
|
||||||
"private_key_id": "...",
|
"private_key_id": "...",
|
||||||
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
|
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
|
||||||
"client_email": "service-account-for-your-repository@your-project-id.iam.gserviceaccount.com",
|
"client_email": "service-account-for-your-repository@your-project-id.iam.gserviceaccount.com",
|
||||||
"client_id": "...",
|
"client_id": "..."
|
||||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
|
||||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
|
||||||
"client_x509_cert_url": "..."
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
// NOTCONSOLE
|
// NOTCONSOLE
|
||||||
|
@ -178,6 +174,12 @@ are marked as `Secure`.
|
||||||
a custom name can be useful to authenticate your cluster when requests
|
a custom name can be useful to authenticate your cluster when requests
|
||||||
statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
|
statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
|
||||||
|
|
||||||
|
`project_id`::
|
||||||
|
|
||||||
|
The Google Cloud project id. This will be automatically infered from the credentials file but
|
||||||
|
can be specified explicitly. For example, it can be used to switch between projects when the
|
||||||
|
same credentials are usable for both the production and the development projects.
|
||||||
|
|
||||||
[[repository-gcs-repository]]
|
[[repository-gcs-repository]]
|
||||||
==== Repository Settings
|
==== Repository Settings
|
||||||
|
|
||||||
|
|
|
@ -22,35 +22,204 @@ esplugin {
|
||||||
classname 'org.elasticsearch.repositories.gcs.GoogleCloudStoragePlugin'
|
classname 'org.elasticsearch.repositories.gcs.GoogleCloudStoragePlugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
versions << [
|
|
||||||
'google': '1.23.0',
|
|
||||||
]
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "com.google.apis:google-api-services-storage:v1-rev115-${versions.google}"
|
compile 'com.google.cloud:google-cloud-storage:1.28.0'
|
||||||
compile "com.google.api-client:google-api-client:${versions.google}"
|
compile 'com.google.cloud:google-cloud-core:1.28.0'
|
||||||
compile "com.google.oauth-client:google-oauth-client:${versions.google}"
|
compile 'com.google.cloud:google-cloud-core-http:1.28.0'
|
||||||
compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
|
compile 'com.google.auth:google-auth-library-oauth2-http:0.9.1'
|
||||||
compile "org.apache.httpcomponents:httpcore:${versions.httpcore}"
|
compile 'com.google.auth:google-auth-library-credentials:0.9.1'
|
||||||
compile "commons-logging:commons-logging:${versions.commonslogging}"
|
compile 'com.google.oauth-client:google-oauth-client:1.23.0'
|
||||||
compile "commons-codec:commons-codec:${versions.commonscodec}"
|
compile 'com.google.http-client:google-http-client:1.23.0'
|
||||||
compile "com.google.http-client:google-http-client:${versions.google}"
|
compile 'com.google.http-client:google-http-client-jackson:1.23.0'
|
||||||
compile "com.google.http-client:google-http-client-jackson2:${versions.google}"
|
compile 'com.google.http-client:google-http-client-jackson2:1.23.0'
|
||||||
|
compile 'com.google.http-client:google-http-client-appengine:1.23.0'
|
||||||
|
compile 'com.google.api-client:google-api-client:1.23.0'
|
||||||
|
compile 'com.google.api:gax:1.25.0'
|
||||||
|
compile 'com.google.api:gax-httpjson:0.40.0'
|
||||||
|
compile 'com.google.api:api-common:1.5.0'
|
||||||
|
compile 'com.google.api.grpc:proto-google-common-protos:1.8.0'
|
||||||
|
compile 'com.google.guava:guava:20.0'
|
||||||
|
compile 'com.google.apis:google-api-services-storage:v1-rev115-1.23.0'
|
||||||
|
compile 'org.codehaus.jackson:jackson-core-asl:1.9.13'
|
||||||
|
compile 'io.grpc:grpc-context:1.9.0'
|
||||||
|
compile 'io.opencensus:opencensus-api:0.11.1'
|
||||||
|
compile 'io.opencensus:opencensus-contrib-http-util:0.11.1'
|
||||||
|
compile 'org.threeten:threetenbp:1.3.6'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyLicenses {
|
dependencyLicenses {
|
||||||
mapping from: /google-.*/, to: 'google'
|
mapping from: /google-cloud-.*/, to: 'google-cloud'
|
||||||
|
mapping from: /google-auth-.*/, to: 'google-auth'
|
||||||
|
mapping from: /google-http-.*/, to: 'google-http'
|
||||||
|
mapping from: /opencensus.*/, to: 'opencensus'
|
||||||
}
|
}
|
||||||
|
|
||||||
thirdPartyAudit.excludes = [
|
thirdPartyAudit.excludes = [
|
||||||
|
// uses internal java api: sun.misc.Unsafe
|
||||||
|
'com.google.common.cache.Striped64',
|
||||||
|
'com.google.common.cache.Striped64$1',
|
||||||
|
'com.google.common.cache.Striped64$Cell',
|
||||||
|
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$1',
|
||||||
|
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$2',
|
||||||
|
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$3',
|
||||||
|
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper',
|
||||||
|
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1',
|
||||||
|
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray',
|
||||||
|
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator',
|
||||||
|
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator$1',
|
||||||
// classes are missing
|
// classes are missing
|
||||||
'com.google.common.base.Splitter',
|
'com.google.appengine.api.datastore.Blob',
|
||||||
'com.google.common.collect.Lists',
|
'com.google.appengine.api.datastore.DatastoreService',
|
||||||
'javax.servlet.ServletContextEvent',
|
'com.google.appengine.api.datastore.DatastoreServiceFactory',
|
||||||
'javax.servlet.ServletContextListener',
|
'com.google.appengine.api.datastore.Entity',
|
||||||
'org.apache.avalon.framework.logger.Logger',
|
'com.google.appengine.api.datastore.Key',
|
||||||
'org.apache.log.Hierarchy',
|
'com.google.appengine.api.datastore.KeyFactory',
|
||||||
'org.apache.log.Logger',
|
'com.google.appengine.api.datastore.PreparedQuery',
|
||||||
|
'com.google.appengine.api.datastore.Query',
|
||||||
|
'com.google.appengine.api.memcache.Expiration',
|
||||||
|
'com.google.appengine.api.memcache.MemcacheService',
|
||||||
|
'com.google.appengine.api.memcache.MemcacheServiceFactory',
|
||||||
|
'com.google.appengine.api.urlfetch.FetchOptions$Builder',
|
||||||
|
'com.google.appengine.api.urlfetch.FetchOptions',
|
||||||
|
'com.google.appengine.api.urlfetch.HTTPHeader',
|
||||||
|
'com.google.appengine.api.urlfetch.HTTPMethod',
|
||||||
|
'com.google.appengine.api.urlfetch.HTTPRequest',
|
||||||
|
'com.google.appengine.api.urlfetch.HTTPResponse',
|
||||||
|
'com.google.appengine.api.urlfetch.URLFetchService',
|
||||||
|
'com.google.appengine.api.urlfetch.URLFetchServiceFactory',
|
||||||
|
'com.google.gson.Gson',
|
||||||
|
'com.google.gson.GsonBuilder',
|
||||||
|
'com.google.gson.TypeAdapter',
|
||||||
|
'com.google.gson.stream.JsonReader',
|
||||||
|
'com.google.gson.stream.JsonWriter',
|
||||||
|
'com.google.iam.v1.Binding$Builder',
|
||||||
|
'com.google.iam.v1.Binding',
|
||||||
|
'com.google.iam.v1.Policy$Builder',
|
||||||
|
'com.google.iam.v1.Policy',
|
||||||
|
'com.google.protobuf.AbstractMessageLite$Builder',
|
||||||
|
'com.google.protobuf.AbstractParser',
|
||||||
|
'com.google.protobuf.Any$Builder',
|
||||||
|
'com.google.protobuf.Any',
|
||||||
|
'com.google.protobuf.AnyOrBuilder',
|
||||||
|
'com.google.protobuf.AnyProto',
|
||||||
|
'com.google.protobuf.Api$Builder',
|
||||||
|
'com.google.protobuf.Api',
|
||||||
|
'com.google.protobuf.ApiOrBuilder',
|
||||||
|
'com.google.protobuf.ApiProto',
|
||||||
|
'com.google.protobuf.ByteString',
|
||||||
|
'com.google.protobuf.CodedInputStream',
|
||||||
|
'com.google.protobuf.CodedOutputStream',
|
||||||
|
'com.google.protobuf.DescriptorProtos',
|
||||||
|
'com.google.protobuf.Descriptors$Descriptor',
|
||||||
|
'com.google.protobuf.Descriptors$EnumDescriptor',
|
||||||
|
'com.google.protobuf.Descriptors$EnumValueDescriptor',
|
||||||
|
'com.google.protobuf.Descriptors$FieldDescriptor',
|
||||||
|
'com.google.protobuf.Descriptors$FileDescriptor$InternalDescriptorAssigner',
|
||||||
|
'com.google.protobuf.Descriptors$FileDescriptor',
|
||||||
|
'com.google.protobuf.Descriptors$OneofDescriptor',
|
||||||
|
'com.google.protobuf.Duration$Builder',
|
||||||
|
'com.google.protobuf.Duration',
|
||||||
|
'com.google.protobuf.DurationOrBuilder',
|
||||||
|
'com.google.protobuf.DurationProto',
|
||||||
|
'com.google.protobuf.EmptyProto',
|
||||||
|
'com.google.protobuf.Enum$Builder',
|
||||||
|
'com.google.protobuf.Enum',
|
||||||
|
'com.google.protobuf.EnumOrBuilder',
|
||||||
|
'com.google.protobuf.ExtensionRegistry',
|
||||||
|
'com.google.protobuf.ExtensionRegistryLite',
|
||||||
|
'com.google.protobuf.FloatValue$Builder',
|
||||||
|
'com.google.protobuf.FloatValue',
|
||||||
|
'com.google.protobuf.FloatValueOrBuilder',
|
||||||
|
'com.google.protobuf.GeneratedMessage$GeneratedExtension',
|
||||||
|
'com.google.protobuf.GeneratedMessage',
|
||||||
|
'com.google.protobuf.GeneratedMessageV3$Builder',
|
||||||
|
'com.google.protobuf.GeneratedMessageV3$BuilderParent',
|
||||||
|
'com.google.protobuf.GeneratedMessageV3$FieldAccessorTable',
|
||||||
|
'com.google.protobuf.GeneratedMessageV3',
|
||||||
|
'com.google.protobuf.Internal$EnumLite',
|
||||||
|
'com.google.protobuf.Internal$EnumLiteMap',
|
||||||
|
'com.google.protobuf.Internal',
|
||||||
|
'com.google.protobuf.InvalidProtocolBufferException',
|
||||||
|
'com.google.protobuf.LazyStringArrayList',
|
||||||
|
'com.google.protobuf.LazyStringList',
|
||||||
|
'com.google.protobuf.MapEntry$Builder',
|
||||||
|
'com.google.protobuf.MapEntry',
|
||||||
|
'com.google.protobuf.MapField',
|
||||||
|
'com.google.protobuf.Message',
|
||||||
|
'com.google.protobuf.MessageOrBuilder',
|
||||||
|
'com.google.protobuf.Parser',
|
||||||
|
'com.google.protobuf.ProtocolMessageEnum',
|
||||||
|
'com.google.protobuf.ProtocolStringList',
|
||||||
|
'com.google.protobuf.RepeatedFieldBuilderV3',
|
||||||
|
'com.google.protobuf.SingleFieldBuilderV3',
|
||||||
|
'com.google.protobuf.Struct$Builder',
|
||||||
|
'com.google.protobuf.Struct',
|
||||||
|
'com.google.protobuf.StructOrBuilder',
|
||||||
|
'com.google.protobuf.StructProto',
|
||||||
|
'com.google.protobuf.Timestamp$Builder',
|
||||||
|
'com.google.protobuf.Timestamp',
|
||||||
|
'com.google.protobuf.TimestampProto',
|
||||||
|
'com.google.protobuf.Type$Builder',
|
||||||
|
'com.google.protobuf.Type',
|
||||||
|
'com.google.protobuf.TypeOrBuilder',
|
||||||
|
'com.google.protobuf.TypeProto',
|
||||||
|
'com.google.protobuf.UInt32Value$Builder',
|
||||||
|
'com.google.protobuf.UInt32Value',
|
||||||
|
'com.google.protobuf.UInt32ValueOrBuilder',
|
||||||
|
'com.google.protobuf.UnknownFieldSet$Builder',
|
||||||
|
'com.google.protobuf.UnknownFieldSet',
|
||||||
|
'com.google.protobuf.WireFormat$FieldType',
|
||||||
|
'com.google.protobuf.WrappersProto',
|
||||||
|
'com.google.protobuf.util.Timestamps',
|
||||||
|
'org.apache.http.ConnectionReuseStrategy',
|
||||||
|
'org.apache.http.Header',
|
||||||
|
'org.apache.http.HttpEntity',
|
||||||
|
'org.apache.http.HttpEntityEnclosingRequest',
|
||||||
|
'org.apache.http.HttpHost',
|
||||||
|
'org.apache.http.HttpRequest',
|
||||||
|
'org.apache.http.HttpResponse',
|
||||||
|
'org.apache.http.HttpVersion',
|
||||||
|
'org.apache.http.RequestLine',
|
||||||
|
'org.apache.http.StatusLine',
|
||||||
|
'org.apache.http.client.AuthenticationHandler',
|
||||||
|
'org.apache.http.client.HttpClient',
|
||||||
|
'org.apache.http.client.HttpRequestRetryHandler',
|
||||||
|
'org.apache.http.client.RedirectHandler',
|
||||||
|
'org.apache.http.client.RequestDirector',
|
||||||
|
'org.apache.http.client.UserTokenHandler',
|
||||||
|
'org.apache.http.client.methods.HttpDelete',
|
||||||
|
'org.apache.http.client.methods.HttpEntityEnclosingRequestBase',
|
||||||
|
'org.apache.http.client.methods.HttpGet',
|
||||||
|
'org.apache.http.client.methods.HttpHead',
|
||||||
|
'org.apache.http.client.methods.HttpOptions',
|
||||||
|
'org.apache.http.client.methods.HttpPost',
|
||||||
|
'org.apache.http.client.methods.HttpPut',
|
||||||
|
'org.apache.http.client.methods.HttpRequestBase',
|
||||||
|
'org.apache.http.client.methods.HttpTrace',
|
||||||
|
'org.apache.http.conn.ClientConnectionManager',
|
||||||
|
'org.apache.http.conn.ConnectionKeepAliveStrategy',
|
||||||
|
'org.apache.http.conn.params.ConnManagerParams',
|
||||||
|
'org.apache.http.conn.params.ConnPerRouteBean',
|
||||||
|
'org.apache.http.conn.params.ConnRouteParams',
|
||||||
|
'org.apache.http.conn.routing.HttpRoutePlanner',
|
||||||
|
'org.apache.http.conn.scheme.PlainSocketFactory',
|
||||||
|
'org.apache.http.conn.scheme.Scheme',
|
||||||
|
'org.apache.http.conn.scheme.SchemeRegistry',
|
||||||
|
'org.apache.http.conn.ssl.SSLSocketFactory',
|
||||||
|
'org.apache.http.conn.ssl.X509HostnameVerifier',
|
||||||
|
'org.apache.http.entity.AbstractHttpEntity',
|
||||||
|
'org.apache.http.impl.client.DefaultHttpClient',
|
||||||
|
'org.apache.http.impl.client.DefaultHttpRequestRetryHandler',
|
||||||
|
'org.apache.http.impl.conn.ProxySelectorRoutePlanner',
|
||||||
|
'org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager',
|
||||||
|
'org.apache.http.message.BasicHttpResponse',
|
||||||
|
'org.apache.http.params.BasicHttpParams',
|
||||||
|
'org.apache.http.params.HttpConnectionParams',
|
||||||
|
'org.apache.http.params.HttpParams',
|
||||||
|
'org.apache.http.params.HttpProtocolParams',
|
||||||
|
'org.apache.http.protocol.HttpContext',
|
||||||
|
'org.apache.http.protocol.HttpProcessor',
|
||||||
|
'org.apache.http.protocol.HttpRequestExecutor'
|
||||||
]
|
]
|
||||||
|
|
||||||
check {
|
check {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
7e537338d40a57ad469239acb6d828fa544fb52b
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2016, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1 +0,0 @@
|
||||||
4b95f4897fa13f2cd904aee711aeafc0c5295cd8
|
|
|
@ -1 +0,0 @@
|
||||||
f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f
|
|
|
@ -0,0 +1 @@
|
||||||
|
36ab73c0b5d4a67447eb89a3174cc76ced150bd1
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2016, Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1 @@
|
||||||
|
cb4bafbfd45b9d24efbb6138a31e37918fac015f
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2016, Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2014, Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1 @@
|
||||||
|
25e0f45f3b3d1b4fccc8944845e51a7a4f359652
|
|
@ -0,0 +1 @@
|
||||||
|
c0fe3a39b0f28d59de1986b3c50f018cd7cb9ec2
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1 @@
|
||||||
|
c0e88c78ce17c92d76bf46345faf3fa68833b216
|
|
@ -0,0 +1 @@
|
||||||
|
7b4559a9513abd98da50958c56a10f8ae00cb0f7
|
|
@ -0,0 +1 @@
|
||||||
|
226019ae816b42c59f1b06999aeeb73722b87200
|
|
@ -0,0 +1 @@
|
||||||
|
0eda0d0f758c1cc525866e52e1226c4eb579d130
|
|
@ -0,0 +1 @@
|
||||||
|
a72ea3a197937ef63a893e73df312dac0d813663
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2014, Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1 @@
|
||||||
|
28b0836f48c9705abf73829bbc536dba29a1329a
|
|
@ -0,0 +1 @@
|
||||||
|
89507701249388e1ed5ddcf8c41f4ce1be7831ef
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -1 +0,0 @@
|
||||||
733db77aa8d9b2d68015189df76ab06304406e50
|
|
|
@ -1 +0,0 @@
|
||||||
e7501a1b34325abb00d17dde96150604a0658b54
|
|
|
@ -0,0 +1 @@
|
||||||
|
3c304d70f42f832e0a86d45bd437f692129299a4
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1 @@
|
||||||
|
54689fbf750a7f26e34fa1f1f96b883c53f51486
|
|
@ -0,0 +1 @@
|
||||||
|
82e572b41e81ecf58d0d1e9a3953a05aa8f9c84b
|
|
@ -0,0 +1 @@
|
||||||
|
b3282312ba82536fc9a7778cabfde149a875e877
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1 @@
|
||||||
|
89dcc04a7e028c3c963413a71f950703cf51f057
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of JSR-310 nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
|
@ -69,7 +69,6 @@ task googleCloudStorageFixture(type: AntFixture) {
|
||||||
|
|
||||||
/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
|
/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
|
||||||
task createServiceAccountFile() {
|
task createServiceAccountFile() {
|
||||||
dependsOn googleCloudStorageFixture
|
|
||||||
doLast {
|
doLast {
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
keyPairGenerator.initialize(1024)
|
keyPairGenerator.initialize(1024)
|
||||||
|
@ -83,11 +82,7 @@ task createServiceAccountFile() {
|
||||||
' "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
|
' "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
|
||||||
' "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
|
' "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
|
||||||
' "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
|
' "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
|
||||||
' "client_id": "123456789101112130594",\n' +
|
' "client_id": "123456789101112130594"\n' +
|
||||||
" \"auth_uri\": \"http://${googleCloudStorageFixture.addressAndPort}/o/oauth2/auth\",\n" +
|
|
||||||
" \"token_uri\": \"http://${googleCloudStorageFixture.addressAndPort}/o/oauth2/token\",\n" +
|
|
||||||
' "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",\n' +
|
|
||||||
' "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/integration_test%40appspot.gserviceaccount.com"\n' +
|
|
||||||
'}', 'UTF-8')
|
'}', 'UTF-8')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +104,7 @@ integTestCluster {
|
||||||
dependsOn createServiceAccountFile, googleCloudStorageFixture
|
dependsOn createServiceAccountFile, googleCloudStorageFixture
|
||||||
/* Use a closure on the string to delay evaluation until tests are executed */
|
/* Use a closure on the string to delay evaluation until tests are executed */
|
||||||
setting 'gcs.client.integration_test.endpoint', "http://${ -> googleCloudStorageFixture.addressAndPort }"
|
setting 'gcs.client.integration_test.endpoint', "http://${ -> googleCloudStorageFixture.addressAndPort }"
|
||||||
|
setting 'gcs.client.integration_test.token_uri', "http://${ -> googleCloudStorageFixture.addressAndPort }/o/oauth2/token"
|
||||||
} else {
|
} else {
|
||||||
println "Using an external service to test the repository-gcs plugin"
|
println "Using an external service to test the repository-gcs plugin"
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,18 @@ import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
@ -52,7 +57,7 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
*/
|
*/
|
||||||
public class GoogleCloudStorageTestServer {
|
public class GoogleCloudStorageTestServer {
|
||||||
|
|
||||||
private static byte[] EMPTY_BYTE = new byte[0];
|
private static final byte[] EMPTY_BYTE = new byte[0];
|
||||||
|
|
||||||
/** List of the buckets stored on this test server **/
|
/** List of the buckets stored on this test server **/
|
||||||
private final Map<String, Bucket> buckets = ConcurrentCollections.newConcurrentMap();
|
private final Map<String, Bucket> buckets = ConcurrentCollections.newConcurrentMap();
|
||||||
|
@ -63,13 +68,6 @@ public class GoogleCloudStorageTestServer {
|
||||||
/** Server endpoint **/
|
/** Server endpoint **/
|
||||||
private final String endpoint;
|
private final String endpoint;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link GoogleCloudStorageTestServer} with the default endpoint
|
|
||||||
*/
|
|
||||||
GoogleCloudStorageTestServer() {
|
|
||||||
this("https://www.googleapis.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link GoogleCloudStorageTestServer} with a custom endpoint
|
* Creates a {@link GoogleCloudStorageTestServer} with a custom endpoint
|
||||||
*/
|
*/
|
||||||
|
@ -87,29 +85,6 @@ public class GoogleCloudStorageTestServer {
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a Google Cloud Storage response for the given request
|
|
||||||
*
|
|
||||||
* @param method the HTTP method of the request
|
|
||||||
* @param url the HTTP URL of the request
|
|
||||||
* @param headers the HTTP headers of the request
|
|
||||||
* @param body the HTTP request body
|
|
||||||
* @return a {@link Response}
|
|
||||||
*
|
|
||||||
* @throws IOException if something goes wrong
|
|
||||||
*/
|
|
||||||
public Response handle(final String method,
|
|
||||||
final String url,
|
|
||||||
final Map<String, List<String>> headers,
|
|
||||||
byte[] body) throws IOException {
|
|
||||||
|
|
||||||
final int questionMark = url.indexOf('?');
|
|
||||||
if (questionMark == -1) {
|
|
||||||
return handle(method, url, null, headers, body);
|
|
||||||
}
|
|
||||||
return handle(method, url.substring(0, questionMark), url.substring(questionMark + 1), headers, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Google Cloud Storage response for the given request
|
* Returns a Google Cloud Storage response for the given request
|
||||||
*
|
*
|
||||||
|
@ -165,7 +140,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/buckets/get
|
// https://cloud.google.com/storage/docs/json_api/v1/buckets/get
|
||||||
handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}", (params, headers, body) -> {
|
handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}", (params, headers, body) -> {
|
||||||
String name = params.get("bucket");
|
final String name = params.get("bucket");
|
||||||
if (Strings.hasText(name) == false) {
|
if (Strings.hasText(name) == false) {
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "bucket name is missing");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "bucket name is missing");
|
||||||
}
|
}
|
||||||
|
@ -181,7 +156,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/get
|
// https://cloud.google.com/storage/docs/json_api/v1/objects/get
|
||||||
handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
|
handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
|
||||||
String objectName = params.get("object");
|
final String objectName = params.get("object");
|
||||||
if (Strings.hasText(objectName) == false) {
|
if (Strings.hasText(objectName) == false) {
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
|
||||||
}
|
}
|
||||||
|
@ -191,7 +166,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
return newError(RestStatus.NOT_FOUND, "bucket not found");
|
return newError(RestStatus.NOT_FOUND, "bucket not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, byte[]> object : bucket.objects.entrySet()) {
|
for (final Map.Entry<String, byte[]> object : bucket.objects.entrySet()) {
|
||||||
if (object.getKey().equals(objectName)) {
|
if (object.getKey().equals(objectName)) {
|
||||||
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectName, object.getValue()));
|
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectName, object.getValue()));
|
||||||
}
|
}
|
||||||
|
@ -203,7 +178,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/delete
|
// https://cloud.google.com/storage/docs/json_api/v1/objects/delete
|
||||||
handlers.insert("DELETE " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
|
handlers.insert("DELETE " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
|
||||||
String objectName = params.get("object");
|
final String objectName = params.get("object");
|
||||||
if (Strings.hasText(objectName) == false) {
|
if (Strings.hasText(objectName) == false) {
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
|
||||||
}
|
}
|
||||||
|
@ -224,25 +199,149 @@ public class GoogleCloudStorageTestServer {
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/insert
|
// https://cloud.google.com/storage/docs/json_api/v1/objects/insert
|
||||||
handlers.insert("POST " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> {
|
handlers.insert("POST " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> {
|
||||||
if ("resumable".equals(params.get("uploadType")) == false) {
|
final String uploadType = params.get("uploadType");
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload type must be resumable");
|
if ("resumable".equals(uploadType)) {
|
||||||
}
|
final String objectName = params.get("name");
|
||||||
|
if (Strings.hasText(objectName) == false) {
|
||||||
final String objectName = params.get("name");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
|
||||||
if (Strings.hasText(objectName) == false) {
|
}
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
|
final Bucket bucket = buckets.get(params.get("bucket"));
|
||||||
}
|
if (bucket == null) {
|
||||||
|
return newError(RestStatus.NOT_FOUND, "bucket not found");
|
||||||
final Bucket bucket = buckets.get(params.get("bucket"));
|
}
|
||||||
if (bucket == null) {
|
if (bucket.objects.putIfAbsent(objectName, EMPTY_BYTE) == null) {
|
||||||
return newError(RestStatus.NOT_FOUND, "bucket not found");
|
final String location = endpoint + "/upload/storage/v1/b/" + bucket.name + "/o?uploadType=resumable&upload_id="
|
||||||
}
|
+ objectName;
|
||||||
|
return new Response(RestStatus.CREATED, singletonMap("Location", location), XContentType.JSON.mediaType(), EMPTY_BYTE);
|
||||||
if (bucket.objects.put(objectName, EMPTY_BYTE) == null) {
|
} else {
|
||||||
String location = endpoint + "/upload/storage/v1/b/" + bucket.name + "/o?uploadType=resumable&upload_id=" + objectName;
|
return newError(RestStatus.CONFLICT, "object already exist");
|
||||||
return new Response(RestStatus.CREATED, singletonMap("Location", location), XContentType.JSON.mediaType(), EMPTY_BYTE);
|
}
|
||||||
|
} else if ("multipart".equals(uploadType)) {
|
||||||
|
/*
|
||||||
|
* A multipart/related request body looks like this (note the binary dump inside a text blob! nice!):
|
||||||
|
* --__END_OF_PART__
|
||||||
|
* Content-Length: 135
|
||||||
|
* Content-Type: application/json; charset=UTF-8
|
||||||
|
* content-transfer-encoding: binary
|
||||||
|
*
|
||||||
|
* {"bucket":"bucket_test","crc32c":"7XacHQ==","md5Hash":"fVztGkklMlUamsSmJK7W+w==",
|
||||||
|
* "name":"tests-KEwE3bU4TuyetBgQIghmUw/master.dat-temp"}
|
||||||
|
* --__END_OF_PART__
|
||||||
|
* content-transfer-encoding: binary
|
||||||
|
*
|
||||||
|
* KEwE3bU4TuyetBgQIghmUw
|
||||||
|
* --__END_OF_PART__--
|
||||||
|
*/
|
||||||
|
String boundary = "__END_OF_PART__";
|
||||||
|
// Determine the multipart boundary
|
||||||
|
final List<String> contentTypes = headers.getOrDefault("Content-Type", headers.get("Content-type"));
|
||||||
|
if (contentTypes != null) {
|
||||||
|
final String contentType = contentTypes.get(0);
|
||||||
|
if ((contentType != null) && contentType.contains("multipart/related; boundary=")) {
|
||||||
|
boundary = contentType.replace("multipart/related; boundary=", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputStream inputStreamBody = new ByteArrayInputStream(body);
|
||||||
|
final List<String> contentEncodings = headers.getOrDefault("Content-Encoding", headers.get("Content-encoding"));
|
||||||
|
if (contentEncodings != null) {
|
||||||
|
if (contentEncodings.stream().anyMatch(x -> "gzip".equalsIgnoreCase(x))) {
|
||||||
|
inputStreamBody = new GZIPInputStream(inputStreamBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Read line by line ?both? parts of the multipart. Decoding headers as
|
||||||
|
// IS_8859_1 is safe.
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStreamBody, StandardCharsets.ISO_8859_1))) {
|
||||||
|
String line;
|
||||||
|
// read first part delimiter
|
||||||
|
line = reader.readLine();
|
||||||
|
if ((line == null) || (line.equals("--" + boundary) == false)) {
|
||||||
|
return newError(RestStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error parsing multipart request. Does not start with the part delimiter.");
|
||||||
|
}
|
||||||
|
final Map<String, List<String>> firstPartHeaders = new HashMap<>();
|
||||||
|
// Reads the first part's headers, if any
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (line.equals("\r\n") || (line.length() == 0)) {
|
||||||
|
// end of headers
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
final String[] header = line.split(":", 2);
|
||||||
|
firstPartHeaders.put(header[0], singletonList(header[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<String> firstPartContentTypes = firstPartHeaders.getOrDefault("Content-Type",
|
||||||
|
firstPartHeaders.get("Content-type"));
|
||||||
|
if ((firstPartContentTypes == null)
|
||||||
|
|| (firstPartContentTypes.stream().noneMatch(x -> x.contains("application/json")))) {
|
||||||
|
return newError(RestStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error parsing multipart request. Metadata part expected to have the \"application/json\" content type.");
|
||||||
|
}
|
||||||
|
// read metadata part, a single line
|
||||||
|
line = reader.readLine();
|
||||||
|
final byte[] metadata = line.getBytes(StandardCharsets.ISO_8859_1);
|
||||||
|
if ((firstPartContentTypes != null) && (firstPartContentTypes.stream().anyMatch((x -> x.contains("charset=utf-8"))))) {
|
||||||
|
// decode as utf-8
|
||||||
|
line = new String(metadata, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
final Matcher objectNameMatcher = Pattern.compile("\"name\":\"([^\"]*)\"").matcher(line);
|
||||||
|
objectNameMatcher.find();
|
||||||
|
final String objectName = objectNameMatcher.group(1);
|
||||||
|
final Matcher bucketNameMatcher = Pattern.compile("\"bucket\":\"([^\"]*)\"").matcher(line);
|
||||||
|
bucketNameMatcher.find();
|
||||||
|
final String bucketName = bucketNameMatcher.group(1);
|
||||||
|
// read second part delimiter
|
||||||
|
line = reader.readLine();
|
||||||
|
if ((line == null) || (line.equals("--" + boundary) == false)) {
|
||||||
|
return newError(RestStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error parsing multipart request. Second part does not start with delimiter. "
|
||||||
|
+ "Is the metadata multi-line?");
|
||||||
|
}
|
||||||
|
final Map<String, List<String>> secondPartHeaders = new HashMap<>();
|
||||||
|
// Reads the second part's headers, if any
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (line.equals("\r\n") || (line.length() == 0)) {
|
||||||
|
// end of headers
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
final String[] header = line.split(":", 2);
|
||||||
|
secondPartHeaders.put(header[0], singletonList(header[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<String> secondPartTransferEncoding = secondPartHeaders.getOrDefault("Content-Transfer-Encoding",
|
||||||
|
secondPartHeaders.get("content-transfer-encoding"));
|
||||||
|
if ((secondPartTransferEncoding == null)
|
||||||
|
|| (secondPartTransferEncoding.stream().noneMatch(x -> x.contains("binary")))) {
|
||||||
|
return newError(RestStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error parsing multipart request. Data part expected to have the \"binary\" content transfer encoding.");
|
||||||
|
}
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
int c;
|
||||||
|
while ((c = reader.read()) != -1) {
|
||||||
|
// one char to one byte, because of the ISO_8859_1 encoding
|
||||||
|
baos.write(c);
|
||||||
|
}
|
||||||
|
final byte[] temp = baos.toByteArray();
|
||||||
|
final byte[] trailingEnding = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.ISO_8859_1);
|
||||||
|
// check trailing
|
||||||
|
for (int i = trailingEnding.length - 1; i >= 0; i--) {
|
||||||
|
if (trailingEnding[i] != temp[(temp.length - trailingEnding.length) + i]) {
|
||||||
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "Error parsing multipart request.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Bucket bucket = buckets.get(bucketName);
|
||||||
|
if (bucket == null) {
|
||||||
|
return newError(RestStatus.NOT_FOUND, "bucket not found");
|
||||||
|
}
|
||||||
|
final byte[] objectData = Arrays.copyOf(temp, temp.length - trailingEnding.length);
|
||||||
|
if ((objectName != null) && (bucketName != null) && (objectData != null)) {
|
||||||
|
bucket.objects.put(objectName, objectData);
|
||||||
|
return new Response(RestStatus.OK, emptyMap(), XContentType.JSON.mediaType(), metadata);
|
||||||
|
} else {
|
||||||
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "error parsing multipart request");
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return newError(RestStatus.CONFLICT, "object already exist");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload type must be resumable or multipart");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -250,7 +349,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
|
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
|
||||||
handlers.insert("PUT " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> {
|
handlers.insert("PUT " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> {
|
||||||
String objectId = params.get("upload_id");
|
final String objectId = params.get("upload_id");
|
||||||
if (Strings.hasText(objectId) == false) {
|
if (Strings.hasText(objectId) == false) {
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload id is missing");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload id is missing");
|
||||||
}
|
}
|
||||||
|
@ -268,38 +367,46 @@ public class GoogleCloudStorageTestServer {
|
||||||
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectId, body));
|
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectId, body));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy Object
|
// Rewrite or Copy Object
|
||||||
//
|
//
|
||||||
|
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/copy
|
// https://cloud.google.com/storage/docs/json_api/v1/objects/copy
|
||||||
handlers.insert("POST " + endpoint + "/storage/v1/b/{srcBucket}/o/{src}/copyTo/b/{destBucket}/o/{dest}", (params, headers, body)-> {
|
handlers.insert("POST " + endpoint + "/storage/v1/b/{srcBucket}/o/{src}/{action}/b/{destBucket}/o/{dest}",
|
||||||
String source = params.get("src");
|
(params, headers, body) -> {
|
||||||
if (Strings.hasText(source) == false) {
|
final String action = params.get("action");
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "source object name is missing");
|
if ((action.equals("rewriteTo") == false) && (action.equals("copyTo") == false)) {
|
||||||
}
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "Action not implemented. None of \"rewriteTo\" or \"copyTo\".");
|
||||||
|
}
|
||||||
final Bucket srcBucket = buckets.get(params.get("srcBucket"));
|
final String source = params.get("src");
|
||||||
if (srcBucket == null) {
|
if (Strings.hasText(source) == false) {
|
||||||
return newError(RestStatus.NOT_FOUND, "source bucket not found");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "source object name is missing");
|
||||||
}
|
}
|
||||||
|
final Bucket srcBucket = buckets.get(params.get("srcBucket"));
|
||||||
String dest = params.get("dest");
|
if (srcBucket == null) {
|
||||||
if (Strings.hasText(dest) == false) {
|
return newError(RestStatus.NOT_FOUND, "source bucket not found");
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "destination object name is missing");
|
}
|
||||||
}
|
final String dest = params.get("dest");
|
||||||
|
if (Strings.hasText(dest) == false) {
|
||||||
final Bucket destBucket = buckets.get(params.get("destBucket"));
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "destination object name is missing");
|
||||||
if (destBucket == null) {
|
}
|
||||||
return newError(RestStatus.NOT_FOUND, "destination bucket not found");
|
final Bucket destBucket = buckets.get(params.get("destBucket"));
|
||||||
}
|
if (destBucket == null) {
|
||||||
|
return newError(RestStatus.NOT_FOUND, "destination bucket not found");
|
||||||
final byte[] sourceBytes = srcBucket.objects.get(source);
|
}
|
||||||
if (sourceBytes == null) {
|
final byte[] sourceBytes = srcBucket.objects.get(source);
|
||||||
return newError(RestStatus.NOT_FOUND, "source object not found");
|
if (sourceBytes == null) {
|
||||||
}
|
return newError(RestStatus.NOT_FOUND, "source object not found");
|
||||||
|
}
|
||||||
destBucket.objects.put(dest, sourceBytes);
|
destBucket.objects.put(dest, sourceBytes);
|
||||||
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(destBucket.name, dest, sourceBytes));
|
if (action.equals("rewriteTo")) {
|
||||||
});
|
final XContentBuilder respBuilder = jsonBuilder();
|
||||||
|
buildRewriteResponse(respBuilder, destBucket.name, dest, sourceBytes.length);
|
||||||
|
return newResponse(RestStatus.OK, emptyMap(), respBuilder);
|
||||||
|
} else {
|
||||||
|
assert action.equals("copyTo");
|
||||||
|
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(destBucket.name, dest, sourceBytes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// List Objects
|
// List Objects
|
||||||
//
|
//
|
||||||
|
@ -317,8 +424,8 @@ public class GoogleCloudStorageTestServer {
|
||||||
builder.startArray("items");
|
builder.startArray("items");
|
||||||
|
|
||||||
final String prefixParam = params.get("prefix");
|
final String prefixParam = params.get("prefix");
|
||||||
for (Map.Entry<String, byte[]> object : bucket.objects.entrySet()) {
|
for (final Map.Entry<String, byte[]> object : bucket.objects.entrySet()) {
|
||||||
if (prefixParam != null && object.getKey().startsWith(prefixParam) == false) {
|
if ((prefixParam != null) && (object.getKey().startsWith(prefixParam) == false)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
buildObjectResource(builder, bucket.name, object.getKey(), object.getValue());
|
buildObjectResource(builder, bucket.name, object.getKey(), object.getValue());
|
||||||
|
@ -333,7 +440,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/request-body
|
// https://cloud.google.com/storage/docs/request-body
|
||||||
handlers.insert("GET " + endpoint + "/download/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
|
handlers.insert("GET " + endpoint + "/download/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
|
||||||
String object = params.get("object");
|
final String object = params.get("object");
|
||||||
if (Strings.hasText(object) == false) {
|
if (Strings.hasText(object) == false) {
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object id is missing");
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object id is missing");
|
||||||
}
|
}
|
||||||
|
@ -353,7 +460,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
// Batch
|
// Batch
|
||||||
//
|
//
|
||||||
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
|
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
|
||||||
handlers.insert("POST " + endpoint + "/batch", (params, headers, body) -> {
|
handlers.insert("POST " + endpoint + "/batch/storage/v1", (params, headers, body) -> {
|
||||||
final List<Response> batchedResponses = new ArrayList<>();
|
final List<Response> batchedResponses = new ArrayList<>();
|
||||||
|
|
||||||
// A batch request body looks like this:
|
// A batch request body looks like this:
|
||||||
|
@ -385,7 +492,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
final List<String> contentTypes = headers.getOrDefault("Content-Type", headers.get("Content-type"));
|
final List<String> contentTypes = headers.getOrDefault("Content-Type", headers.get("Content-type"));
|
||||||
if (contentTypes != null) {
|
if (contentTypes != null) {
|
||||||
final String contentType = contentTypes.get(0);
|
final String contentType = contentTypes.get(0);
|
||||||
if (contentType != null && contentType.contains("multipart/mixed; boundary=")) {
|
if ((contentType != null) && contentType.contains("multipart/mixed; boundary=")) {
|
||||||
boundary = contentType.replace("multipart/mixed; boundary=", "");
|
boundary = contentType.replace("multipart/mixed; boundary=", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,25 +505,25 @@ public class GoogleCloudStorageTestServer {
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
// Start of a batched request
|
// Start of a batched request
|
||||||
if (line.equals("--" + boundary)) {
|
if (line.equals("--" + boundary)) {
|
||||||
Map<String, List<String>> batchedHeaders = new HashMap<>();
|
final Map<String, List<String>> batchedHeaders = new HashMap<>();
|
||||||
|
|
||||||
// Reads the headers, if any
|
// Reads the headers, if any
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
if (line.equals("\r\n") || line.length() == 0) {
|
if (line.equals("\r\n") || (line.length() == 0)) {
|
||||||
// end of headers
|
// end of headers
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
String[] header = line.split(":", 2);
|
final String[] header = line.split(":", 2);
|
||||||
batchedHeaders.put(header[0], singletonList(header[1]));
|
batchedHeaders.put(header[0], singletonList(header[1]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the method and URL
|
// Reads the method and URL
|
||||||
line = reader.readLine();
|
line = reader.readLine();
|
||||||
String batchedUrl = line.substring(0, line.lastIndexOf(' '));
|
final String batchedUrl = line.substring(0, line.lastIndexOf(' '));
|
||||||
|
|
||||||
final Map<String, String> batchedParams = new HashMap<>();
|
final Map<String, String> batchedParams = new HashMap<>();
|
||||||
int questionMark = batchedUrl.indexOf('?');
|
final int questionMark = batchedUrl.indexOf('?');
|
||||||
if (questionMark != -1) {
|
if (questionMark != -1) {
|
||||||
RestUtils.decodeQueryString(batchedUrl.substring(questionMark + 1), 0, batchedParams);
|
RestUtils.decodeQueryString(batchedUrl.substring(questionMark + 1), 0, batchedParams);
|
||||||
}
|
}
|
||||||
|
@ -424,16 +531,16 @@ public class GoogleCloudStorageTestServer {
|
||||||
// Reads the body
|
// Reads the body
|
||||||
line = reader.readLine();
|
line = reader.readLine();
|
||||||
byte[] batchedBody = new byte[0];
|
byte[] batchedBody = new byte[0];
|
||||||
if (line != null || line.startsWith("--" + boundary) == false) {
|
if ((line != null) || (line.startsWith("--" + boundary) == false)) {
|
||||||
batchedBody = line.getBytes(StandardCharsets.UTF_8);
|
batchedBody = line.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executes the batched request
|
// Executes the batched request
|
||||||
RequestHandler handler = handlers.retrieve(batchedUrl, batchedParams);
|
final RequestHandler handler = handlers.retrieve(batchedUrl, batchedParams);
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
try {
|
try {
|
||||||
batchedResponses.add(handler.execute(batchedParams, batchedHeaders, batchedBody));
|
batchedResponses.add(handler.execute(batchedParams, batchedHeaders, batchedBody));
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
batchedResponses.add(newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage()));
|
batchedResponses.add(newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -442,11 +549,11 @@ public class GoogleCloudStorageTestServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can build the response
|
// Now we can build the response
|
||||||
String sep = "--";
|
final String sep = "--";
|
||||||
String line = "\r\n";
|
final String line = "\r\n";
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
for (Response response : batchedResponses) {
|
for (final Response response : batchedResponses) {
|
||||||
builder.append(sep).append(boundary).append(line);
|
builder.append(sep).append(boundary).append(line);
|
||||||
builder.append("Content-Type: application/http").append(line);
|
builder.append("Content-Type: application/http").append(line);
|
||||||
builder.append(line);
|
builder.append(line);
|
||||||
|
@ -465,7 +572,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
builder.append(line);
|
builder.append(line);
|
||||||
builder.append(sep).append(boundary).append(sep);
|
builder.append(sep).append(boundary).append(sep);
|
||||||
|
|
||||||
byte[] content = builder.toString().getBytes(StandardCharsets.UTF_8);
|
final byte[] content = builder.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
return new Response(RestStatus.OK, emptyMap(), "multipart/mixed; boundary=" + boundary, content);
|
return new Response(RestStatus.OK, emptyMap(), "multipart/mixed; boundary=" + boundary, content);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -525,7 +632,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
BytesReference.bytes(xContentBuilder).writeTo(out);
|
BytesReference.bytes(xContentBuilder).writeTo(out);
|
||||||
return new Response(status, headers, XContentType.JSON.mediaType(), out.toByteArray());
|
return new Response(status, headers, XContentType.JSON.mediaType(), out.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
return newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage());
|
return newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,8 +659,8 @@ public class GoogleCloudStorageTestServer {
|
||||||
BytesReference.bytes(builder).writeTo(out);
|
BytesReference.bytes(builder).writeTo(out);
|
||||||
}
|
}
|
||||||
return new Response(status, emptyMap(), XContentType.JSON.mediaType(), out.toByteArray());
|
return new Response(status, emptyMap(), XContentType.JSON.mediaType(), out.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
byte[] bytes = (message != null ? message : "something went wrong").getBytes(StandardCharsets.UTF_8);
|
final byte[] bytes = (message != null ? message : "something went wrong").getBytes(StandardCharsets.UTF_8);
|
||||||
return new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), " text/plain", bytes);
|
return new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), " text/plain", bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,6 +672,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
private static XContentBuilder buildBucketResource(final String name) throws IOException {
|
private static XContentBuilder buildBucketResource(final String name) throws IOException {
|
||||||
return jsonBuilder().startObject()
|
return jsonBuilder().startObject()
|
||||||
.field("kind", "storage#bucket")
|
.field("kind", "storage#bucket")
|
||||||
|
.field("name", name)
|
||||||
.field("id", name)
|
.field("id", name)
|
||||||
.endObject();
|
.endObject();
|
||||||
}
|
}
|
||||||
|
@ -573,8 +681,7 @@ public class GoogleCloudStorageTestServer {
|
||||||
* Storage Object JSON representation as defined in
|
* Storage Object JSON representation as defined in
|
||||||
* https://cloud.google.com/storage/docs/json_api/v1/objects#resource
|
* https://cloud.google.com/storage/docs/json_api/v1/objects#resource
|
||||||
*/
|
*/
|
||||||
private static XContentBuilder buildObjectResource(final String bucket, final String name, final byte[] bytes)
|
private static XContentBuilder buildObjectResource(final String bucket, final String name, final byte[] bytes) throws IOException {
|
||||||
throws IOException {
|
|
||||||
return buildObjectResource(jsonBuilder(), bucket, name, bytes);
|
return buildObjectResource(jsonBuilder(), bucket, name, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,7 +697,32 @@ public class GoogleCloudStorageTestServer {
|
||||||
.field("kind", "storage#object")
|
.field("kind", "storage#object")
|
||||||
.field("id", String.join("/", bucket, name))
|
.field("id", String.join("/", bucket, name))
|
||||||
.field("name", name)
|
.field("name", name)
|
||||||
|
.field("bucket", bucket)
|
||||||
.field("size", String.valueOf(bytes.length))
|
.field("size", String.valueOf(bytes.length))
|
||||||
.endObject();
|
.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the rewrite response as defined by
|
||||||
|
* https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite
|
||||||
|
*/
|
||||||
|
private static XContentBuilder buildRewriteResponse(final XContentBuilder builder,
|
||||||
|
final String destBucket,
|
||||||
|
final String dest,
|
||||||
|
final int byteSize) throws IOException {
|
||||||
|
builder.startObject()
|
||||||
|
.field("kind", "storage#rewriteResponse")
|
||||||
|
.field("totalBytesRewritten", String.valueOf(byteSize))
|
||||||
|
.field("objectSize", String.valueOf(byteSize))
|
||||||
|
.field("done", true)
|
||||||
|
.startObject("resource")
|
||||||
|
.field("kind", "storage#object")
|
||||||
|
.field("id", String.join("/", destBucket, dest))
|
||||||
|
.field("name", dest)
|
||||||
|
.field("bucket", destBucket)
|
||||||
|
.field("size", String.valueOf(byteSize))
|
||||||
|
.endObject()
|
||||||
|
.endObject();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,59 +19,55 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.googleapis.batch.BatchRequest;
|
import com.google.cloud.ReadChannel;
|
||||||
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
|
import com.google.cloud.WriteChannel;
|
||||||
import com.google.api.client.googleapis.json.GoogleJsonError;
|
import com.google.cloud.storage.Blob;
|
||||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
import com.google.cloud.storage.BlobId;
|
||||||
import com.google.api.client.http.HttpHeaders;
|
import com.google.cloud.storage.BlobInfo;
|
||||||
import com.google.api.client.http.InputStreamContent;
|
import com.google.cloud.storage.Bucket;
|
||||||
import com.google.api.services.storage.Storage;
|
import com.google.cloud.storage.Storage;
|
||||||
import com.google.api.services.storage.model.Bucket;
|
import com.google.cloud.storage.Storage.BlobListOption;
|
||||||
import com.google.api.services.storage.model.Objects;
|
import com.google.cloud.storage.Storage.CopyRequest;
|
||||||
import com.google.api.services.storage.model.StorageObject;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.blobstore.BlobContainer;
|
import org.elasticsearch.common.blobstore.BlobContainer;
|
||||||
import org.elasticsearch.common.blobstore.BlobMetaData;
|
import org.elasticsearch.common.blobstore.BlobMetaData;
|
||||||
import org.elasticsearch.common.blobstore.BlobPath;
|
import org.elasticsearch.common.blobstore.BlobPath;
|
||||||
import org.elasticsearch.common.blobstore.BlobStore;
|
import org.elasticsearch.common.blobstore.BlobStore;
|
||||||
import org.elasticsearch.common.blobstore.BlobStoreException;
|
import org.elasticsearch.common.blobstore.BlobStoreException;
|
||||||
import org.elasticsearch.common.blobstore.support.PlainBlobMetaData;
|
import org.elasticsearch.common.blobstore.support.PlainBlobMetaData;
|
||||||
|
import org.elasticsearch.common.collect.MapBuilder;
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.concurrent.CountDown;
|
import org.elasticsearch.core.internal.io.Streams;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Spliterator;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
|
|
||||||
|
|
||||||
class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore {
|
class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore {
|
||||||
|
|
||||||
/**
|
// The recommended maximum size of a blob that should be uploaded in a single
|
||||||
* Google Cloud Storage batch requests are limited to 1000 operations
|
// request. Larger files should be uploaded over multiple requests (this is
|
||||||
**/
|
// called "resumable upload")
|
||||||
private static final int MAX_BATCHING_REQUESTS = 999;
|
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
|
||||||
|
private static final int LARGE_BLOB_THRESHOLD_BYTE_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
private final Storage client;
|
private final Storage storage;
|
||||||
private final String bucket;
|
private final String bucket;
|
||||||
|
|
||||||
GoogleCloudStorageBlobStore(Settings settings, String bucket, Storage storageClient) {
|
GoogleCloudStorageBlobStore(Settings settings, String bucket, Storage storage) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.bucket = bucket;
|
this.bucket = bucket;
|
||||||
this.client = storageClient;
|
this.storage = storage;
|
||||||
|
|
||||||
if (doesBucketExist(bucket) == false) {
|
if (doesBucketExist(bucket) == false) {
|
||||||
throw new BlobStoreException("Bucket [" + bucket + "] does not exist");
|
throw new BlobStoreException("Bucket [" + bucket + "] does not exist");
|
||||||
}
|
}
|
||||||
|
@ -99,59 +95,45 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
*/
|
*/
|
||||||
boolean doesBucketExist(String bucketName) {
|
boolean doesBucketExist(String bucketName) {
|
||||||
try {
|
try {
|
||||||
return SocketAccess.doPrivilegedIOException(() -> {
|
final Bucket bucket = SocketAccess.doPrivilegedIOException(() -> storage.get(bucketName));
|
||||||
try {
|
return bucket != null;
|
||||||
Bucket bucket = client.buckets().get(bucketName).execute();
|
} catch (final Exception e) {
|
||||||
if (bucket != null) {
|
|
||||||
return Strings.hasText(bucket.getId());
|
|
||||||
}
|
|
||||||
} catch (GoogleJsonResponseException e) {
|
|
||||||
GoogleJsonError error = e.getDetails();
|
|
||||||
if ((e.getStatusCode() == HTTP_NOT_FOUND) || ((error != null) && (error.getCode() == HTTP_NOT_FOUND))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new BlobStoreException("Unable to check if bucket [" + bucketName + "] exists", e);
|
throw new BlobStoreException("Unable to check if bucket [" + bucketName + "] exists", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all blobs in the bucket
|
* List blobs in the bucket under the specified path. The path root is removed.
|
||||||
*
|
*
|
||||||
* @param path base path of the blobs to list
|
* @param path
|
||||||
|
* base path of the blobs to list
|
||||||
* @return a map of blob names and their metadata
|
* @return a map of blob names and their metadata
|
||||||
*/
|
*/
|
||||||
Map<String, BlobMetaData> listBlobs(String path) throws IOException {
|
Map<String, BlobMetaData> listBlobs(String path) throws IOException {
|
||||||
return SocketAccess.doPrivilegedIOException(() -> listBlobsByPath(bucket, path, path));
|
return listBlobsByPrefix(path, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all blobs in the bucket which have a prefix
|
* List all blobs in the bucket which have a prefix
|
||||||
*
|
*
|
||||||
* @param path base path of the blobs to list
|
* @param path
|
||||||
* @param prefix prefix of the blobs to list
|
* base path of the blobs to list. This path is removed from the
|
||||||
* @return a map of blob names and their metadata
|
* names of the blobs returned.
|
||||||
|
* @param prefix
|
||||||
|
* prefix of the blobs to list.
|
||||||
|
* @return a map of blob names and their metadata.
|
||||||
*/
|
*/
|
||||||
Map<String, BlobMetaData> listBlobsByPrefix(String path, String prefix) throws IOException {
|
Map<String, BlobMetaData> listBlobsByPrefix(String path, String prefix) throws IOException {
|
||||||
return SocketAccess.doPrivilegedIOException(() -> listBlobsByPath(bucket, buildKey(path, prefix), path));
|
final String pathPrefix = buildKey(path, prefix);
|
||||||
}
|
final MapBuilder<String, BlobMetaData> mapBuilder = MapBuilder.newMapBuilder();
|
||||||
|
SocketAccess.doPrivilegedVoidIOException(() -> {
|
||||||
/**
|
storage.get(bucket).list(BlobListOption.prefix(pathPrefix)).iterateAll().forEach(blob -> {
|
||||||
* Lists all blobs in a given bucket
|
assert blob.getName().startsWith(path);
|
||||||
*
|
final String suffixName = blob.getName().substring(path.length());
|
||||||
* @param bucketName name of the bucket
|
mapBuilder.put(suffixName, new PlainBlobMetaData(suffixName, blob.getSize()));
|
||||||
* @param path base path of the blobs to list
|
});
|
||||||
* @param pathToRemove if true, this path part is removed from blob name
|
});
|
||||||
* @return a map of blob names and their metadata
|
return mapBuilder.immutableMap();
|
||||||
*/
|
|
||||||
private Map<String, BlobMetaData> listBlobsByPath(String bucketName, String path, String pathToRemove) throws IOException {
|
|
||||||
return blobsStream(client, bucketName, path, MAX_BATCHING_REQUESTS)
|
|
||||||
.map(new BlobMetaDataConverter(pathToRemove))
|
|
||||||
.collect(Collectors.toMap(PlainBlobMetaData::name, Function.identity()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,19 +143,9 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
* @return true if the blob exists, false otherwise
|
* @return true if the blob exists, false otherwise
|
||||||
*/
|
*/
|
||||||
boolean blobExists(String blobName) throws IOException {
|
boolean blobExists(String blobName) throws IOException {
|
||||||
try {
|
final BlobId blobId = BlobId.of(bucket, blobName);
|
||||||
StorageObject blob = SocketAccess.doPrivilegedIOException(() -> client.objects().get(bucket, blobName).execute());
|
final Blob blob = SocketAccess.doPrivilegedIOException(() -> storage.get(blobId));
|
||||||
if (blob != null) {
|
return blob != null;
|
||||||
return Strings.hasText(blob.getId());
|
|
||||||
}
|
|
||||||
} catch (GoogleJsonResponseException e) {
|
|
||||||
GoogleJsonError error = e.getDetails();
|
|
||||||
if ((e.getStatusCode() == HTTP_NOT_FOUND) || ((error != null) && (error.getCode() == HTTP_NOT_FOUND))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -183,18 +155,29 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
* @return an InputStream
|
* @return an InputStream
|
||||||
*/
|
*/
|
||||||
InputStream readBlob(String blobName) throws IOException {
|
InputStream readBlob(String blobName) throws IOException {
|
||||||
try {
|
final BlobId blobId = BlobId.of(bucket, blobName);
|
||||||
return SocketAccess.doPrivilegedIOException(() -> {
|
final Blob blob = SocketAccess.doPrivilegedIOException(() -> storage.get(blobId));
|
||||||
Storage.Objects.Get object = client.objects().get(bucket, blobName);
|
if (blob == null) {
|
||||||
return object.executeMediaAsInputStream();
|
throw new NoSuchFileException("Blob [" + blobName + "] does not exit");
|
||||||
});
|
|
||||||
} catch (GoogleJsonResponseException e) {
|
|
||||||
GoogleJsonError error = e.getDetails();
|
|
||||||
if ((e.getStatusCode() == HTTP_NOT_FOUND) || ((error != null) && (error.getCode() == HTTP_NOT_FOUND))) {
|
|
||||||
throw new NoSuchFileException(e.getMessage());
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
final ReadChannel readChannel = SocketAccess.doPrivilegedIOException(blob::reader);
|
||||||
|
return Channels.newInputStream(new ReadableByteChannel() {
|
||||||
|
@SuppressForbidden(reason = "Channel is based of a socket not a file")
|
||||||
|
@Override
|
||||||
|
public int read(ByteBuffer dst) throws IOException {
|
||||||
|
return SocketAccess.doPrivilegedIOException(() -> readChannel.read(dst));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return readChannel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
SocketAccess.doPrivilegedVoidIOException(readChannel::close);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -204,14 +187,58 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
* @param blobSize expected size of the blob to be written
|
* @param blobSize expected size of the blob to be written
|
||||||
*/
|
*/
|
||||||
void writeBlob(String blobName, InputStream inputStream, long blobSize) throws IOException {
|
void writeBlob(String blobName, InputStream inputStream, long blobSize) throws IOException {
|
||||||
SocketAccess.doPrivilegedVoidIOException(() -> {
|
final BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).build();
|
||||||
InputStreamContent stream = new InputStreamContent(null, inputStream);
|
if (blobSize > LARGE_BLOB_THRESHOLD_BYTE_SIZE) {
|
||||||
stream.setLength(blobSize);
|
writeBlobResumable(blobInfo, inputStream);
|
||||||
|
} else {
|
||||||
|
writeBlobMultipart(blobInfo, inputStream, blobSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Storage.Objects.Insert insert = client.objects().insert(bucket, null, stream);
|
/**
|
||||||
insert.setName(blobName);
|
* Uploads a blob using the "resumable upload" method (multiple requests, which
|
||||||
insert.execute();
|
* can be independently retried in case of failure, see
|
||||||
});
|
* https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
|
||||||
|
*
|
||||||
|
* @param blobInfo the info for the blob to be uploaded
|
||||||
|
* @param inputStream the stream containing the blob data
|
||||||
|
*/
|
||||||
|
private void writeBlobResumable(BlobInfo blobInfo, InputStream inputStream) throws IOException {
|
||||||
|
final WriteChannel writeChannel = SocketAccess.doPrivilegedIOException(() -> storage.writer(blobInfo));
|
||||||
|
Streams.copy(inputStream, Channels.newOutputStream(new WritableByteChannel() {
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return writeChannel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
SocketAccess.doPrivilegedVoidIOException(writeChannel::close);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "Channel is based of a socket not a file")
|
||||||
|
@Override
|
||||||
|
public int write(ByteBuffer src) throws IOException {
|
||||||
|
return SocketAccess.doPrivilegedIOException(() -> writeChannel.write(src));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a blob using the "multipart upload" method (a single
|
||||||
|
* 'multipart/related' request containing both data and metadata. The request is
|
||||||
|
* gziped), see:
|
||||||
|
* https://cloud.google.com/storage/docs/json_api/v1/how-tos/multipart-upload
|
||||||
|
*
|
||||||
|
* @param blobInfo the info for the blob to be uploaded
|
||||||
|
* @param inputStream the stream containing the blob data
|
||||||
|
* @param blobSize the size
|
||||||
|
*/
|
||||||
|
private void writeBlobMultipart(BlobInfo blobInfo, InputStream inputStream, long blobSize) throws IOException {
|
||||||
|
assert blobSize <= LARGE_BLOB_THRESHOLD_BYTE_SIZE : "large blob uploads should use the resumable upload method";
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.toIntExact(blobSize));
|
||||||
|
Streams.copy(inputStream, baos);
|
||||||
|
SocketAccess.doPrivilegedVoidIOException(() -> storage.create(blobInfo, baos.toByteArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,10 +247,11 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
* @param blobName name of the blob
|
* @param blobName name of the blob
|
||||||
*/
|
*/
|
||||||
void deleteBlob(String blobName) throws IOException {
|
void deleteBlob(String blobName) throws IOException {
|
||||||
if (!blobExists(blobName)) {
|
final BlobId blobId = BlobId.of(bucket, blobName);
|
||||||
|
final boolean deleted = SocketAccess.doPrivilegedIOException(() -> storage.delete(blobId));
|
||||||
|
if (deleted == false) {
|
||||||
throw new NoSuchFileException("Blob [" + blobName + "] does not exist");
|
throw new NoSuchFileException("Blob [" + blobName + "] does not exist");
|
||||||
}
|
}
|
||||||
SocketAccess.doPrivilegedIOException(() -> client.objects().delete(bucket, blobName).execute());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,7 +260,7 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
* @param prefix prefix of the buckets to delete
|
* @param prefix prefix of the buckets to delete
|
||||||
*/
|
*/
|
||||||
void deleteBlobsByPrefix(String prefix) throws IOException {
|
void deleteBlobsByPrefix(String prefix) throws IOException {
|
||||||
deleteBlobs(listBlobsByPath(bucket, prefix, null).keySet());
|
deleteBlobs(listBlobsByPrefix("", prefix).keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,163 +269,55 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
|
||||||
* @param blobNames names of the bucket to delete
|
* @param blobNames names of the bucket to delete
|
||||||
*/
|
*/
|
||||||
void deleteBlobs(Collection<String> blobNames) throws IOException {
|
void deleteBlobs(Collection<String> blobNames) throws IOException {
|
||||||
if (blobNames == null || blobNames.isEmpty()) {
|
if (blobNames.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// for a single op submit a simple delete instead of a batch of size 1
|
||||||
if (blobNames.size() == 1) {
|
if (blobNames.size() == 1) {
|
||||||
deleteBlob(blobNames.iterator().next());
|
deleteBlob(blobNames.iterator().next());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<Storage.Objects.Delete> deletions = new ArrayList<>(Math.min(MAX_BATCHING_REQUESTS, blobNames.size()));
|
final List<BlobId> blobIdsToDelete = blobNames.stream().map(blobName -> BlobId.of(bucket, blobName)).collect(Collectors.toList());
|
||||||
final Iterator<String> blobs = blobNames.iterator();
|
final List<Boolean> deletedStatuses = SocketAccess.doPrivilegedIOException(() -> storage.delete(blobIdsToDelete));
|
||||||
|
assert blobIdsToDelete.size() == deletedStatuses.size();
|
||||||
SocketAccess.doPrivilegedVoidIOException(() -> {
|
boolean failed = false;
|
||||||
while (blobs.hasNext()) {
|
for (int i = 0; i < blobIdsToDelete.size(); i++) {
|
||||||
// Create a delete request for each blob to delete
|
if (deletedStatuses.get(i) == false) {
|
||||||
deletions.add(client.objects().delete(bucket, blobs.next()));
|
logger.error("Failed to delete blob [{}] in bucket [{}]", blobIdsToDelete.get(i).getName(), bucket);
|
||||||
|
failed = true;
|
||||||
if (blobs.hasNext() == false || deletions.size() == MAX_BATCHING_REQUESTS) {
|
|
||||||
try {
|
|
||||||
// Deletions are executed using a batch request
|
|
||||||
BatchRequest batch = client.batch();
|
|
||||||
|
|
||||||
// Used to track successful deletions
|
|
||||||
CountDown countDown = new CountDown(deletions.size());
|
|
||||||
|
|
||||||
for (Storage.Objects.Delete delete : deletions) {
|
|
||||||
// Queue the delete request in batch
|
|
||||||
delete.queue(batch, new JsonBatchCallback<Void>() {
|
|
||||||
@Override
|
|
||||||
public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
|
|
||||||
logger.error("failed to delete blob [{}] in bucket [{}]: {}", delete.getObject(), delete.getBucket(), e
|
|
||||||
.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void aVoid, HttpHeaders responseHeaders) throws IOException {
|
|
||||||
countDown.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.execute();
|
|
||||||
|
|
||||||
if (countDown.isCountedDown() == false) {
|
|
||||||
throw new IOException("Failed to delete all [" + deletions.size() + "] blobs");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
deletions.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
if (failed) {
|
||||||
|
throw new IOException("Failed to delete all [" + blobIdsToDelete.size() + "] blobs");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves a blob within the same bucket
|
* Moves a blob within the same bucket
|
||||||
*
|
*
|
||||||
* @param sourceBlob name of the blob to move
|
* @param sourceBlob name of the blob to move
|
||||||
* @param targetBlob new name of the blob in the target bucket
|
* @param targetBlob new name of the blob in the same bucket
|
||||||
*/
|
*/
|
||||||
void moveBlob(String sourceBlob, String targetBlob) throws IOException {
|
void moveBlob(String sourceBlobName, String targetBlobName) throws IOException {
|
||||||
SocketAccess.doPrivilegedIOException(() -> {
|
final BlobId sourceBlobId = BlobId.of(bucket, sourceBlobName);
|
||||||
|
final BlobId targetBlobId = BlobId.of(bucket, targetBlobName);
|
||||||
|
final CopyRequest request = CopyRequest.newBuilder()
|
||||||
|
.setSource(sourceBlobId)
|
||||||
|
.setTarget(targetBlobId)
|
||||||
|
.build();
|
||||||
|
SocketAccess.doPrivilegedVoidIOException(() -> {
|
||||||
// There's no atomic "move" in GCS so we need to copy and delete
|
// There's no atomic "move" in GCS so we need to copy and delete
|
||||||
client.objects().copy(bucket, sourceBlob, bucket, targetBlob, null).execute();
|
storage.copy(request).getResult();
|
||||||
client.objects().delete(bucket, sourceBlob).execute();
|
final boolean deleted = storage.delete(sourceBlobId);
|
||||||
return null;
|
if (deleted == false) {
|
||||||
|
throw new IOException("Failed to move source [" + sourceBlobName + "] to target [" + targetBlobName + "]");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildKey(String keyPath, String s) {
|
private static String buildKey(String keyPath, String s) {
|
||||||
assert s != null;
|
assert s != null;
|
||||||
return keyPath + s;
|
return keyPath + s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link StorageObject} to a {@link PlainBlobMetaData}
|
|
||||||
*/
|
|
||||||
class BlobMetaDataConverter implements Function<StorageObject, PlainBlobMetaData> {
|
|
||||||
|
|
||||||
private final String pathToRemove;
|
|
||||||
|
|
||||||
BlobMetaDataConverter(String pathToRemove) {
|
|
||||||
this.pathToRemove = pathToRemove;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PlainBlobMetaData apply(StorageObject storageObject) {
|
|
||||||
String blobName = storageObject.getName();
|
|
||||||
if (Strings.hasLength(pathToRemove)) {
|
|
||||||
blobName = blobName.substring(pathToRemove.length());
|
|
||||||
}
|
|
||||||
return new PlainBlobMetaData(blobName, storageObject.getSize().longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spliterator can be used to list storage objects stored in a bucket.
|
|
||||||
*/
|
|
||||||
static class StorageObjectsSpliterator implements Spliterator<StorageObject> {
|
|
||||||
|
|
||||||
private final Storage.Objects.List list;
|
|
||||||
|
|
||||||
StorageObjectsSpliterator(Storage client, String bucketName, String prefix, long pageSize) throws IOException {
|
|
||||||
list = SocketAccess.doPrivilegedIOException(() -> client.objects().list(bucketName));
|
|
||||||
list.setMaxResults(pageSize);
|
|
||||||
if (prefix != null) {
|
|
||||||
list.setPrefix(prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean tryAdvance(Consumer<? super StorageObject> action) {
|
|
||||||
try {
|
|
||||||
// Retrieves the next page of items
|
|
||||||
Objects objects = SocketAccess.doPrivilegedIOException(list::execute);
|
|
||||||
|
|
||||||
if ((objects == null) || (objects.getItems() == null) || (objects.getItems().isEmpty())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumes all the items
|
|
||||||
objects.getItems().forEach(action::accept);
|
|
||||||
|
|
||||||
// Sets the page token of the next page,
|
|
||||||
// null indicates that all items have been consumed
|
|
||||||
String next = objects.getNextPageToken();
|
|
||||||
if (next != null) {
|
|
||||||
list.setPageToken(next);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new BlobStoreException("Exception while listing objects", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Spliterator<StorageObject> trySplit() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long estimateSize() {
|
|
||||||
return Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int characteristics() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link Stream} of {@link StorageObject}s that are stored in a given bucket.
|
|
||||||
*/
|
|
||||||
static Stream<StorageObject> blobsStream(Storage client, String bucketName, String prefix, long pageSize) throws IOException {
|
|
||||||
return StreamSupport.stream(new StorageObjectsSpliterator(client, bucketName, prefix, pageSize), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,10 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
|
||||||
import com.google.api.services.storage.StorageScopes;
|
import com.google.api.services.storage.StorageScopes;
|
||||||
|
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.settings.SecureSetting;
|
import org.elasticsearch.common.settings.SecureSetting;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -28,10 +30,12 @@ import org.elasticsearch.common.unit.TimeValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static org.elasticsearch.common.settings.Setting.timeSetting;
|
import static org.elasticsearch.common.settings.Setting.timeSetting;
|
||||||
|
|
||||||
|
@ -44,11 +48,19 @@ public class GoogleCloudStorageClientSettings {
|
||||||
|
|
||||||
/** A json Service Account file loaded from secure settings. */
|
/** A json Service Account file loaded from secure settings. */
|
||||||
static final Setting.AffixSetting<InputStream> CREDENTIALS_FILE_SETTING = Setting.affixKeySetting(PREFIX, "credentials_file",
|
static final Setting.AffixSetting<InputStream> CREDENTIALS_FILE_SETTING = Setting.affixKeySetting(PREFIX, "credentials_file",
|
||||||
key -> SecureSetting.secureFile(key, null));
|
key -> SecureSetting.secureFile(key, null));
|
||||||
|
|
||||||
/** An override for the Storage endpoint to connect to. */
|
/** An override for the Storage endpoint to connect to. */
|
||||||
static final Setting.AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(PREFIX, "endpoint",
|
static final Setting.AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(PREFIX, "endpoint",
|
||||||
key -> new Setting<>(key, "", s -> s, Setting.Property.NodeScope));
|
key -> Setting.simpleString(key, Setting.Property.NodeScope));
|
||||||
|
|
||||||
|
/** An override for the Google Project ID. */
|
||||||
|
static final Setting.AffixSetting<String> PROJECT_ID_SETTING = Setting.affixKeySetting(PREFIX, "project_id",
|
||||||
|
key -> Setting.simpleString(key, Setting.Property.NodeScope));
|
||||||
|
|
||||||
|
/** An override for the Token Server URI in the oauth flow. */
|
||||||
|
static final Setting.AffixSetting<URI> TOKEN_URI_SETTING = Setting.affixKeySetting(PREFIX, "token_uri",
|
||||||
|
key -> new Setting<>(key, "", URI::create, Setting.Property.NodeScope));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timeout to establish a connection. A value of {@code -1} corresponds to an infinite timeout. A value of {@code 0}
|
* The timeout to establish a connection. A value of {@code -1} corresponds to an infinite timeout. A value of {@code 0}
|
||||||
|
@ -64,45 +76,59 @@ public class GoogleCloudStorageClientSettings {
|
||||||
static final Setting.AffixSetting<TimeValue> READ_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "read_timeout",
|
static final Setting.AffixSetting<TimeValue> READ_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "read_timeout",
|
||||||
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
|
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
|
||||||
|
|
||||||
/** Name used by the client when it uses the Google Cloud JSON API. **/
|
/** Name used by the client when it uses the Google Cloud JSON API. */
|
||||||
static final Setting.AffixSetting<String> APPLICATION_NAME_SETTING = Setting.affixKeySetting(PREFIX, "application_name",
|
static final Setting.AffixSetting<String> APPLICATION_NAME_SETTING = Setting.affixKeySetting(PREFIX, "application_name",
|
||||||
key -> new Setting<>(key, "repository-gcs", s -> s, Setting.Property.NodeScope));
|
key -> new Setting<>(key, "repository-gcs", Function.identity(), Setting.Property.NodeScope, Setting.Property.Deprecated));
|
||||||
|
|
||||||
/** The credentials used by the client to connect to the Storage endpoint **/
|
/** The credentials used by the client to connect to the Storage endpoint. */
|
||||||
private final GoogleCredential credential;
|
private final ServiceAccountCredentials credential;
|
||||||
|
|
||||||
/** The Storage root URL the client should talk to, or empty string to use the default. **/
|
/** The Storage endpoint URL the client should talk to. Null value sets the default. */
|
||||||
private final String endpoint;
|
private final String endpoint;
|
||||||
|
|
||||||
/** The timeout to establish a connection **/
|
/** The Google project ID overriding the default way to infer it. Null value sets the default. */
|
||||||
|
private final String projectId;
|
||||||
|
|
||||||
|
/** The timeout to establish a connection */
|
||||||
private final TimeValue connectTimeout;
|
private final TimeValue connectTimeout;
|
||||||
|
|
||||||
/** The timeout to read data from an established connection **/
|
/** The timeout to read data from an established connection */
|
||||||
private final TimeValue readTimeout;
|
private final TimeValue readTimeout;
|
||||||
|
|
||||||
/** The Storage client application name **/
|
/** The Storage client application name */
|
||||||
private final String applicationName;
|
private final String applicationName;
|
||||||
|
|
||||||
GoogleCloudStorageClientSettings(final GoogleCredential credential,
|
/** The token server URI. This leases access tokens in the oauth flow. */
|
||||||
|
private final URI tokenUri;
|
||||||
|
|
||||||
|
GoogleCloudStorageClientSettings(final ServiceAccountCredentials credential,
|
||||||
final String endpoint,
|
final String endpoint,
|
||||||
|
final String projectId,
|
||||||
final TimeValue connectTimeout,
|
final TimeValue connectTimeout,
|
||||||
final TimeValue readTimeout,
|
final TimeValue readTimeout,
|
||||||
final String applicationName) {
|
final String applicationName,
|
||||||
|
final URI tokenUri) {
|
||||||
this.credential = credential;
|
this.credential = credential;
|
||||||
this.endpoint = endpoint;
|
this.endpoint = endpoint;
|
||||||
|
this.projectId = projectId;
|
||||||
this.connectTimeout = connectTimeout;
|
this.connectTimeout = connectTimeout;
|
||||||
this.readTimeout = readTimeout;
|
this.readTimeout = readTimeout;
|
||||||
this.applicationName = applicationName;
|
this.applicationName = applicationName;
|
||||||
|
this.tokenUri = tokenUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GoogleCredential getCredential() {
|
public ServiceAccountCredentials getCredential() {
|
||||||
return credential;
|
return credential;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEndpoint() {
|
public String getHost() {
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getProjectId() {
|
||||||
|
return Strings.hasLength(projectId) ? projectId : (credential != null ? credential.getProjectId() : null);
|
||||||
|
}
|
||||||
|
|
||||||
public TimeValue getConnectTimeout() {
|
public TimeValue getConnectTimeout() {
|
||||||
return connectTimeout;
|
return connectTimeout;
|
||||||
}
|
}
|
||||||
|
@ -115,9 +141,13 @@ public class GoogleCloudStorageClientSettings {
|
||||||
return applicationName;
|
return applicationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public URI getTokenUri() {
|
||||||
|
return tokenUri;
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, GoogleCloudStorageClientSettings> load(final Settings settings) {
|
public static Map<String, GoogleCloudStorageClientSettings> load(final Settings settings) {
|
||||||
final Map<String, GoogleCloudStorageClientSettings> clients = new HashMap<>();
|
final Map<String, GoogleCloudStorageClientSettings> clients = new HashMap<>();
|
||||||
for (String clientName: settings.getGroups(PREFIX).keySet()) {
|
for (final String clientName: settings.getGroups(PREFIX).keySet()) {
|
||||||
clients.put(clientName, getClientSettings(settings, clientName));
|
clients.put(clientName, getClientSettings(settings, clientName));
|
||||||
}
|
}
|
||||||
if (clients.containsKey("default") == false) {
|
if (clients.containsKey("default") == false) {
|
||||||
|
@ -132,22 +162,27 @@ public class GoogleCloudStorageClientSettings {
|
||||||
return new GoogleCloudStorageClientSettings(
|
return new GoogleCloudStorageClientSettings(
|
||||||
loadCredential(settings, clientName),
|
loadCredential(settings, clientName),
|
||||||
getConfigValue(settings, clientName, ENDPOINT_SETTING),
|
getConfigValue(settings, clientName, ENDPOINT_SETTING),
|
||||||
|
getConfigValue(settings, clientName, PROJECT_ID_SETTING),
|
||||||
getConfigValue(settings, clientName, CONNECT_TIMEOUT_SETTING),
|
getConfigValue(settings, clientName, CONNECT_TIMEOUT_SETTING),
|
||||||
getConfigValue(settings, clientName, READ_TIMEOUT_SETTING),
|
getConfigValue(settings, clientName, READ_TIMEOUT_SETTING),
|
||||||
getConfigValue(settings, clientName, APPLICATION_NAME_SETTING)
|
getConfigValue(settings, clientName, APPLICATION_NAME_SETTING),
|
||||||
|
getConfigValue(settings, clientName, TOKEN_URI_SETTING)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the service account file corresponding to a given client name. If no file is defined for the client,
|
* Loads the service account file corresponding to a given client name. If no
|
||||||
* a {@code null} credential is returned.
|
* file is defined for the client, a {@code null} credential is returned.
|
||||||
*
|
*
|
||||||
* @param settings the {@link Settings}
|
* @param settings
|
||||||
* @param clientName the client name
|
* the {@link Settings}
|
||||||
|
* @param clientName
|
||||||
|
* the client name
|
||||||
*
|
*
|
||||||
* @return the {@link GoogleCredential} to use for the given client, {@code null} if no service account is defined.
|
* @return the {@link ServiceAccountCredentials} to use for the given client,
|
||||||
|
* {@code null} if no service account is defined.
|
||||||
*/
|
*/
|
||||||
static GoogleCredential loadCredential(final Settings settings, final String clientName) {
|
static ServiceAccountCredentials loadCredential(final Settings settings, final String clientName) {
|
||||||
try {
|
try {
|
||||||
if (CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).exists(settings) == false) {
|
if (CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).exists(settings) == false) {
|
||||||
// explicitly returning null here so that the default credential
|
// explicitly returning null here so that the default credential
|
||||||
|
@ -155,19 +190,22 @@ public class GoogleCloudStorageClientSettings {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try (InputStream credStream = CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).get(settings)) {
|
try (InputStream credStream = CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).get(settings)) {
|
||||||
GoogleCredential credential = GoogleCredential.fromStream(credStream);
|
final Collection<String> scopes = Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL);
|
||||||
if (credential.createScopedRequired()) {
|
return SocketAccess.doPrivilegedIOException(() -> {
|
||||||
credential = credential.createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL));
|
final ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(credStream);
|
||||||
}
|
if (credentials.createScopedRequired()) {
|
||||||
return credential;
|
return (ServiceAccountCredentials) credentials.createScoped(scopes);
|
||||||
|
}
|
||||||
|
return credentials;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T getConfigValue(final Settings settings, final String clientName, final Setting.AffixSetting<T> clientSetting) {
|
private static <T> T getConfigValue(final Settings settings, final String clientName, final Setting.AffixSetting<T> clientSetting) {
|
||||||
Setting<T> concreteSetting = clientSetting.getConcreteSettingForNamespace(clientName);
|
final Setting<T> concreteSetting = clientSetting.getConcreteSettingForNamespace(clientName);
|
||||||
return concreteSetting.get(settings);
|
return concreteSetting.get(settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,21 +19,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.auth.oauth2.TokenRequest;
|
|
||||||
import com.google.api.client.auth.oauth2.TokenResponse;
|
|
||||||
import com.google.api.client.googleapis.json.GoogleJsonError;
|
|
||||||
import com.google.api.client.http.GenericUrl;
|
|
||||||
import com.google.api.client.http.HttpHeaders;
|
|
||||||
import com.google.api.client.json.GenericJson;
|
|
||||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
|
||||||
import com.google.api.client.json.webtoken.JsonWebToken;
|
|
||||||
import com.google.api.client.util.ClassInfo;
|
|
||||||
import com.google.api.client.util.Data;
|
|
||||||
import com.google.api.services.storage.Storage;
|
|
||||||
import com.google.api.services.storage.model.Bucket;
|
|
||||||
import com.google.api.services.storage.model.Objects;
|
|
||||||
import com.google.api.services.storage.model.StorageObject;
|
|
||||||
import org.elasticsearch.SpecialPermission;
|
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
|
@ -42,8 +27,6 @@ import org.elasticsearch.plugins.Plugin;
|
||||||
import org.elasticsearch.plugins.RepositoryPlugin;
|
import org.elasticsearch.plugins.RepositoryPlugin;
|
||||||
import org.elasticsearch.repositories.Repository;
|
import org.elasticsearch.repositories.Repository;
|
||||||
|
|
||||||
import java.security.AccessController;
|
|
||||||
import java.security.PrivilegedAction;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -51,63 +34,6 @@ import java.util.Map;
|
||||||
|
|
||||||
public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin {
|
public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin {
|
||||||
|
|
||||||
static {
|
|
||||||
/*
|
|
||||||
* Google HTTP client changes access levels because its silly and we
|
|
||||||
* can't allow that on any old stack stack so we pull it here, up front,
|
|
||||||
* so we can cleanly check the permissions for it. Without this changing
|
|
||||||
* the permission can fail if any part of core is on the stack because
|
|
||||||
* our plugin permissions don't allow core to "reach through" plugins to
|
|
||||||
* change the permission. Because that'd be silly.
|
|
||||||
*/
|
|
||||||
SpecialPermission.check();
|
|
||||||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
|
||||||
// ClassInfo put in cache all the fields of a given class
|
|
||||||
// that are annoted with @Key; at the same time it changes
|
|
||||||
// the field access level using setAccessible(). Calling
|
|
||||||
// them here put the ClassInfo in cache (they are never evicted)
|
|
||||||
// before the SecurityManager is installed.
|
|
||||||
ClassInfo.of(HttpHeaders.class, true);
|
|
||||||
|
|
||||||
ClassInfo.of(JsonWebSignature.Header.class, false);
|
|
||||||
ClassInfo.of(JsonWebToken.Payload.class, false);
|
|
||||||
|
|
||||||
ClassInfo.of(TokenRequest.class, false);
|
|
||||||
ClassInfo.of(TokenResponse.class, false);
|
|
||||||
|
|
||||||
ClassInfo.of(GenericJson.class, false);
|
|
||||||
ClassInfo.of(GenericUrl.class, false);
|
|
||||||
|
|
||||||
Data.nullOf(GoogleJsonError.ErrorInfo.class);
|
|
||||||
ClassInfo.of(GoogleJsonError.class, false);
|
|
||||||
|
|
||||||
Data.nullOf(Bucket.Cors.class);
|
|
||||||
ClassInfo.of(Bucket.class, false);
|
|
||||||
ClassInfo.of(Bucket.Cors.class, false);
|
|
||||||
ClassInfo.of(Bucket.Lifecycle.class, false);
|
|
||||||
ClassInfo.of(Bucket.Logging.class, false);
|
|
||||||
ClassInfo.of(Bucket.Owner.class, false);
|
|
||||||
ClassInfo.of(Bucket.Versioning.class, false);
|
|
||||||
ClassInfo.of(Bucket.Website.class, false);
|
|
||||||
|
|
||||||
ClassInfo.of(StorageObject.class, false);
|
|
||||||
ClassInfo.of(StorageObject.Owner.class, false);
|
|
||||||
|
|
||||||
ClassInfo.of(Objects.class, false);
|
|
||||||
|
|
||||||
ClassInfo.of(Storage.Buckets.Get.class, false);
|
|
||||||
ClassInfo.of(Storage.Buckets.Insert.class, false);
|
|
||||||
|
|
||||||
ClassInfo.of(Storage.Objects.Get.class, false);
|
|
||||||
ClassInfo.of(Storage.Objects.Insert.class, false);
|
|
||||||
ClassInfo.of(Storage.Objects.Delete.class, false);
|
|
||||||
ClassInfo.of(Storage.Objects.Copy.class, false);
|
|
||||||
ClassInfo.of(Storage.Objects.List.class, false);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, GoogleCloudStorageClientSettings> clientsSettings;
|
private final Map<String, GoogleCloudStorageClientSettings> clientsSettings;
|
||||||
|
|
||||||
public GoogleCloudStoragePlugin(final Settings settings) {
|
public GoogleCloudStoragePlugin(final Settings settings) {
|
||||||
|
@ -134,8 +60,10 @@ public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING,
|
GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING,
|
||||||
GoogleCloudStorageClientSettings.ENDPOINT_SETTING,
|
GoogleCloudStorageClientSettings.ENDPOINT_SETTING,
|
||||||
|
GoogleCloudStorageClientSettings.PROJECT_ID_SETTING,
|
||||||
GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING,
|
GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING,
|
||||||
GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING,
|
GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING,
|
||||||
GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING);
|
GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING,
|
||||||
|
GoogleCloudStorageClientSettings.TOKEN_URI_SETTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.services.storage.Storage;
|
|
||||||
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
|
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.blobstore.BlobPath;
|
import org.elasticsearch.common.blobstore.BlobPath;
|
||||||
|
@ -27,7 +26,6 @@ import org.elasticsearch.common.blobstore.BlobStore;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.unit.ByteSizeUnit;
|
import org.elasticsearch.common.unit.ByteSizeUnit;
|
||||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.repositories.RepositoryException;
|
import org.elasticsearch.repositories.RepositoryException;
|
||||||
|
@ -39,7 +37,8 @@ import static org.elasticsearch.common.settings.Setting.Property;
|
||||||
import static org.elasticsearch.common.settings.Setting.boolSetting;
|
import static org.elasticsearch.common.settings.Setting.boolSetting;
|
||||||
import static org.elasticsearch.common.settings.Setting.byteSizeSetting;
|
import static org.elasticsearch.common.settings.Setting.byteSizeSetting;
|
||||||
import static org.elasticsearch.common.settings.Setting.simpleString;
|
import static org.elasticsearch.common.settings.Setting.simpleString;
|
||||||
import static org.elasticsearch.common.unit.TimeValue.timeValueMillis;
|
|
||||||
|
import com.google.cloud.storage.Storage;
|
||||||
|
|
||||||
class GoogleCloudStorageRepository extends BlobStoreRepository {
|
class GoogleCloudStorageRepository extends BlobStoreRepository {
|
||||||
|
|
||||||
|
|
|
@ -19,23 +19,26 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
import com.google.api.client.googleapis.GoogleUtils;
|
||||||
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
|
|
||||||
import com.google.api.client.http.HttpBackOffIOExceptionHandler;
|
|
||||||
import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
|
|
||||||
import com.google.api.client.http.HttpRequest;
|
|
||||||
import com.google.api.client.http.HttpRequestInitializer;
|
|
||||||
import com.google.api.client.http.HttpTransport;
|
import com.google.api.client.http.HttpTransport;
|
||||||
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
|
import com.google.api.client.http.javanet.DefaultConnectionFactory;
|
||||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||||
import com.google.api.client.util.ExponentialBackOff;
|
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||||
import com.google.api.services.storage.Storage;
|
import com.google.cloud.http.HttpTransportOptions;
|
||||||
|
import com.google.cloud.storage.Storage;
|
||||||
|
import com.google.cloud.storage.StorageOptions;
|
||||||
|
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.collect.MapBuilder;
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class GoogleCloudStorageService extends AbstractComponent {
|
public class GoogleCloudStorageService extends AbstractComponent {
|
||||||
|
@ -51,42 +54,107 @@ public class GoogleCloudStorageService extends AbstractComponent {
|
||||||
/**
|
/**
|
||||||
* Creates a client that can be used to manage Google Cloud Storage objects.
|
* Creates a client that can be used to manage Google Cloud Storage objects.
|
||||||
*
|
*
|
||||||
* @param clientName name of client settings to use from secure settings
|
* @param clientName name of client settings to use, including secure settings
|
||||||
* @return a Client instance that can be used to manage Storage objects
|
* @return a Client instance that can be used to manage Storage objects
|
||||||
*/
|
*/
|
||||||
public Storage createClient(final String clientName) throws Exception {
|
public Storage createClient(final String clientName) throws Exception {
|
||||||
final GoogleCloudStorageClientSettings clientSettings = clientsSettings.get(clientName);
|
final GoogleCloudStorageClientSettings clientSettings = clientsSettings.get(clientName);
|
||||||
if (clientSettings == null) {
|
if (clientSettings == null) {
|
||||||
throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: " +
|
throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: "
|
||||||
Strings.collectionToDelimitedString(clientsSettings.keySet(), ","));
|
+ Strings.collectionToDelimitedString(clientsSettings.keySet(), ","));
|
||||||
}
|
}
|
||||||
|
final HttpTransport httpTransport = createHttpTransport(clientSettings.getHost());
|
||||||
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
|
final HttpTransportOptions httpTransportOptions = HttpTransportOptions.newBuilder()
|
||||||
HttpRequestInitializer requestInitializer = createRequestInitializer(clientSettings);
|
.setConnectTimeout(toTimeout(clientSettings.getConnectTimeout()))
|
||||||
|
.setReadTimeout(toTimeout(clientSettings.getReadTimeout()))
|
||||||
Storage.Builder storage = new Storage.Builder(transport, JacksonFactory.getDefaultInstance(), requestInitializer);
|
.setHttpTransportFactory(() -> httpTransport)
|
||||||
if (Strings.hasLength(clientSettings.getApplicationName())) {
|
.build();
|
||||||
storage.setApplicationName(clientSettings.getApplicationName());
|
final StorageOptions.Builder storageOptionsBuilder = StorageOptions.newBuilder()
|
||||||
|
.setTransportOptions(httpTransportOptions)
|
||||||
|
.setHeaderProvider(() -> {
|
||||||
|
final MapBuilder<String, String> mapBuilder = MapBuilder.newMapBuilder();
|
||||||
|
if (Strings.hasLength(clientSettings.getApplicationName())) {
|
||||||
|
mapBuilder.put("user-agent", clientSettings.getApplicationName());
|
||||||
|
}
|
||||||
|
return mapBuilder.immutableMap();
|
||||||
|
});
|
||||||
|
if (Strings.hasLength(clientSettings.getHost())) {
|
||||||
|
storageOptionsBuilder.setHost(clientSettings.getHost());
|
||||||
}
|
}
|
||||||
if (Strings.hasLength(clientSettings.getEndpoint())) {
|
if (Strings.hasLength(clientSettings.getProjectId())) {
|
||||||
storage.setRootUrl(clientSettings.getEndpoint());
|
storageOptionsBuilder.setProjectId(clientSettings.getProjectId());
|
||||||
}
|
}
|
||||||
return storage.build();
|
if (clientSettings.getCredential() == null) {
|
||||||
|
logger.warn("\"Application Default Credentials\" are not supported out of the box."
|
||||||
|
+ " Additional file system permissions have to be granted to the plugin.");
|
||||||
|
} else {
|
||||||
|
ServiceAccountCredentials serviceAccountCredentials = clientSettings.getCredential();
|
||||||
|
// override token server URI
|
||||||
|
final URI tokenServerUri = clientSettings.getTokenUri();
|
||||||
|
if (Strings.hasLength(tokenServerUri.toString())) {
|
||||||
|
// Rebuild the service account credentials in order to use a custom Token url.
|
||||||
|
// This is mostly used for testing purpose.
|
||||||
|
serviceAccountCredentials = serviceAccountCredentials.toBuilder().setTokenServerUri(tokenServerUri).build();
|
||||||
|
}
|
||||||
|
storageOptionsBuilder.setCredentials(serviceAccountCredentials);
|
||||||
|
}
|
||||||
|
return storageOptionsBuilder.build().getService();
|
||||||
}
|
}
|
||||||
|
|
||||||
static HttpRequestInitializer createRequestInitializer(final GoogleCloudStorageClientSettings settings) throws IOException {
|
/**
|
||||||
GoogleCredential credential = settings.getCredential();
|
* Pins the TLS trust certificates and, more importantly, overrides connection
|
||||||
if (credential == null) {
|
* URLs in the case of a custom endpoint setting because some connections don't
|
||||||
credential = GoogleCredential.getApplicationDefault();
|
* fully honor this setting (bugs in the SDK). The default connection factory
|
||||||
|
* opens a new connection for each request. This is required for the storage
|
||||||
|
* instance to be thread-safe.
|
||||||
|
**/
|
||||||
|
private static HttpTransport createHttpTransport(final String endpoint) throws Exception {
|
||||||
|
final NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
|
||||||
|
// requires java.lang.RuntimePermission "setFactory"
|
||||||
|
builder.trustCertificates(GoogleUtils.getCertificateTrustStore());
|
||||||
|
if (Strings.hasLength(endpoint)) {
|
||||||
|
final URL endpointUrl = URI.create(endpoint).toURL();
|
||||||
|
builder.setConnectionFactory(new DefaultConnectionFactory() {
|
||||||
|
@Override
|
||||||
|
public HttpURLConnection openConnection(final URL originalUrl) throws IOException {
|
||||||
|
// test if the URL is built correctly, ie following the `host` setting
|
||||||
|
if (originalUrl.getHost().equals(endpointUrl.getHost()) && originalUrl.getPort() == endpointUrl.getPort()
|
||||||
|
&& originalUrl.getProtocol().equals(endpointUrl.getProtocol())) {
|
||||||
|
return super.openConnection(originalUrl);
|
||||||
|
}
|
||||||
|
// override connection URLs because some don't follow the config. See
|
||||||
|
// https://github.com/GoogleCloudPlatform/google-cloud-java/issues/3254 and
|
||||||
|
// https://github.com/GoogleCloudPlatform/google-cloud-java/issues/3255
|
||||||
|
URI originalUri;
|
||||||
|
try {
|
||||||
|
originalUri = originalUrl.toURI();
|
||||||
|
} catch (final URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
String overridePath = "/";
|
||||||
|
if (originalUri.getRawPath() != null) {
|
||||||
|
overridePath = originalUri.getRawPath();
|
||||||
|
}
|
||||||
|
if (originalUri.getRawQuery() != null) {
|
||||||
|
overridePath += "?" + originalUri.getRawQuery();
|
||||||
|
}
|
||||||
|
return super.openConnection(
|
||||||
|
new URL(endpointUrl.getProtocol(), endpointUrl.getHost(), endpointUrl.getPort(), overridePath));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return new DefaultHttpRequestInitializer(credential, toTimeout(settings.getConnectTimeout()), toTimeout(settings.getReadTimeout()));
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Converts timeout values from the settings to a timeout value for the Google Cloud SDK **/
|
/**
|
||||||
|
* Converts timeout values from the settings to a timeout value for the Google
|
||||||
|
* Cloud SDK
|
||||||
|
**/
|
||||||
static Integer toTimeout(final TimeValue timeout) {
|
static Integer toTimeout(final TimeValue timeout) {
|
||||||
// Null or zero in settings means the default timeout
|
// Null or zero in settings means the default timeout
|
||||||
if (timeout == null || TimeValue.ZERO.equals(timeout)) {
|
if (timeout == null || TimeValue.ZERO.equals(timeout)) {
|
||||||
return null;
|
// negative value means using the default value
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
// -1 means infinite timeout
|
// -1 means infinite timeout
|
||||||
if (TimeValue.MINUS_ONE.equals(timeout)) {
|
if (TimeValue.MINUS_ONE.equals(timeout)) {
|
||||||
|
@ -96,51 +164,4 @@ public class GoogleCloudStorageService extends AbstractComponent {
|
||||||
return Math.toIntExact(timeout.getMillis());
|
return Math.toIntExact(timeout.getMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP request initializer that set timeouts and backoff handler while deferring authentication to GoogleCredential.
|
|
||||||
* See https://cloud.google.com/storage/transfer/create-client#retry
|
|
||||||
*/
|
|
||||||
static class DefaultHttpRequestInitializer implements HttpRequestInitializer {
|
|
||||||
|
|
||||||
private final Integer connectTimeout;
|
|
||||||
private final Integer readTimeout;
|
|
||||||
private final GoogleCredential credential;
|
|
||||||
|
|
||||||
DefaultHttpRequestInitializer(GoogleCredential credential, Integer connectTimeoutMillis, Integer readTimeoutMillis) {
|
|
||||||
this.credential = credential;
|
|
||||||
this.connectTimeout = connectTimeoutMillis;
|
|
||||||
this.readTimeout = readTimeoutMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(HttpRequest request) {
|
|
||||||
if (connectTimeout != null) {
|
|
||||||
request.setConnectTimeout(connectTimeout);
|
|
||||||
}
|
|
||||||
if (readTimeout != null) {
|
|
||||||
request.setReadTimeout(readTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(newBackOff()));
|
|
||||||
request.setInterceptor(credential);
|
|
||||||
|
|
||||||
final HttpUnsuccessfulResponseHandler handler = new HttpBackOffUnsuccessfulResponseHandler(newBackOff());
|
|
||||||
request.setUnsuccessfulResponseHandler((req, resp, supportsRetry) -> {
|
|
||||||
// Let the credential handle the response. If it failed, we rely on our backoff handler
|
|
||||||
return credential.handleResponse(req, resp, supportsRetry) || handler.handleResponse(req, resp, supportsRetry);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExponentialBackOff newBackOff() {
|
|
||||||
return new ExponentialBackOff.Builder()
|
|
||||||
.setInitialIntervalMillis(100)
|
|
||||||
.setMaxIntervalMillis(6000)
|
|
||||||
.setMaxElapsedTimeMillis(900000)
|
|
||||||
.setMultiplier(1.5)
|
|
||||||
.setRandomizationFactor(0.5)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
grant {
|
grant {
|
||||||
|
// required by: com.google.api.client.json.JsonParser#parseValue
|
||||||
permission java.lang.RuntimePermission "accessDeclaredMembers";
|
permission java.lang.RuntimePermission "accessDeclaredMembers";
|
||||||
permission java.lang.RuntimePermission "setFactory";
|
// required by: com.google.api.client.json.GenericJson#<init>
|
||||||
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
||||||
permission java.net.URLPermission "http://www.googleapis.com/*", "*";
|
// required to add google certs to the gcs client trustore
|
||||||
permission java.net.URLPermission "https://www.googleapis.com/*", "*";
|
permission java.lang.RuntimePermission "setFactory";
|
||||||
|
|
||||||
// gcs client opens socket connections for to access repository
|
// gcs client opens socket connections for to access repository
|
||||||
permission java.net.SocketPermission "*", "connect";
|
permission java.net.SocketPermission "*", "connect";
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you 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 com.google.cloud.storage;
|
||||||
|
|
||||||
|
import com.google.cloud.storage.spi.v1.StorageRpc;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class that exposed Google SDK package protected methods to
|
||||||
|
* create specific StorageRpc objects in unit tests.
|
||||||
|
*/
|
||||||
|
public class StorageRpcOptionUtils {
|
||||||
|
|
||||||
|
private StorageRpcOptionUtils(){}
|
||||||
|
|
||||||
|
public static String getPrefix(final Storage.BlobListOption... options) {
|
||||||
|
if (options != null) {
|
||||||
|
for (final Option option : options) {
|
||||||
|
final StorageRpc.Option rpcOption = option.getRpcOption();
|
||||||
|
if (StorageRpc.Option.PREFIX.equals(rpcOption)) {
|
||||||
|
return (String) option.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CopyWriter createCopyWriter(final Blob result) {
|
||||||
|
return new CopyWriter(mock(StorageOptions.class), mock(StorageRpc.RewriteResponse.class)) {
|
||||||
|
@Override
|
||||||
|
public Blob getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you 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 com.google.cloud.storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class that exposed Google SDK package protected methods to
|
||||||
|
* create buckets and blobs objects in unit tests.
|
||||||
|
*/
|
||||||
|
public class StorageTestUtils {
|
||||||
|
|
||||||
|
private StorageTestUtils(){}
|
||||||
|
|
||||||
|
public static Bucket createBucket(final Storage storage, final String bucketName) {
|
||||||
|
return new Bucket(storage, (BucketInfo.BuilderImpl) BucketInfo.newBuilder(bucketName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Blob createBlob(final Storage storage, final String bucketName, final String blobName, final long blobSize) {
|
||||||
|
return new Blob(storage, (BlobInfo.BuilderImpl) BlobInfo.newBuilder(bucketName, blobName).setSize(blobSize));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.services.storage.Storage;
|
import com.google.cloud.storage.Storage;
|
||||||
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
|
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.ByteSizeUnit;
|
import org.elasticsearch.common.unit.ByteSizeUnit;
|
||||||
|
|
|
@ -18,20 +18,25 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
|
||||||
import com.google.api.services.storage.StorageScopes;
|
import com.google.api.services.storage.StorageScopes;
|
||||||
|
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||||
|
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.common.settings.MockSecureSettings;
|
import org.elasticsearch.common.settings.MockSecureSettings;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -39,6 +44,7 @@ import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSetting
|
||||||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING;
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING;
|
||||||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING;
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING;
|
||||||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.ENDPOINT_SETTING;
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.ENDPOINT_SETTING;
|
||||||
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.PROJECT_ID_SETTING;
|
||||||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING;
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING;
|
||||||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.getClientSettings;
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.getClientSettings;
|
||||||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.loadCredential;
|
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.loadCredential;
|
||||||
|
@ -46,59 +52,78 @@ import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSetting
|
||||||
public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
|
public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
|
||||||
|
|
||||||
public void testLoadWithEmptySettings() {
|
public void testLoadWithEmptySettings() {
|
||||||
Map<String, GoogleCloudStorageClientSettings> clientsSettings = GoogleCloudStorageClientSettings.load(Settings.EMPTY);
|
final Map<String, GoogleCloudStorageClientSettings> clientsSettings = GoogleCloudStorageClientSettings.load(Settings.EMPTY);
|
||||||
assertEquals(1, clientsSettings.size());
|
assertEquals(1, clientsSettings.size());
|
||||||
assertNotNull(clientsSettings.get("default"));
|
assertNotNull(clientsSettings.get("default"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLoad() throws Exception {
|
public void testLoad() throws Exception {
|
||||||
final int nbClients = randomIntBetween(1, 5);
|
final int nbClients = randomIntBetween(1, 5);
|
||||||
final Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients = randomClients(nbClients);
|
final List<Setting<?>> deprecationWarnings = new ArrayList<>();
|
||||||
|
final Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients = randomClients(nbClients, deprecationWarnings);
|
||||||
final Map<String, GoogleCloudStorageClientSettings> expectedClientsSettings = randomClients.v1();
|
final Map<String, GoogleCloudStorageClientSettings> expectedClientsSettings = randomClients.v1();
|
||||||
|
|
||||||
Map<String, GoogleCloudStorageClientSettings> actualClientsSettings = GoogleCloudStorageClientSettings.load(randomClients.v2());
|
final Map<String, GoogleCloudStorageClientSettings> actualClientsSettings = GoogleCloudStorageClientSettings
|
||||||
|
.load(randomClients.v2());
|
||||||
assertEquals(expectedClientsSettings.size(), actualClientsSettings.size());
|
assertEquals(expectedClientsSettings.size(), actualClientsSettings.size());
|
||||||
|
|
||||||
for (String clientName : expectedClientsSettings.keySet()) {
|
for (final String clientName : expectedClientsSettings.keySet()) {
|
||||||
GoogleCloudStorageClientSettings actualClientSettings = actualClientsSettings.get(clientName);
|
final GoogleCloudStorageClientSettings actualClientSettings = actualClientsSettings.get(clientName);
|
||||||
assertNotNull(actualClientSettings);
|
assertNotNull(actualClientSettings);
|
||||||
GoogleCloudStorageClientSettings expectedClientSettings = expectedClientsSettings.get(clientName);
|
final GoogleCloudStorageClientSettings expectedClientSettings = expectedClientsSettings.get(clientName);
|
||||||
assertNotNull(expectedClientSettings);
|
assertNotNull(expectedClientSettings);
|
||||||
|
|
||||||
assertGoogleCredential(expectedClientSettings.getCredential(), actualClientSettings.getCredential());
|
assertGoogleCredential(expectedClientSettings.getCredential(), actualClientSettings.getCredential());
|
||||||
assertEquals(expectedClientSettings.getEndpoint(), actualClientSettings.getEndpoint());
|
assertEquals(expectedClientSettings.getHost(), actualClientSettings.getHost());
|
||||||
|
assertEquals(expectedClientSettings.getProjectId(), actualClientSettings.getProjectId());
|
||||||
assertEquals(expectedClientSettings.getConnectTimeout(), actualClientSettings.getConnectTimeout());
|
assertEquals(expectedClientSettings.getConnectTimeout(), actualClientSettings.getConnectTimeout());
|
||||||
assertEquals(expectedClientSettings.getReadTimeout(), actualClientSettings.getReadTimeout());
|
assertEquals(expectedClientSettings.getReadTimeout(), actualClientSettings.getReadTimeout());
|
||||||
assertEquals(expectedClientSettings.getApplicationName(), actualClientSettings.getApplicationName());
|
assertEquals(expectedClientSettings.getApplicationName(), actualClientSettings.getApplicationName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deprecationWarnings.isEmpty() == false) {
|
||||||
|
assertSettingDeprecationsAndWarnings(deprecationWarnings.toArray(new Setting<?>[0]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLoadCredential() throws Exception {
|
public void testLoadCredential() throws Exception {
|
||||||
Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClient = randomClients(1);
|
final List<Setting<?>> deprecationWarnings = new ArrayList<>();
|
||||||
GoogleCloudStorageClientSettings expectedClientSettings = randomClient.v1().values().iterator().next();
|
final Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClient = randomClients(1, deprecationWarnings);
|
||||||
String clientName = randomClient.v1().keySet().iterator().next();
|
final GoogleCloudStorageClientSettings expectedClientSettings = randomClient.v1().values().iterator().next();
|
||||||
|
final String clientName = randomClient.v1().keySet().iterator().next();
|
||||||
assertGoogleCredential(expectedClientSettings.getCredential(), loadCredential(randomClient.v2(), clientName));
|
assertGoogleCredential(expectedClientSettings.getCredential(), loadCredential(randomClient.v2(), clientName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testProjectIdDefaultsToCredentials() throws Exception {
|
||||||
|
final String clientName = randomAlphaOfLength(5);
|
||||||
|
final Tuple<ServiceAccountCredentials, byte[]> credentials = randomCredential(clientName);
|
||||||
|
final ServiceAccountCredentials credential = credentials.v1();
|
||||||
|
final GoogleCloudStorageClientSettings googleCloudStorageClientSettings = new GoogleCloudStorageClientSettings(credential,
|
||||||
|
ENDPOINT_SETTING.getDefault(Settings.EMPTY), PROJECT_ID_SETTING.getDefault(Settings.EMPTY),
|
||||||
|
CONNECT_TIMEOUT_SETTING.getDefault(Settings.EMPTY), READ_TIMEOUT_SETTING.getDefault(Settings.EMPTY),
|
||||||
|
APPLICATION_NAME_SETTING.getDefault(Settings.EMPTY), new URI(""));
|
||||||
|
assertEquals(credential.getProjectId(), googleCloudStorageClientSettings.getProjectId());
|
||||||
|
}
|
||||||
|
|
||||||
/** Generates a given number of GoogleCloudStorageClientSettings along with the Settings to build them from **/
|
/** Generates a given number of GoogleCloudStorageClientSettings along with the Settings to build them from **/
|
||||||
private Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients(final int nbClients) throws Exception {
|
private Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients(final int nbClients,
|
||||||
|
final List<Setting<?>> deprecationWarnings)
|
||||||
|
throws Exception {
|
||||||
final Map<String, GoogleCloudStorageClientSettings> expectedClients = new HashMap<>();
|
final Map<String, GoogleCloudStorageClientSettings> expectedClients = new HashMap<>();
|
||||||
expectedClients.put("default", getClientSettings(Settings.EMPTY, "default"));
|
|
||||||
|
|
||||||
final Settings.Builder settings = Settings.builder();
|
final Settings.Builder settings = Settings.builder();
|
||||||
final MockSecureSettings secureSettings = new MockSecureSettings();
|
final MockSecureSettings secureSettings = new MockSecureSettings();
|
||||||
|
|
||||||
for (int i = 0; i < nbClients; i++) {
|
for (int i = 0; i < nbClients; i++) {
|
||||||
String clientName = randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
|
final String clientName = randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
|
||||||
|
final GoogleCloudStorageClientSettings clientSettings = randomClient(clientName, settings, secureSettings, deprecationWarnings);
|
||||||
GoogleCloudStorageClientSettings clientSettings = randomClient(clientName, settings, secureSettings);
|
|
||||||
expectedClients.put(clientName, clientSettings);
|
expectedClients.put(clientName, clientSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
GoogleCloudStorageClientSettings clientSettings = randomClient("default", settings, secureSettings);
|
final GoogleCloudStorageClientSettings clientSettings = randomClient("default", settings, secureSettings, deprecationWarnings);
|
||||||
expectedClients.put("default", clientSettings);
|
expectedClients.put("default", clientSettings);
|
||||||
|
} else {
|
||||||
|
expectedClients.put("default", getClientSettings(Settings.EMPTY, "default"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Tuple.tuple(expectedClients, settings.setSecureSettings(secureSettings).build());
|
return Tuple.tuple(expectedClients, settings.setSecureSettings(secureSettings).build());
|
||||||
|
@ -107,20 +132,30 @@ public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
|
||||||
/** Generates a random GoogleCloudStorageClientSettings along with the Settings to build it **/
|
/** Generates a random GoogleCloudStorageClientSettings along with the Settings to build it **/
|
||||||
private static GoogleCloudStorageClientSettings randomClient(final String clientName,
|
private static GoogleCloudStorageClientSettings randomClient(final String clientName,
|
||||||
final Settings.Builder settings,
|
final Settings.Builder settings,
|
||||||
final MockSecureSettings secureSettings) throws Exception {
|
final MockSecureSettings secureSettings,
|
||||||
|
final List<Setting<?>> deprecationWarnings) throws Exception {
|
||||||
|
|
||||||
Tuple<GoogleCredential, byte[]> credentials = randomCredential(clientName);
|
final Tuple<ServiceAccountCredentials, byte[]> credentials = randomCredential(clientName);
|
||||||
GoogleCredential credential = credentials.v1();
|
final ServiceAccountCredentials credential = credentials.v1();
|
||||||
secureSettings.setFile(CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).getKey(), credentials.v2());
|
secureSettings.setFile(CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).getKey(), credentials.v2());
|
||||||
|
|
||||||
String endpoint;
|
String endpoint;
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
endpoint = randomAlphaOfLength(5);
|
endpoint = randomFrom("http://www.elastic.co", "http://metadata.google.com:88/oauth", "https://www.googleapis.com",
|
||||||
|
"https://www.elastic.co:443", "http://localhost:8443", "https://www.googleapis.com/oauth/token");
|
||||||
settings.put(ENDPOINT_SETTING.getConcreteSettingForNamespace(clientName).getKey(), endpoint);
|
settings.put(ENDPOINT_SETTING.getConcreteSettingForNamespace(clientName).getKey(), endpoint);
|
||||||
} else {
|
} else {
|
||||||
endpoint = ENDPOINT_SETTING.getDefault(Settings.EMPTY);
|
endpoint = ENDPOINT_SETTING.getDefault(Settings.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String projectId;
|
||||||
|
if (randomBoolean()) {
|
||||||
|
projectId = randomAlphaOfLength(5);
|
||||||
|
settings.put(PROJECT_ID_SETTING.getConcreteSettingForNamespace(clientName).getKey(), projectId);
|
||||||
|
} else {
|
||||||
|
projectId = PROJECT_ID_SETTING.getDefault(Settings.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
TimeValue connectTimeout;
|
TimeValue connectTimeout;
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
connectTimeout = randomTimeout();
|
connectTimeout = randomTimeout();
|
||||||
|
@ -141,40 +176,35 @@ public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
applicationName = randomAlphaOfLength(5);
|
applicationName = randomAlphaOfLength(5);
|
||||||
settings.put(APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName).getKey(), applicationName);
|
settings.put(APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName).getKey(), applicationName);
|
||||||
|
deprecationWarnings.add(APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName));
|
||||||
} else {
|
} else {
|
||||||
applicationName = APPLICATION_NAME_SETTING.getDefault(Settings.EMPTY);
|
applicationName = APPLICATION_NAME_SETTING.getDefault(Settings.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GoogleCloudStorageClientSettings(credential, endpoint, connectTimeout, readTimeout, applicationName);
|
return new GoogleCloudStorageClientSettings(credential, endpoint, projectId, connectTimeout, readTimeout, applicationName,
|
||||||
|
new URI(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generates a random GoogleCredential along with its corresponding Service Account file provided as a byte array **/
|
/** Generates a random GoogleCredential along with its corresponding Service Account file provided as a byte array **/
|
||||||
private static Tuple<GoogleCredential, byte[]> randomCredential(final String clientName) throws Exception {
|
private static Tuple<ServiceAccountCredentials, byte[]> randomCredential(final String clientName) throws Exception {
|
||||||
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
||||||
|
final ServiceAccountCredentials.Builder credentialBuilder = ServiceAccountCredentials.newBuilder();
|
||||||
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder();
|
credentialBuilder.setClientId("id_" + clientName);
|
||||||
credentialBuilder.setServiceAccountId(clientName);
|
credentialBuilder.setClientEmail(clientName);
|
||||||
credentialBuilder.setServiceAccountProjectId("project_id_" + clientName);
|
credentialBuilder.setProjectId("project_id_" + clientName);
|
||||||
credentialBuilder.setServiceAccountScopes(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL));
|
credentialBuilder.setPrivateKey(keyPair.getPrivate());
|
||||||
credentialBuilder.setServiceAccountPrivateKey(keyPair.getPrivate());
|
credentialBuilder.setPrivateKeyId("private_key_id_" + clientName);
|
||||||
credentialBuilder.setServiceAccountPrivateKeyId("private_key_id_" + clientName);
|
credentialBuilder.setScopes(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL));
|
||||||
|
final String encodedPrivateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
|
||||||
String encodedPrivateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
|
final String serviceAccount = "{\"type\":\"service_account\"," +
|
||||||
String serviceAccount = "{\"type\":\"service_account\"," +
|
|
||||||
"\"project_id\":\"project_id_" + clientName + "\"," +
|
"\"project_id\":\"project_id_" + clientName + "\"," +
|
||||||
"\"private_key_id\":\"private_key_id_" + clientName + "\"," +
|
"\"private_key_id\":\"private_key_id_" + clientName + "\"," +
|
||||||
"\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n" +
|
"\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n" +
|
||||||
encodedPrivateKey +
|
encodedPrivateKey +
|
||||||
"\\n-----END PRIVATE KEY-----\\n\"," +
|
"\\n-----END PRIVATE KEY-----\\n\"," +
|
||||||
"\"client_email\":\"" + clientName + "\"," +
|
"\"client_email\":\"" + clientName + "\"," +
|
||||||
"\"client_id\":\"id_" + clientName + "\"," +
|
"\"client_id\":\"id_" + clientName + "\"" +
|
||||||
"\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\"," +
|
"}";
|
||||||
"\"token_uri\":\"https://accounts.google.com/o/oauth2/token\"," +
|
|
||||||
"\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\"," +
|
|
||||||
"\"client_x509_cert_url\":\"https://www.googleapis.com/robot/v1/metadata/x509/" +
|
|
||||||
clientName +
|
|
||||||
"%40appspot.gserviceaccount.com\"}";
|
|
||||||
|
|
||||||
return Tuple.tuple(credentialBuilder.build(), serviceAccount.getBytes(StandardCharsets.UTF_8));
|
return Tuple.tuple(credentialBuilder.build(), serviceAccount.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,14 +212,16 @@ public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
|
||||||
return randomFrom(TimeValue.MINUS_ONE, TimeValue.ZERO, TimeValue.parseTimeValue(randomPositiveTimeValue(), "test"));
|
return randomFrom(TimeValue.MINUS_ONE, TimeValue.ZERO, TimeValue.parseTimeValue(randomPositiveTimeValue(), "test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertGoogleCredential(final GoogleCredential expected, final GoogleCredential actual) {
|
private static void assertGoogleCredential(ServiceAccountCredentials expected, ServiceAccountCredentials actual) {
|
||||||
if (expected != null) {
|
if (expected != null) {
|
||||||
assertEquals(expected.getServiceAccountUser(), actual.getServiceAccountUser());
|
assertEquals(expected.getServiceAccountUser(), actual.getServiceAccountUser());
|
||||||
assertEquals(expected.getServiceAccountId(), actual.getServiceAccountId());
|
assertEquals(expected.getClientId(), actual.getClientId());
|
||||||
assertEquals(expected.getServiceAccountProjectId(), actual.getServiceAccountProjectId());
|
assertEquals(expected.getClientEmail(), actual.getClientEmail());
|
||||||
assertEquals(expected.getServiceAccountScopesAsString(), actual.getServiceAccountScopesAsString());
|
assertEquals(expected.getAccount(), actual.getAccount());
|
||||||
assertEquals(expected.getServiceAccountPrivateKey(), actual.getServiceAccountPrivateKey());
|
assertEquals(expected.getProjectId(), actual.getProjectId());
|
||||||
assertEquals(expected.getServiceAccountPrivateKeyId(), actual.getServiceAccountPrivateKeyId());
|
assertEquals(expected.getScopes(), actual.getScopes());
|
||||||
|
assertEquals(expected.getPrivateKey(), actual.getPrivateKey());
|
||||||
|
assertEquals(expected.getPrivateKeyId(), actual.getPrivateKeyId());
|
||||||
} else {
|
} else {
|
||||||
assertNull(actual);
|
assertNull(actual);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,79 +19,65 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
import com.google.auth.Credentials;
|
||||||
import com.google.api.client.http.GenericUrl;
|
import com.google.cloud.http.HttpTransportOptions;
|
||||||
import com.google.api.client.http.HttpIOExceptionHandler;
|
import com.google.cloud.storage.Storage;
|
||||||
import com.google.api.client.http.HttpRequest;
|
|
||||||
import com.google.api.client.http.HttpRequestFactory;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import com.google.api.client.http.HttpRequestInitializer;
|
|
||||||
import com.google.api.client.http.HttpResponse;
|
|
||||||
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
|
|
||||||
import com.google.api.client.testing.http.MockHttpTransport;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyBoolean;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class GoogleCloudStorageServiceTests extends ESTestCase {
|
public class GoogleCloudStorageServiceTests extends ESTestCase {
|
||||||
|
|
||||||
/**
|
public void testClientInitializer() throws Exception {
|
||||||
* Test that the {@link GoogleCloudStorageService.DefaultHttpRequestInitializer} attaches new instances
|
final String clientName = randomAlphaOfLength(4).toLowerCase(Locale.ROOT);
|
||||||
* of {@link HttpIOExceptionHandler} and {@link HttpUnsuccessfulResponseHandler} for every HTTP requests.
|
|
||||||
*/
|
|
||||||
public void testDefaultHttpRequestInitializer() throws IOException {
|
|
||||||
final Environment environment = mock(Environment.class);
|
final Environment environment = mock(Environment.class);
|
||||||
when(environment.settings()).thenReturn(Settings.EMPTY);
|
final TimeValue connectTimeValue = TimeValue.timeValueNanos(randomIntBetween(0, 2000000));
|
||||||
|
final TimeValue readTimeValue = TimeValue.timeValueNanos(randomIntBetween(0, 2000000));
|
||||||
final GoogleCredential credential = mock(GoogleCredential.class);
|
final String applicationName = randomAlphaOfLength(4);
|
||||||
when(credential.handleResponse(any(HttpRequest.class), any(HttpResponse.class), anyBoolean())).thenReturn(false);
|
final String hostName = randomFrom("http://", "https://") + randomAlphaOfLength(4) + ":" + randomIntBetween(1, 65535);
|
||||||
|
final String projectIdName = randomAlphaOfLength(4);
|
||||||
final TimeValue readTimeout = TimeValue.timeValueSeconds(randomIntBetween(1, 120));
|
final Settings settings = Settings.builder()
|
||||||
final TimeValue connectTimeout = TimeValue.timeValueSeconds(randomIntBetween(1, 120));
|
.put(GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING.getConcreteSettingForNamespace(clientName).getKey(),
|
||||||
final String endpoint = randomBoolean() ? randomAlphaOfLength(10) : null;
|
connectTimeValue.getStringRep())
|
||||||
final String applicationName = randomBoolean() ? randomAlphaOfLength(10) : null;
|
.put(GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING.getConcreteSettingForNamespace(clientName).getKey(),
|
||||||
|
readTimeValue.getStringRep())
|
||||||
final GoogleCloudStorageClientSettings clientSettings =
|
.put(GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName).getKey(),
|
||||||
new GoogleCloudStorageClientSettings(credential, endpoint, connectTimeout, readTimeout, applicationName);
|
applicationName)
|
||||||
|
.put(GoogleCloudStorageClientSettings.ENDPOINT_SETTING.getConcreteSettingForNamespace(clientName).getKey(), hostName)
|
||||||
final HttpRequestInitializer initializer = GoogleCloudStorageService.createRequestInitializer(clientSettings);
|
.put(GoogleCloudStorageClientSettings.PROJECT_ID_SETTING.getConcreteSettingForNamespace(clientName).getKey(), projectIdName)
|
||||||
final HttpRequestFactory requestFactory = new MockHttpTransport().createRequestFactory(initializer);
|
.build();
|
||||||
|
when(environment.settings()).thenReturn(settings);
|
||||||
final HttpRequest request1 = requestFactory.buildGetRequest(new GenericUrl());
|
final GoogleCloudStorageClientSettings clientSettings = GoogleCloudStorageClientSettings.getClientSettings(settings, clientName);
|
||||||
assertEquals((int) connectTimeout.millis(), request1.getConnectTimeout());
|
final GoogleCloudStorageService service = new GoogleCloudStorageService(environment,
|
||||||
assertEquals((int) readTimeout.millis(), request1.getReadTimeout());
|
Collections.singletonMap(clientName, clientSettings));
|
||||||
assertSame(credential, request1.getInterceptor());
|
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.createClient("another_client"));
|
||||||
assertNotNull(request1.getIOExceptionHandler());
|
assertThat(e.getMessage(), Matchers.startsWith("Unknown client name"));
|
||||||
assertNotNull(request1.getUnsuccessfulResponseHandler());
|
assertSettingDeprecationsAndWarnings(
|
||||||
|
new Setting<?>[] { GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName) });
|
||||||
final HttpRequest request2 = requestFactory.buildGetRequest(new GenericUrl());
|
final Storage storage = service.createClient(clientName);
|
||||||
assertEquals((int) connectTimeout.millis(), request2.getConnectTimeout());
|
assertThat(storage.getOptions().getApplicationName(), Matchers.containsString(applicationName));
|
||||||
assertEquals((int) readTimeout.millis(), request2.getReadTimeout());
|
assertThat(storage.getOptions().getHost(), Matchers.is(hostName));
|
||||||
assertSame(request1.getInterceptor(), request2.getInterceptor());
|
assertThat(storage.getOptions().getProjectId(), Matchers.is(projectIdName));
|
||||||
assertNotNull(request2.getIOExceptionHandler());
|
assertThat(storage.getOptions().getTransportOptions(), Matchers.instanceOf(HttpTransportOptions.class));
|
||||||
assertNotSame(request1.getIOExceptionHandler(), request2.getIOExceptionHandler());
|
assertThat(((HttpTransportOptions) storage.getOptions().getTransportOptions()).getConnectTimeout(),
|
||||||
assertNotNull(request2.getUnsuccessfulResponseHandler());
|
Matchers.is((int) connectTimeValue.millis()));
|
||||||
assertNotSame(request1.getUnsuccessfulResponseHandler(), request2.getUnsuccessfulResponseHandler());
|
assertThat(((HttpTransportOptions) storage.getOptions().getTransportOptions()).getReadTimeout(),
|
||||||
|
Matchers.is((int) readTimeValue.millis()));
|
||||||
request1.getUnsuccessfulResponseHandler().handleResponse(null, null, false);
|
assertThat(storage.getOptions().getCredentials(), Matchers.nullValue(Credentials.class));
|
||||||
verify(credential, times(1)).handleResponse(any(HttpRequest.class), any(HttpResponse.class), anyBoolean());
|
|
||||||
|
|
||||||
request2.getUnsuccessfulResponseHandler().handleResponse(null, null, false);
|
|
||||||
verify(credential, times(2)).handleResponse(any(HttpRequest.class), any(HttpResponse.class), anyBoolean());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToTimeout() {
|
public void testToTimeout() {
|
||||||
assertNull(GoogleCloudStorageService.toTimeout(null));
|
assertEquals(-1, GoogleCloudStorageService.toTimeout(null).intValue());
|
||||||
assertNull(GoogleCloudStorageService.toTimeout(TimeValue.ZERO));
|
assertEquals(-1, GoogleCloudStorageService.toTimeout(TimeValue.ZERO).intValue());
|
||||||
assertEquals(0, GoogleCloudStorageService.toTimeout(TimeValue.MINUS_ONE).intValue());
|
assertEquals(0, GoogleCloudStorageService.toTimeout(TimeValue.MINUS_ONE).intValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,289 +19,478 @@
|
||||||
|
|
||||||
package org.elasticsearch.repositories.gcs;
|
package org.elasticsearch.repositories.gcs;
|
||||||
|
|
||||||
import com.google.api.client.googleapis.json.GoogleJsonError;
|
import com.google.api.gax.paging.Page;
|
||||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
import com.google.cloud.Policy;
|
||||||
import com.google.api.client.http.AbstractInputStreamContent;
|
import com.google.cloud.ReadChannel;
|
||||||
import com.google.api.client.http.HttpHeaders;
|
import com.google.cloud.RestorableState;
|
||||||
import com.google.api.client.http.HttpMethods;
|
import com.google.cloud.WriteChannel;
|
||||||
import com.google.api.client.http.HttpRequest;
|
import com.google.cloud.storage.Acl;
|
||||||
import com.google.api.client.http.HttpRequestInitializer;
|
import com.google.cloud.storage.Blob;
|
||||||
import com.google.api.client.http.HttpResponseException;
|
import com.google.cloud.storage.BlobId;
|
||||||
import com.google.api.client.http.LowLevelHttpRequest;
|
import com.google.cloud.storage.BlobInfo;
|
||||||
import com.google.api.client.http.LowLevelHttpResponse;
|
import com.google.cloud.storage.Bucket;
|
||||||
import com.google.api.client.http.MultipartContent;
|
import com.google.cloud.storage.BucketInfo;
|
||||||
import com.google.api.client.json.JsonFactory;
|
import com.google.cloud.storage.CopyWriter;
|
||||||
import com.google.api.client.testing.http.MockHttpTransport;
|
import com.google.cloud.storage.ServiceAccount;
|
||||||
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
|
import com.google.cloud.storage.Storage;
|
||||||
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
|
import com.google.cloud.storage.StorageBatch;
|
||||||
import com.google.api.services.storage.Storage;
|
import com.google.cloud.storage.StorageException;
|
||||||
import com.google.api.services.storage.model.Bucket;
|
import com.google.cloud.storage.StorageOptions;
|
||||||
import com.google.api.services.storage.model.StorageObject;
|
import com.google.cloud.storage.StorageRpcOptionUtils;
|
||||||
import org.elasticsearch.common.io.Streams;
|
import com.google.cloud.storage.StorageTestUtils;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
|
||||||
|
import org.elasticsearch.core.internal.io.IOUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigInteger;
|
import java.net.URL;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import static org.mockito.Mockito.mock;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link MockStorage} mocks a {@link Storage} client by storing all the blobs
|
* {@link MockStorage} mocks a {@link Storage} client by storing all the blobs
|
||||||
* in a given concurrent map.
|
* in a given concurrent map.
|
||||||
*/
|
*/
|
||||||
class MockStorage extends Storage {
|
class MockStorage implements Storage {
|
||||||
|
|
||||||
/* A custom HTTP header name used to propagate the name of the blobs to delete in batch requests */
|
|
||||||
private static final String DELETION_HEADER = "x-blob-to-delete";
|
|
||||||
|
|
||||||
private final String bucketName;
|
private final String bucketName;
|
||||||
private final ConcurrentMap<String, byte[]> blobs;
|
private final ConcurrentMap<String, byte[]> blobs;
|
||||||
|
|
||||||
MockStorage(final String bucket, final ConcurrentMap<String, byte[]> blobs) {
|
MockStorage(final String bucket, final ConcurrentMap<String, byte[]> blobs) {
|
||||||
super(new MockedHttpTransport(blobs), mock(JsonFactory.class), mock(HttpRequestInitializer.class));
|
this.bucketName = Objects.requireNonNull(bucket);
|
||||||
this.bucketName = bucket;
|
this.blobs = Objects.requireNonNull(blobs);
|
||||||
this.blobs = blobs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Buckets buckets() {
|
public Bucket get(String bucket, BucketGetOption... options) {
|
||||||
return new MockBuckets();
|
if (bucketName.equals(bucket)) {
|
||||||
|
return StorageTestUtils.createBucket(this, bucketName);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Objects objects() {
|
public Blob get(BlobId blob) {
|
||||||
return new MockObjects();
|
if (bucketName.equals(blob.getBucket())) {
|
||||||
}
|
final byte[] bytes = blobs.get(blob.getName());
|
||||||
|
if (bytes != null) {
|
||||||
class MockBuckets extends Buckets {
|
return StorageTestUtils.createBlob(this, bucketName, blob.getName(), bytes.length);
|
||||||
|
|
||||||
@Override
|
|
||||||
public Get get(String getBucket) {
|
|
||||||
return new Get(getBucket) {
|
|
||||||
@Override
|
|
||||||
public Bucket execute() {
|
|
||||||
if (bucketName.equals(getBucket())) {
|
|
||||||
Bucket bucket = new Bucket();
|
|
||||||
bucket.setId(bucketName);
|
|
||||||
return bucket;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockObjects extends Objects {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Get get(String getBucket, String getObject) {
|
|
||||||
return new Get(getBucket, getObject) {
|
|
||||||
@Override
|
|
||||||
public StorageObject execute() throws IOException {
|
|
||||||
if (bucketName.equals(getBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getBucket());
|
|
||||||
}
|
|
||||||
if (blobs.containsKey(getObject()) == false) {
|
|
||||||
throw newObjectNotFoundException(getObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageObject storageObject = new StorageObject();
|
|
||||||
storageObject.setId(getObject());
|
|
||||||
return storageObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream executeMediaAsInputStream() throws IOException {
|
|
||||||
if (bucketName.equals(getBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getBucket());
|
|
||||||
}
|
|
||||||
if (blobs.containsKey(getObject()) == false) {
|
|
||||||
throw newObjectNotFoundException(getObject());
|
|
||||||
}
|
|
||||||
return new ByteArrayInputStream(blobs.get(getObject()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Insert insert(String insertBucket, StorageObject insertObject, AbstractInputStreamContent insertStream) {
|
|
||||||
return new Insert(insertBucket, insertObject) {
|
|
||||||
@Override
|
|
||||||
public StorageObject execute() throws IOException {
|
|
||||||
if (bucketName.equals(getBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getBucket());
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
Streams.copy(insertStream.getInputStream(), out);
|
|
||||||
blobs.put(getName(), out.toByteArray());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List list(String listBucket) {
|
|
||||||
return new List(listBucket) {
|
|
||||||
@Override
|
|
||||||
public com.google.api.services.storage.model.Objects execute() throws IOException {
|
|
||||||
if (bucketName.equals(getBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getBucket());
|
|
||||||
}
|
|
||||||
|
|
||||||
final com.google.api.services.storage.model.Objects objects = new com.google.api.services.storage.model.Objects();
|
|
||||||
|
|
||||||
final java.util.List<StorageObject> storageObjects = new ArrayList<>();
|
|
||||||
for (Entry<String, byte[]> blob : blobs.entrySet()) {
|
|
||||||
if (getPrefix() == null || blob.getKey().startsWith(getPrefix())) {
|
|
||||||
StorageObject storageObject = new StorageObject();
|
|
||||||
storageObject.setId(blob.getKey());
|
|
||||||
storageObject.setName(blob.getKey());
|
|
||||||
storageObject.setSize(BigInteger.valueOf((long) blob.getValue().length));
|
|
||||||
storageObjects.add(storageObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
objects.setItems(storageObjects);
|
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Delete delete(String deleteBucket, String deleteObject) {
|
|
||||||
return new Delete(deleteBucket, deleteObject) {
|
|
||||||
@Override
|
|
||||||
public Void execute() throws IOException {
|
|
||||||
if (bucketName.equals(getBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getBucket());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blobs.containsKey(getObject()) == false) {
|
|
||||||
throw newObjectNotFoundException(getObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
blobs.remove(getObject());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpRequest buildHttpRequest() throws IOException {
|
|
||||||
HttpRequest httpRequest = super.buildHttpRequest();
|
|
||||||
httpRequest.getHeaders().put(DELETION_HEADER, getObject());
|
|
||||||
return httpRequest;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Copy copy(String srcBucket, String srcObject, String destBucket, String destObject, StorageObject content) {
|
|
||||||
return new Copy(srcBucket, srcObject, destBucket, destObject, content) {
|
|
||||||
@Override
|
|
||||||
public StorageObject execute() throws IOException {
|
|
||||||
if (bucketName.equals(getSourceBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getSourceBucket());
|
|
||||||
}
|
|
||||||
if (bucketName.equals(getDestinationBucket()) == false) {
|
|
||||||
throw newBucketNotFoundException(getDestinationBucket());
|
|
||||||
}
|
|
||||||
|
|
||||||
final byte[] bytes = blobs.get(getSourceObject());
|
|
||||||
if (bytes == null) {
|
|
||||||
throw newObjectNotFoundException(getSourceObject());
|
|
||||||
}
|
|
||||||
blobs.put(getDestinationObject(), bytes);
|
|
||||||
|
|
||||||
StorageObject storageObject = new StorageObject();
|
|
||||||
storageObject.setId(getDestinationObject());
|
|
||||||
return storageObject;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GoogleJsonResponseException newBucketNotFoundException(final String bucket) {
|
|
||||||
HttpResponseException.Builder builder = new HttpResponseException.Builder(404, "Bucket not found: " + bucket, new HttpHeaders());
|
|
||||||
return new GoogleJsonResponseException(builder, new GoogleJsonError());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GoogleJsonResponseException newObjectNotFoundException(final String object) {
|
|
||||||
HttpResponseException.Builder builder = new HttpResponseException.Builder(404, "Object not found: " + object, new HttpHeaders());
|
|
||||||
return new GoogleJsonResponseException(builder, new GoogleJsonError());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link MockedHttpTransport} extends the existing testing transport to analyze the content
|
|
||||||
* of {@link com.google.api.client.googleapis.batch.BatchRequest} and delete the appropriates
|
|
||||||
* blobs. We use this because {@link Storage#batch()} is final and there is no other way to
|
|
||||||
* extend batch requests for testing purposes.
|
|
||||||
*/
|
|
||||||
static class MockedHttpTransport extends MockHttpTransport {
|
|
||||||
|
|
||||||
private final ConcurrentMap<String, byte[]> blobs;
|
|
||||||
|
|
||||||
MockedHttpTransport(final ConcurrentMap<String, byte[]> blobs) {
|
|
||||||
this.blobs = blobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LowLevelHttpRequest buildRequest(final String method, final String url) throws IOException {
|
|
||||||
// We analyze the content of the Batch request to detect our custom HTTP header,
|
|
||||||
// and extract from it the name of the blob to delete. Then we reply a simple
|
|
||||||
// batch response so that the client parser is happy.
|
|
||||||
//
|
|
||||||
// See https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch for the
|
|
||||||
// format of the batch request body.
|
|
||||||
if (HttpMethods.POST.equals(method) && url.endsWith("/batch")) {
|
|
||||||
return new MockLowLevelHttpRequest() {
|
|
||||||
@Override
|
|
||||||
public LowLevelHttpResponse execute() throws IOException {
|
|
||||||
final String contentType = new MultipartContent().getType();
|
|
||||||
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
|
||||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
|
||||||
getStreamingContent().writeTo(out);
|
|
||||||
|
|
||||||
Streams.readAllLines(new ByteArrayInputStream(out.toByteArray()), line -> {
|
|
||||||
if (line != null && line.startsWith(DELETION_HEADER)) {
|
|
||||||
builder.append("--__END_OF_PART__\r\n");
|
|
||||||
builder.append("Content-Type: application/http").append("\r\n");
|
|
||||||
builder.append("\r\n");
|
|
||||||
builder.append("HTTP/1.1 ");
|
|
||||||
|
|
||||||
final String blobName = line.substring(line.indexOf(':') + 1).trim();
|
|
||||||
if (blobs.containsKey(blobName)) {
|
|
||||||
builder.append(RestStatus.OK.getStatus());
|
|
||||||
blobs.remove(blobName);
|
|
||||||
} else {
|
|
||||||
builder.append(RestStatus.NOT_FOUND.getStatus());
|
|
||||||
}
|
|
||||||
builder.append("\r\n");
|
|
||||||
builder.append("Content-Type: application/json; charset=UTF-8").append("\r\n");
|
|
||||||
builder.append("Content-Length: 0").append("\r\n");
|
|
||||||
builder.append("\r\n");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.append("\r\n");
|
|
||||||
builder.append("--__END_OF_PART__--");
|
|
||||||
}
|
|
||||||
|
|
||||||
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
|
|
||||||
response.setStatusCode(200);
|
|
||||||
response.setContent(builder.toString());
|
|
||||||
response.setContentType(contentType);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return super.buildRequest(method, url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(BlobId blob) {
|
||||||
|
if (bucketName.equals(blob.getBucket()) && blobs.containsKey(blob.getName())) {
|
||||||
|
return blobs.remove(blob.getName()) != null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> delete(Iterable<BlobId> blobIds) {
|
||||||
|
final List<Boolean> ans = new ArrayList<>();
|
||||||
|
for (final BlobId blobId : blobIds) {
|
||||||
|
ans.add(delete(blobId));
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) {
|
||||||
|
if (bucketName.equals(blobInfo.getBucket()) == false) {
|
||||||
|
throw new StorageException(404, "Bucket not found");
|
||||||
|
}
|
||||||
|
blobs.put(blobInfo.getName(), content);
|
||||||
|
return get(BlobId.of(blobInfo.getBucket(), blobInfo.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CopyWriter copy(CopyRequest copyRequest) {
|
||||||
|
if (bucketName.equals(copyRequest.getSource().getBucket()) == false) {
|
||||||
|
throw new StorageException(404, "Source bucket not found");
|
||||||
|
}
|
||||||
|
if (bucketName.equals(copyRequest.getTarget().getBucket()) == false) {
|
||||||
|
throw new StorageException(404, "Target bucket not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] bytes = blobs.get(copyRequest.getSource().getName());
|
||||||
|
if (bytes == null) {
|
||||||
|
throw new StorageException(404, "Source blob does not exist");
|
||||||
|
}
|
||||||
|
blobs.put(copyRequest.getTarget().getName(), bytes);
|
||||||
|
return StorageRpcOptionUtils
|
||||||
|
.createCopyWriter(get(BlobId.of(copyRequest.getTarget().getBucket(), copyRequest.getTarget().getName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Blob> list(String bucket, BlobListOption... options) {
|
||||||
|
if (bucketName.equals(bucket) == false) {
|
||||||
|
throw new StorageException(404, "Bucket not found");
|
||||||
|
}
|
||||||
|
final Storage storage = this;
|
||||||
|
final String prefix = StorageRpcOptionUtils.getPrefix(options);
|
||||||
|
|
||||||
|
return new Page<Blob>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNextPage() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNextPageToken() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Blob> getNextPage() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Blob> iterateAll() {
|
||||||
|
return blobs.entrySet().stream()
|
||||||
|
.filter(blob -> ((prefix == null) || blob.getKey().startsWith(prefix)))
|
||||||
|
.map(blob -> StorageTestUtils.createBlob(storage, bucketName, blob.getKey(), blob.getValue().length))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Blob> getValues() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadChannel reader(BlobId blob, BlobSourceOption... options) {
|
||||||
|
if (bucketName.equals(blob.getBucket())) {
|
||||||
|
final byte[] bytes = blobs.get(blob.getName());
|
||||||
|
final ReadableByteChannel readableByteChannel = Channels.newChannel(new ByteArrayInputStream(bytes));
|
||||||
|
return new ReadChannel() {
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
IOUtils.closeWhileHandlingException(readableByteChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long position) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunkSize(int chunkSize) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestorableState<ReadChannel> capture() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ByteBuffer dst) throws IOException {
|
||||||
|
return readableByteChannel.read(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return readableByteChannel.isOpen();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) {
|
||||||
|
if (bucketName.equals(blobInfo.getBucket())) {
|
||||||
|
final ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
return new WriteChannel() {
|
||||||
|
|
||||||
|
final WritableByteChannel writableByteChannel = Channels.newChannel(output);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunkSize(int chunkSize) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestorableState<WriteChannel> capture() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int write(ByteBuffer src) throws IOException {
|
||||||
|
return writableByteChannel.write(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return writableByteChannel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
IOUtils.closeWhileHandlingException(writableByteChannel);
|
||||||
|
blobs.put(blobInfo.getName(), output.toByteArray());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything below this line is not implemented.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob create(BlobInfo blobInfo, BlobTargetOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob get(String bucket, String blob, BlobGetOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob get(BlobId blob, BlobGetOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Bucket> list(BucketListOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob update(BlobInfo blobInfo, BlobTargetOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob update(BlobInfo blobInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(String bucket, BucketSourceOption... options) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(String bucket, String blob, BlobSourceOption... options) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(BlobId blob, BlobSourceOption... options) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob compose(ComposeRequest composeRequest) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... options) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StorageBatch batch() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadChannel reader(String bucket, String blob, BlobSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Blob> get(BlobId... blobIds) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Blob> get(Iterable<BlobId> blobIds) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Blob> update(BlobInfo... blobInfos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Blob> update(Iterable<BlobInfo> blobInfos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> delete(BlobId... blobIds) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl getAcl(String bucket, Acl.Entity entity, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl getAcl(String bucket, Acl.Entity entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteAcl(String bucket, Acl.Entity entity, BucketSourceOption... options) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteAcl(String bucket, Acl.Entity entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl createAcl(String bucket, Acl acl, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl createAcl(String bucket, Acl acl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl updateAcl(String bucket, Acl acl, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl updateAcl(String bucket, Acl acl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Acl> listAcls(String bucket, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Acl> listAcls(String bucket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl getDefaultAcl(String bucket, Acl.Entity entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteDefaultAcl(String bucket, Acl.Entity entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl createDefaultAcl(String bucket, Acl acl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl updateDefaultAcl(String bucket, Acl acl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Acl> listDefaultAcls(String bucket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl getAcl(BlobId blob, Acl.Entity entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteAcl(BlobId blob, Acl.Entity entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl createAcl(BlobId blob, Acl acl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acl updateAcl(BlobId blob, Acl acl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Acl> listAcls(BlobId blob) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Policy getIamPolicy(String bucket, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> testIamPermissions(String bucket, List<String> permissions, BucketSourceOption... options) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServiceAccount getServiceAccount(String projectId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StorageOptions getOptions() {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue