Skip to content

Commit 240aa34

Browse files
committed
Use client settings in repository-gcs (#28575)
Similarly to what has been done for s3 and azure, this commit removes the repository settings `application_name` and `connect/read_timeout` in favor of client settings. It introduce a GoogleCloudStorageClientSettings class (similar to S3ClientSettings) and a bunch of unit tests for that, it aligns the documentation to be more coherent with the S3 one, it documents the connect/read timeouts that were not documented at all and also adds a new client setting that allows to define a custom endpoint.
1 parent 1229699 commit 240aa34

File tree

13 files changed

+719
-224
lines changed

13 files changed

+719
-224
lines changed

docs/plugins/repository-gcs.asciidoc

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,89 @@ PUT _snapshot/my_gcs_repository
116116
// CONSOLE
117117
// TEST[skip:we don't have gcs setup while testing this]
118118

119-
[[repository-gcs-bucket-permission]]
120-
===== Set Bucket Permission
119+
[[repository-gcs-client]]
120+
==== Client Settings
121121

122-
The service account used to access the bucket must have the "Writer" access to the bucket:
122+
The client used to connect to Google Cloud Storage has a number of settings available.
123+
Client setting names are of the form `gcs.client.CLIENT_NAME.SETTING_NAME` and specified
124+
inside `elasticsearch.yml`. The default client name looked up by a `gcs` repository is
125+
called `default`, but can be customized with the repository setting `client`.
123126

124-
1. Connect to the https://console.cloud.google.com/[Google Cloud Platform Console]
125-
2. Select your project
126-
3. Got to the https://console.cloud.google.com/storage/browser[Storage Browser]
127-
4. Select the bucket and "Edit bucket permission"
128-
5. The service account must be configured as a "User" with "Writer" access
127+
For example:
128+
129+
[source,js]
130+
----
131+
PUT _snapshot/my_gcs_repository
132+
{
133+
"type": "gcs",
134+
"settings": {
135+
"bucket": "my_bucket",
136+
"client": "my_alternate_client"
137+
}
138+
}
139+
----
140+
// CONSOLE
141+
// TEST[skip:we don't have gcs setup while testing this]
142+
143+
Some settings are sensitive and must be stored in the
144+
{ref}/secure-settings.html[elasticsearch keystore]. This is the case for the service account file:
145+
146+
[source,sh]
147+
----
148+
bin/elasticsearch-keystore add-file gcs.client.default.credentials_file
149+
----
150+
151+
The following are the available client settings. Those that must be stored in the keystore
152+
are marked as `Secure`.
129153

154+
`credentials_file`::
155+
156+
The service account file that is used to authenticate to the Google Cloud Storage service. (Secure)
157+
158+
`endpoint`::
159+
160+
The Google Cloud Storage service endpoint to connect to. This will be automatically
161+
determined by the Google Cloud Storage client but can be specified explicitly.
162+
163+
`connect_timeout`::
164+
165+
The timeout to establish a connection to the Google Cloud Storage service. The value should
166+
specify the unit. For example, a value of `5s` specifies a 5 second timeout. The value of `-1`
167+
corresponds to an infinite timeout. The default value is 20 seconds.
168+
169+
`read_timeout`::
170+
171+
The timeout to read data from an established connection. The value should
172+
specify the unit. For example, a value of `5s` specifies a 5 second timeout. The value of `-1`
173+
corresponds to an infinite timeout. The default value is 20 seconds.
174+
175+
`application_name`::
176+
177+
Name used by the client when it uses the Google Cloud Storage service. Setting
178+
a custom name can be useful to authenticate your cluster when requests
179+
statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
130180

131181
[[repository-gcs-repository]]
132-
==== Create a Repository
182+
==== Repository Settings
183+
184+
The `gcs` repository type supports a number of settings to customize how data
185+
is stored in Google Cloud Storage.
133186

134-
Once everything is installed and every node is started, you can create a new repository that
135-
uses Google Cloud Storage to store snapshots:
187+
These can be specified when creating the repository. For example:
136188

137189
[source,js]
138190
----
139191
PUT _snapshot/my_gcs_repository
140192
{
141193
"type": "gcs",
142194
"settings": {
143-
"bucket": "my_bucket"
195+
"bucket": "my_other_bucket",
196+
"base_path": "dev"
144197
}
145198
}
146199
----
147200
// CONSOLE
148-
// TEST[skip:we don't have gcs setup while testing this]
201+
// TEST[skip:we don't have gcs set up while testing this]
149202

150203
The following settings are supported:
151204

@@ -155,8 +208,8 @@ The following settings are supported:
155208

156209
`client`::
157210

158-
The client congfiguration to use. This controls which credentials are used to connect
159-
to Compute Engine.
211+
The name of the client to use to connect to Google Cloud Storage.
212+
Defaults to `default`.
160213

161214
`base_path`::
162215

@@ -177,6 +230,16 @@ The following settings are supported:
177230

178231
`application_name`::
179232

180-
Name used by the plugin when it uses the Google Cloud JSON API. Setting
181-
a custom name can be useful to authenticate your cluster when requests
182-
statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
233+
deprecated[6.3.0, This setting is now defined in the <<repository-gcs-client, client settings>>]
234+
Name used by the client when it uses the Google Cloud Storage service.
235+
236+
[[repository-gcs-bucket-permission]]
237+
===== Recommended Bucket Permission
238+
239+
The service account used to access the bucket must have the "Writer" access to the bucket:
240+
241+
1. Connect to the https://console.cloud.google.com/[Google Cloud Platform Console]
242+
2. Select your project
243+
3. Got to the https://console.cloud.google.com/storage/browser[Storage Browser]
244+
4. Select the bucket and "Edit bucket permission"
245+
5. The service account must be configured as a "User" with "Writer" access

docs/plugins/repository-s3.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ PUT _snapshot/my_s3_repository
3636

3737
The client used to connect to S3 has a number of settings available. Client setting names are of
3838
the form `s3.client.CLIENT_NAME.SETTING_NAME` and specified inside `elasticsearch.yml`. The
39-
default client name looked up by an s3 repository is called `default`, but can be customized
39+
default client name looked up by a `s3` repository is called `default`, but can be customized
4040
with the repository setting `client`. For example:
4141

4242
[source,js]

docs/reference/migration/index.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ include::migrate_6_0.asciidoc[]
2424
include::migrate_6_1.asciidoc[]
2525

2626
include::migrate_6_2.asciidoc[]
27+
28+
include::migrate_6_3.asciidoc[]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[[breaking-changes-6.3]]
2+
== Breaking changes in 6.3
3+
4+
[[breaking_63_plugins_changes]]
5+
=== Plugins changes
6+
7+
==== GCS Repository plugin
8+
9+
* The repository settings `application_name`, `connect_timeout` and `read_timeout` have been deprecated and
10+
must now be specified in the client settings instead.
11+
12+
See {plugins}/repository-gcs-client.html#repository-gcs-client[Google Cloud Storage Client Settings].
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.repositories.gcs;
20+
21+
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
22+
import com.google.api.services.storage.StorageScopes;
23+
import org.elasticsearch.common.settings.SecureSetting;
24+
import org.elasticsearch.common.settings.Setting;
25+
import org.elasticsearch.common.settings.Settings;
26+
import org.elasticsearch.common.unit.TimeValue;
27+
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import java.io.UncheckedIOException;
31+
import java.util.Collections;
32+
import java.util.HashMap;
33+
import java.util.Map;
34+
35+
import static org.elasticsearch.common.settings.Setting.timeSetting;
36+
37+
/**
38+
* Container for Google Cloud Storage clients settings.
39+
*/
40+
public class GoogleCloudStorageClientSettings {
41+
42+
private static final String PREFIX = "gcs.client.";
43+
44+
/** A json Service Account file loaded from secure settings. */
45+
static final Setting.AffixSetting<InputStream> CREDENTIALS_FILE_SETTING = Setting.affixKeySetting(PREFIX, "credentials_file",
46+
key -> SecureSetting.secureFile(key, null));
47+
48+
/** An override for the Storage endpoint to connect to. */
49+
static final Setting.AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(PREFIX, "endpoint",
50+
key -> new Setting<>(key, "", s -> s, Setting.Property.NodeScope));
51+
52+
/**
53+
* The timeout to establish a connection. A value of {@code -1} corresponds to an infinite timeout. A value of {@code 0}
54+
* corresponds to the default timeout of the Google Cloud Storage Java Library.
55+
*/
56+
static final Setting.AffixSetting<TimeValue> CONNECT_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "connect_timeout",
57+
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
58+
59+
/**
60+
* The timeout to read data from an established connection. A value of {@code -1} corresponds to an infinite timeout. A value of
61+
* {@code 0} corresponds to the default timeout of the Google Cloud Storage Java Library.
62+
*/
63+
static final Setting.AffixSetting<TimeValue> READ_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "read_timeout",
64+
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
65+
66+
/** Name used by the client when it uses the Google Cloud JSON API. **/
67+
static final Setting.AffixSetting<String> APPLICATION_NAME_SETTING = Setting.affixKeySetting(PREFIX, "application_name",
68+
key -> new Setting<>(key, "repository-gcs", s -> s, Setting.Property.NodeScope));
69+
70+
/** The credentials used by the client to connect to the Storage endpoint **/
71+
private final GoogleCredential credential;
72+
73+
/** The Storage root URL the client should talk to, or empty string to use the default. **/
74+
private final String endpoint;
75+
76+
/** The timeout to establish a connection **/
77+
private final TimeValue connectTimeout;
78+
79+
/** The timeout to read data from an established connection **/
80+
private final TimeValue readTimeout;
81+
82+
/** The Storage client application name **/
83+
private final String applicationName;
84+
85+
GoogleCloudStorageClientSettings(final GoogleCredential credential,
86+
final String endpoint,
87+
final TimeValue connectTimeout,
88+
final TimeValue readTimeout,
89+
final String applicationName) {
90+
this.credential = credential;
91+
this.endpoint = endpoint;
92+
this.connectTimeout = connectTimeout;
93+
this.readTimeout = readTimeout;
94+
this.applicationName = applicationName;
95+
}
96+
97+
public GoogleCredential getCredential() {
98+
return credential;
99+
}
100+
101+
public String getEndpoint() {
102+
return endpoint;
103+
}
104+
105+
public TimeValue getConnectTimeout() {
106+
return connectTimeout;
107+
}
108+
109+
public TimeValue getReadTimeout() {
110+
return readTimeout;
111+
}
112+
113+
public String getApplicationName() {
114+
return applicationName;
115+
}
116+
117+
public static Map<String, GoogleCloudStorageClientSettings> load(final Settings settings) {
118+
final Map<String, GoogleCloudStorageClientSettings> clients = new HashMap<>();
119+
for (String clientName: settings.getGroups(PREFIX).keySet()) {
120+
clients.put(clientName, getClientSettings(settings, clientName));
121+
}
122+
if (clients.containsKey("default") == false) {
123+
// this won't find any settings under the default client,
124+
// but it will pull all the fallback static settings
125+
clients.put("default", getClientSettings(settings, "default"));
126+
}
127+
return Collections.unmodifiableMap(clients);
128+
}
129+
130+
static GoogleCloudStorageClientSettings getClientSettings(final Settings settings, final String clientName) {
131+
return new GoogleCloudStorageClientSettings(
132+
loadCredential(settings, clientName),
133+
getConfigValue(settings, clientName, ENDPOINT_SETTING),
134+
getConfigValue(settings, clientName, CONNECT_TIMEOUT_SETTING),
135+
getConfigValue(settings, clientName, READ_TIMEOUT_SETTING),
136+
getConfigValue(settings, clientName, APPLICATION_NAME_SETTING)
137+
);
138+
}
139+
140+
/**
141+
* Loads the service account file corresponding to a given client name. If no file is defined for the client,
142+
* a {@code null} credential is returned.
143+
*
144+
* @param settings the {@link Settings}
145+
* @param clientName the client name
146+
*
147+
* @return the {@link GoogleCredential} to use for the given client, {@code null} if no service account is defined.
148+
*/
149+
static GoogleCredential loadCredential(final Settings settings, final String clientName) {
150+
try {
151+
if (CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).exists(settings) == false) {
152+
// explicitly returning null here so that the default credential
153+
// can be loaded later when creating the Storage client
154+
return null;
155+
}
156+
try (InputStream credStream = CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).get(settings)) {
157+
GoogleCredential credential = GoogleCredential.fromStream(credStream);
158+
if (credential.createScopedRequired()) {
159+
credential = credential.createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL));
160+
}
161+
return credential;
162+
}
163+
} catch (IOException e) {
164+
throw new UncheckedIOException(e);
165+
}
166+
}
167+
168+
private static <T> T getConfigValue(final Settings settings, final String clientName, final Setting.AffixSetting<T> clientSetting) {
169+
Setting<T> concreteSetting = clientSetting.getConcreteSettingForNamespace(clientName);
170+
return concreteSetting.get(settings);
171+
}
172+
}

0 commit comments

Comments
 (0)