This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new ece10285d0 added AWS Secrets Manager variable resolver. fixes #8021
(#8023)
ece10285d0 is described below
commit ece10285d0992ea91671e7b8d8f3d3fd2b922f16
Author: Bart Maertens <[email protected]>
AuthorDate: Thu Aug 20 09:18:57 2026 +0200
added AWS Secrets Manager variable resolver. fixes #8021 (#8023)
* added AWS Secrets Manager variable resolver. fixes #8021
* key and labels in AWS region combobox. #8021
---
docs/hop-user-manual/modules/ROOT/nav.adoc | 1 +
.../aws-secrets-manager-variable-resolver.adoc | 249 ++++++++
.../metadata-types/variable-resolver/index.adoc | 1 +
lib/pom.xml | 5 +
plugins/tech/aws/pom.xml | 12 +
.../resolver/aws/AwsSecretsManagerAuthType.java | 32 ++
.../aws/AwsSecretsManagerVariableResolver.java | 625 +++++++++++++++++++++
.../aws/messages/messages_de_DE.properties | 39 ++
.../aws/messages/messages_en_US.properties | 39 ++
.../aws/messages/messages_es_AR.properties | 39 ++
.../aws/messages/messages_es_ES.properties | 39 ++
.../aws/messages/messages_fr_FR.properties | 39 ++
.../aws/messages/messages_it_IT.properties | 39 ++
.../aws/messages/messages_ja_JP.properties | 39 ++
.../aws/messages/messages_ko_KR.properties | 39 ++
.../aws/messages/messages_nl_NL.properties | 39 ++
.../aws/messages/messages_no_NO.properties | 39 ++
.../aws/messages/messages_pl_PL.properties | 39 ++
.../aws/messages/messages_pt_BR.properties | 39 ++
.../aws/messages/messages_pt_PT.properties | 39 ++
.../aws/messages/messages_zh_CN.properties | 39 ++
.../aws/AwsSecretsManagerVariableResolverTest.java | 477 ++++++++++++++++
.../aws/AwsSecretsManagerWidgetVisibilityTest.java | 177 ++++++
.../variables/resolver/VariableResolverEditor.java | 64 ++-
24 files changed, 2173 insertions(+), 16 deletions(-)
diff --git a/docs/hop-user-manual/modules/ROOT/nav.adoc
b/docs/hop-user-manual/modules/ROOT/nav.adoc
index f4bbb31125..34a5e63868 100644
--- a/docs/hop-user-manual/modules/ROOT/nav.adoc
+++ b/docs/hop-user-manual/modules/ROOT/nav.adoc
@@ -502,6 +502,7 @@ under the License.
** xref:metadata-types/jms-connection.adoc[JMS connection]
** xref:metadata-types/spark-catalog.adoc[Spark Catalog]
** xref:metadata-types/variable-resolver/index.adoc[Variable Resolver]
+***
xref:metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc[AWS
Secrets Manager variable resolver]
***
xref:metadata-types/variable-resolver/azure-key-vault-variable-resolver.adoc[Azure
Key Vault variable resolver]
***
xref:metadata-types/variable-resolver/google-secret-manager-variable-resolver.adoc[Google
Secret Manager variable resolver]
***
xref:metadata-types/variable-resolver/hashicorp-vault-variable-resolver.adoc[Hashicorp
Vault variable resolver]
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc
new file mode 100644
index 0000000000..b239fbd250
--- /dev/null
+++
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc
@@ -0,0 +1,249 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF 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.
+////
+[[AwsSecretsManagerVariableResolver]]
+:imagesdir: ../assets/images
+:openvar: #{
+:closevar: }
+:description: The AWS Secrets Manager Variable Resolver looks up secrets in
AWS Secrets Manager and makes them available as variables in your pipelines and
workflows.
+
+= AWS Secrets Manager Variable Resolver
+
+== Description
+
+The AWS Secrets Manager Variable Resolver looks up secrets in AWS Secrets
Manager and makes them available as variables in your Apache Hop pipelines and
workflows.
+Database passwords, API keys and other credentials stay in AWS and out of your
project files.
+
+Unlike Azure Key Vault or Hashicorp Vault there is nothing to install or
provision: Secrets Manager is available in every region of every AWS account.
+You only need a secret and an identity that is allowed to read it.
+
+== Options
+
+The editor only shows the options that apply to the authentication type you
picked, so switching type changes which fields are on screen.
+Every option accepts variables, which is what lets one resolver serve several
environments.
+
+=== AWS region
+
+The region that holds your secrets.
+The dropdown lists each region with the name AWS gives it, for example
`eu-west-1 - Europe (Ireland)`, but only the region code is stored and used, so
the metadata file simply reads `eu-west-1`.
+You can also type a code yourself, or set a variable such as `${AWS_REGION}`.
+
+Secrets are regional and are not replicated between regions unless you
explicitly configure replication, so this has to match the region the secret
was created in.
+Leave the option empty to let Hop pick up the region the way the AWS CLI does,
from the `AWS_REGION` environment variable or the active profile.
+
+=== Authentication type
+
+How Hop proves to AWS who it is. The three types have no options in common, so
the editor shows only the fields belonging to the one you pick.
+
+`AUTOMATIC`:: Let the AWS SDK find credentials by itself. This is the default,
and the one to prefer.
++
+The SDK works through its standard provider chain and takes the first
credentials it finds: Java system properties, then environment variables
(`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`), then a web identity token
(how EKS IRSA works), then the shared credentials file, then container
credentials (the ECS task role), and finally the EC2 instance profile.
++
+On EC2, ECS or EKS this means the surrounding infrastructure supplies a role
automatically and no credential is stored in the Hop project at all. It also
picks up whatever you already use locally, so if `aws sts get-caller-identity`
works in the shell you start Hop from, this option works too.
++
+Needs no further options.
+
+`ACCESS_KEYS`:: Use an access key and secret key you enter yourself, with an
optional session token.
++
+Use this when Hop runs outside AWS and there is no profile to read, or when
you deliberately want a separate identity for secret lookups. Uses the *Access
key*, *Secret key* and *Session token* options.
+
+`CREDENTIALS_FILE`:: Read credentials from an AWS credentials file, optionally
choosing a profile from it.
++
+Use this when you already keep several AWS identities side by side and want
this resolver to use a specific one rather than whichever the chain happens to
find first. Uses the *Credentials file* and *Profile name* options.
+
+[TIP]
+====
+Prefer `AUTOMATIC` wherever the runtime environment can provide credentials.
+Long lived access keys stored in a project are exactly the kind of thing
Secrets Manager exists to get rid of, and `AUTOMATIC` keeps the Hop project
identical between a laptop, a server and a container.
+====
+
+=== Access key / Secret key / Session token
+
+The AWS access key ID and secret access key, used only with authentication
type `ACCESS_KEYS`.
+
+The session token is only needed for temporary credentials, the kind handed
out by `sts assume-role` or by AWS SSO. Leave it empty for a normal long lived
key pair. Temporary credentials expire, so a resolver configured with them
stops working when they do.
+
+All three values are stored encrypted in the metadata file.
+
+=== Credentials file / Profile name
+
+Used with authentication type `CREDENTIALS_FILE`.
+
+The credentials file is a file in the format the AWS CLI writes, holding one
or more named profiles.
+Leave it empty to use the standard location, `~/.aws/credentials`
(`%USERPROFILE%\.aws\credentials` on Windows).
+
+The profile name picks which entry in that file to use, and defaults to
`default`.
+
+=== Endpoint override
+
+An alternative Secrets Manager endpoint.
+Use this for a VPC endpoint, or to point Hop at a local AWS emulator during
development.
+Leave it empty to use the regular regional AWS endpoint.
+
+=== Secret name prefix
+
+A prefix placed in front of every secret name before the lookup.
+With a prefix of `production/`, the expression
`{openvar}aws-secrets:database{closevar}` reads the secret
`production/database`.
+
+This keeps expressions short and lets the same pipeline read from
`development/` or `production/` by pointing the resolver at a different prefix
per environment.
+The prefix itself may contain variables, for example `${ENVIRONMENT}/`.
+
+=== Version stage
+
+Which version of the secret to read. `AWSCURRENT` by default, which is what
you want unless you have a specific reason not to.
+
+Secrets Manager does not overwrite a secret when its value changes: it stores
a new *version* and keeps the old ones. A *staging label* is a moving pointer
to one of those versions, so that something reading "the current value" does
not need to know any version IDs. AWS maintains three labels for you:
+
+`AWSCURRENT`:: The value in use right now. This is what an application should
read, and what this resolver reads unless you say otherwise.
+`AWSPREVIOUS`:: The value that was current before the most recent change.
+`AWSPENDING`:: A new value created by a rotation that has not been promoted
yet. It exists only while a rotation is in progress.
+
+When a secret rotates, AWS creates the new version with `AWSPENDING`, tests
it, and then moves `AWSCURRENT` onto it while the version it replaced picks up
`AWSPREVIOUS`. Because the labels move rather than the values, an expression
reading `AWSCURRENT` keeps working across a rotation without any change in Hop.
+
+Reasons to change this option are rare and usually temporary:
+
+* A rotation broke something and you need the previous credential back while
you sort it out: `AWSPREVIOUS`.
+* You are testing a rotation before it is promoted: `AWSPENDING`.
+
+[WARNING]
+====
+Do not leave the option on `AWSPREVIOUS`. It is a pointer, not a snapshot: the
next rotation moves it to what is current today, so a resolver left pointing at
it silently starts serving a different secret.
+====
+
+=== Cache TTL (seconds)
+
+How long a retrieved secret is reused before Hop asks AWS for it again.
+`0`, the default, disables caching and looks the secret up every time.
+
+Secrets Manager bills per API call and Hop resolves variables often, so a
small value such as `60` can noticeably reduce both cost and latency.
+The trade-off is that a rotated secret takes up to this long to be picked up,
so keep the value well below your rotation window.
+
+== Expression syntax
+
+Secrets are addressed with the standard variable resolver syntax:
+
+`{openvar}name:secret-name:value-key{closevar}`
+
+* `name`: the name of the variable resolver metadata element
+* `secret-name`: the name of the secret in AWS Secrets Manager
+* `value-key`: when the secret holds JSON, the key to pick out of it (OPTIONAL)
+
+A secret can hold a plain value or JSON: `SecretString` is free-form text and
AWS makes no distinction.
+The console nudges you towards JSON because its create-secret screen opens on
a *Key/value* tab, but the *Plaintext* tab next to it stores a bare value just
as well.
+
+For a plain secret, two parts are enough:
+
+* `{openvar}aws-secrets:my-api-key{closevar}` : the value as stored
+
+For a JSON secret, the third part picks one value out of it.
+Given a secret called `some-db` holding:
+
+[source,json]
+----
+{"dbname":"test","host":"localhost","password":"some-password","port":3306,"username":"john"}
+----
+
+a resolver named `aws-secrets` gives you:
+
+* `{openvar}aws-secrets:some-db:host{closevar}` : localhost
+* `{openvar}aws-secrets:some-db:username{closevar}` : john
+* `{openvar}aws-secrets:some-db{closevar}` : the entire JSON document
+
+=== Keys in database credential secrets
+
+Secrets created with one of the console's database types (RDS, DocumentDB,
Redshift) always use the same fixed key names, because the AWS rotation
functions expect them:
+
+`username`, `password`, `engine`, `host`, `port`, `dbname`,
`dbInstanceIdentifier`
+
+Note `host`, not `hostname`.
+Asking for a key that is not in the secret yields an empty value rather than
an error, so if a lookup comes back empty, resolve the secret without the third
part first and check the key names in the JSON you get back.
+
+[IMPORTANT]
+====
+Use the *name* of the secret, not its ARN.
+Resolver expressions are split on the `:` character and an ARN contains colons
of its own, so `{openvar}aws-secrets:arn:aws:secretsmanager:...{closevar}`
cannot work.
+When your secret names share a common path, put that path in the *Secret name
prefix* option instead of typing it in every expression.
+====
+
+== Setting up AWS
+
+=== Create a secret
+
+[source,bash]
+----
+# A plain value, resolved with a two part expression
+aws secretsmanager create-secret \
+ --name production/api-key \
+ --secret-string "my-api-key"
+
+# A JSON secret, so a single value can be picked out of it
+aws secretsmanager create-secret \
+ --name production/some-db \
+ --secret-string
'{"hostname":"localhost","port":"3306","username":"john","password":"some-password"}'
+----
+
+=== Grant read access
+
+The identity Hop runs as needs `secretsmanager:GetSecretValue` on the secrets
it reads:
+
+[source,json]
+----
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": "secretsmanager:GetSecretValue",
+ "Resource":
"arn:aws:secretsmanager:eu-west-1:123456789012:secret:production/*"
+ }
+ ]
+}
+----
+
+Secrets encrypted with a customer managed KMS key also need `kms:Decrypt` on
that key.
+Secrets using the AWS managed `aws/secretsmanager` key need no extra
permission.
+
+== Usage
+
+=== In a database connection
+
+1. Create or edit a relational database connection
+2. Set the *Password* field to
`{openvar}aws-secrets:production/some-db:password{closevar}`
+3. The password is retrieved from Secrets Manager when the connection is used
+
+=== Per environment
+
+Set a variable in each of your
xref:projects/projects-environments.adoc[environments] and let it point at the
resolver expression in production only:
+
+* development: `PASSWORD` = `dev-password`
+* production: `PASSWORD` =
`{openvar}aws-secrets:production/some-db:password{closevar}`
+
+Pipelines and workflows simply use `${PASSWORD}` and need no changes between
environments.
+
+== Troubleshooting
+
+Errors are not shown in the user interface while you type an expression, since
that would make expressions impossible to enter.
+Check the log in the xref:hop-gui/index.adoc[Hop GUI] execution pane to see
what the resolver reports.
+
+Common causes:
+
+* *An empty value came back*: the lookup worked but the JSON key does not
exist. Resolve the secret without the third part to see the real key names.
+* *Secret not found*: the secret does not exist in the configured region, or
the name prefix is missing. Remember that secrets are regional.
+* *An ARN was used instead of a name*: see the note in the expression syntax
section above.
+* *Access denied*: the identity is missing `secretsmanager:GetSecretValue`, or
`kms:Decrypt` for a customer managed key.
+* *No region*: no region is configured on the resolver and none could be found
in the environment.
+
+NOTE: A secret holding a binary value is returned base64 encoded, since a
variable value has to be text.
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
index 8a529cd2fd..9de1b4a8fd 100644
---
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
+++
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
@@ -56,6 +56,7 @@ NOTE: For now, the variable resolver is not doing any more
nested resolving to a
Here are the available variable resolver plugins:
+*
xref:metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc[AWS
Secrets Manager variable resolver]
*
xref:metadata-types/variable-resolver/azure-key-vault-variable-resolver.adoc[Azure
Key Vault variable resolver]
*
xref:metadata-types/variable-resolver/google-secret-manager-variable-resolver.adoc[Google
Secret Manager Variable Resolver]
*
xref:metadata-types/variable-resolver/hashicorp-vault-variable-resolver.adoc[Hashicorp
Vault variable resolver]
diff --git a/lib/pom.xml b/lib/pom.xml
index 91cee4ff11..d0014185c1 100644
--- a/lib/pom.xml
+++ b/lib/pom.xml
@@ -254,6 +254,11 @@
<artifactId>s3</artifactId>
<version>${aws-java-sdk.version}</version>
</dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>secretsmanager</artifactId>
+ <version>${aws-java-sdk.version}</version>
+ </dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
diff --git a/plugins/tech/aws/pom.xml b/plugins/tech/aws/pom.xml
index f471f7eb25..b5465a68d5 100644
--- a/plugins/tech/aws/pom.xml
+++ b/plugins/tech/aws/pom.xml
@@ -76,6 +76,18 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>secretsmanager</artifactId>
+ <version>${aws-java-sdk.version}</version>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>io.netty</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
diff --git
a/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerAuthType.java
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerAuthType.java
new file mode 100644
index 0000000000..c7f91ec10a
--- /dev/null
+++
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerAuthType.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.hop.core.variables.resolver.aws;
+
+/** Authentication method used by the AWS Secrets Manager variable resolver. */
+public enum AwsSecretsManagerAuthType {
+ /**
+ * Let the AWS SDK find credentials by itself, through the default provider
chain: system
+ * properties, environment variables, a web identity token, the credentials
file, container
+ * credentials and finally the EC2 instance profile.
+ */
+ AUTOMATIC,
+ /** An explicit access key and secret key, optionally with a session token.
*/
+ ACCESS_KEYS,
+ /** An AWS credentials file, optionally with a profile name. */
+ CREDENTIALS_FILE,
+}
diff --git
a/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolver.java
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolver.java
new file mode 100644
index 0000000000..8eef7a96b3
--- /dev/null
+++
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolver.java
@@ -0,0 +1,625 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.hop.core.variables.resolver.aws;
+
+import java.net.URI;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.encryption.Encr;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.gui.plugin.GuiElementType;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.GuiWidgetElement;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.resolver.IVariableResolver;
+import org.apache.hop.core.variables.resolver.VariableResolver;
+import org.apache.hop.core.variables.resolver.VariableResolverPlugin;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.ui.core.gui.GuiCompositeWidgets;
+import org.apache.hop.ui.core.gui.IGuiPluginCompositeWidgetsListener;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Control;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.profiles.ProfileFile;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.regions.RegionMetadata;
+import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import
software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
+import
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
+import
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
+import
software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
+
+/**
+ * Looks up secrets in AWS Secrets Manager.
+ *
+ * <p>A secret is addressed by its name, not by its ARN: the variable resolver
expression syntax
+ * <code>#{name:secret:key}</code> splits on the colon, and an ARN contains
colons of its own. Use
+ * the secret name prefix option to reach secrets that need a longer path.
+ *
+ * <p>Secrets Manager secrets frequently hold JSON. Picking a single value out
of that JSON is
+ * handled generically by the variable resolver machinery, so this resolver
simply returns the
+ * secret payload as it is stored.
+ */
+@Getter
+@Setter
+@GuiPlugin
+@VariableResolverPlugin(
+ id = "AwsSecretsManager",
+ name = "AWS Secrets Manager Variable Resolver",
+ description = "Look up values of secrets in AWS Secrets Manager",
+ documentationUrl =
+
"/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.html")
+public class AwsSecretsManagerVariableResolver
+ implements IVariableResolver, IGuiPluginCompositeWidgetsListener {
+
+ private static final String DEFAULT_VERSION_STAGE = "AWSCURRENT";
+
+ /** Separates the region code from its description in the region combo box.
*/
+ private static final String REGION_DESCRIPTION_SEPARATOR = " - ";
+
+ /** The region codes the SDK knows, so that anything else can be left alone.
*/
+ private static final Set<String> KNOWN_REGION_IDS =
+
Region.regions().stream().map(Region::id).collect(Collectors.toUnmodifiableSet());
+
+ static final String ID_REGION = "region";
+ static final String ID_AUTHENTICATION_TYPE = "authenticationType";
+ static final String ID_ACCESS_KEY = "accessKey";
+ static final String ID_SECRET_KEY = "secretKey";
+ static final String ID_SESSION_TOKEN = "sessionToken";
+ static final String ID_CREDENTIALS_FILE = "credentialsFile";
+ static final String ID_PROFILE_NAME = "profileName";
+ static final String ID_ENDPOINT_OVERRIDE = "endpointOverride";
+ static final String ID_SECRET_NAME_PREFIX = "secretNamePrefix";
+ static final String ID_VERSION_STAGE = "versionStage";
+ static final String ID_CACHE_TTL_SECONDS = "cacheTtlSeconds";
+
+ private final LogChannel log = new
LogChannel("AwsSecretsManagerVariableResolver");
+
+ /** Guards {@link #client}, {@link #clientSignature} and {@link #cache}. */
+ private final Object clientLock = new Object();
+
+ private transient SecretsManagerClient client;
+
+ /** The resolved configuration the cached client was built for. */
+ private transient String clientSignature;
+
+ private final transient Map<String, CachedSecret> cache = new
ConcurrentHashMap<>();
+
+ @GuiWidgetElement(
+ id = ID_REGION,
+ order = "010",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.COMBO,
+ comboValuesMethod = "getRegions",
+ getterMethod = "getRegionLabel",
+ label = "i18n::AwsSecretsManagerVariableResolver.Region.Label",
+ toolTip = "i18n::AwsSecretsManagerVariableResolver.Region.Tooltip")
+ @HopMetadataProperty
+ private String region;
+
+ @GuiWidgetElement(
+ id = ID_AUTHENTICATION_TYPE,
+ order = "020",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.COMBO,
+ comboValuesMethod = "getAuthenticationTypes",
+ label =
"i18n::AwsSecretsManagerVariableResolver.AuthenticationType.Label",
+ toolTip =
"i18n::AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip")
+ @HopMetadataProperty
+ private String authenticationType;
+
+ @GuiWidgetElement(
+ id = ID_ACCESS_KEY,
+ order = "030",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ password = true,
+ label = "i18n::AwsSecretsManagerVariableResolver.AccessKey.Label",
+ toolTip = "i18n::AwsSecretsManagerVariableResolver.AccessKey.Tooltip")
+ @HopMetadataProperty(password = true)
+ private String accessKey;
+
+ @GuiWidgetElement(
+ id = ID_SECRET_KEY,
+ order = "040",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ password = true,
+ label = "i18n::AwsSecretsManagerVariableResolver.SecretKey.Label",
+ toolTip = "i18n::AwsSecretsManagerVariableResolver.SecretKey.Tooltip")
+ @HopMetadataProperty(password = true)
+ private String secretKey;
+
+ @GuiWidgetElement(
+ id = ID_SESSION_TOKEN,
+ order = "050",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ password = true,
+ label = "i18n::AwsSecretsManagerVariableResolver.SessionToken.Label",
+ toolTip = "i18n::AwsSecretsManagerVariableResolver.SessionToken.Tooltip")
+ @HopMetadataProperty(password = true)
+ private String sessionToken;
+
+ @GuiWidgetElement(
+ id = ID_CREDENTIALS_FILE,
+ order = "060",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.FILENAME,
+ label = "i18n::AwsSecretsManagerVariableResolver.CredentialsFile.Label",
+ toolTip =
"i18n::AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip")
+ @HopMetadataProperty
+ private String credentialsFile;
+
+ @GuiWidgetElement(
+ id = ID_PROFILE_NAME,
+ order = "070",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ label = "i18n::AwsSecretsManagerVariableResolver.ProfileName.Label",
+ toolTip = "i18n::AwsSecretsManagerVariableResolver.ProfileName.Tooltip")
+ @HopMetadataProperty
+ private String profileName;
+
+ @GuiWidgetElement(
+ id = ID_ENDPOINT_OVERRIDE,
+ order = "080",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ label = "i18n::AwsSecretsManagerVariableResolver.EndpointOverride.Label",
+ toolTip =
"i18n::AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip")
+ @HopMetadataProperty
+ private String endpointOverride;
+
+ @GuiWidgetElement(
+ id = ID_SECRET_NAME_PREFIX,
+ order = "090",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ label = "i18n::AwsSecretsManagerVariableResolver.SecretNamePrefix.Label",
+ toolTip =
"i18n::AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip")
+ @HopMetadataProperty
+ private String secretNamePrefix;
+
+ @GuiWidgetElement(
+ id = ID_VERSION_STAGE,
+ order = "100",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ label = "i18n::AwsSecretsManagerVariableResolver.VersionStage.Label",
+ toolTip = "i18n::AwsSecretsManagerVariableResolver.VersionStage.Tooltip")
+ @HopMetadataProperty
+ private String versionStage;
+
+ @GuiWidgetElement(
+ id = ID_CACHE_TTL_SECONDS,
+ order = "110",
+ parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+ type = GuiElementType.TEXT,
+ label = "i18n::AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label",
+ toolTip =
"i18n::AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip")
+ @HopMetadataProperty
+ private String cacheTtlSeconds;
+
+ /**
+ * The region combo shows "eu-west-1 - Europe (Ireland)" but only the code
is worth keeping, so
+ * the description is stripped before it reaches the field and the metadata
file. Hand written on
+ * purpose: this is the setter the widget layer calls, and Lombok leaves it
alone.
+ */
+ public void setRegion(String region) {
+ this.region = regionCode(region);
+ }
+
+ /**
+ * The value to show in the region combo box: the stored code together with
its description, so
+ * that it matches one of the entries {@link #getRegions} offers. Anything
that is not a region
+ * code -- a variable reference, most likely -- is shown as it is.
+ */
+ public String getRegionLabel() {
+ String code = regionCode(region);
+ if (StringUtils.isEmpty(code) || !KNOWN_REGION_IDS.contains(code)) {
+ return region;
+ }
+ return code + REGION_DESCRIPTION_SEPARATOR + describeRegion(code);
+ }
+
+ public AwsSecretsManagerVariableResolver() {
+ authenticationType = AwsSecretsManagerAuthType.AUTOMATIC.name();
+ versionStage = DEFAULT_VERSION_STAGE;
+ cacheTtlSeconds = "0";
+ }
+
+ @Override
+ public void init() {
+ // The client is built lazily on the first resolve() call: only then do we
have the variables
+ // needed to resolve the configuration fields.
+ }
+
+ @Override
+ public String resolve(String secretName, IVariables variables) throws
HopException {
+ if (StringUtils.isEmpty(secretName)) {
+ return null;
+ }
+
+ // An ARN never reaches us intact: the expression is split on ':' before
this method is called,
+ // so #{name:arn:aws:secretsmanager:...} arrives here as the bare string
"arn". Refusing it is
+ // friendlier than looking up a secret that was never meant.
+ if ("arn".equals(secretName) || secretName.startsWith("arn:")) {
+ log.logError(
+ "Secret '"
+ + secretName
+ + "' looks like an ARN. Variable resolver expressions are split
on ':', so an ARN "
+ + "cannot be used here. Use the name of the secret and, when the
names share a "
+ + "common path, the secret name prefix option of this
resolver.");
+ return null;
+ }
+
+ String prefix = variables.resolve(secretNamePrefix);
+ String secretId = StringUtils.isEmpty(prefix) ? secretName : prefix +
secretName;
+
+ long ttl = Const.toLong(variables.resolve(cacheTtlSeconds), 0L);
+ if (ttl > 0) {
+ CachedSecret cached = cache.get(secretId);
+ if (cached != null && !cached.isExpired()) {
+ return cached.value;
+ }
+ }
+
+ try {
+ SecretsManagerClient secretsManagerClient = getClient(variables);
+
+ String stage = variables.resolve(versionStage);
+ GetSecretValueRequest request =
+ GetSecretValueRequest.builder()
+ .secretId(secretId)
+ .versionStage(StringUtils.isEmpty(stage) ? DEFAULT_VERSION_STAGE
: stage)
+ .build();
+
+ GetSecretValueResponse response =
secretsManagerClient.getSecretValue(request);
+ String value = extractValue(secretId, response);
+
+ if (value != null && ttl > 0) {
+ cache.put(secretId, new CachedSecret(value, System.currentTimeMillis()
+ ttl * 1000L));
+ }
+ return value;
+ } catch (ResourceNotFoundException e) {
+ log.logError("Secret '" + secretId + "' was not found in AWS Secrets
Manager", e);
+ return null;
+ } catch (Exception e) {
+ log.logError("Error looking up secret '" + secretId + "' in AWS Secrets
Manager", e);
+ return null;
+ }
+ }
+
+ /**
+ * A Secrets Manager secret holds either a string or a binary payload.
Binary secrets are returned
+ * base64 encoded since a variable value has to be a String.
+ */
+ private String extractValue(String secretId, GetSecretValueResponse
response) {
+ if (response.secretString() != null) {
+ return response.secretString();
+ }
+ if (response.secretBinary() != null) {
+ log.logDetailed(
+ "Secret '" + secretId + "' holds a binary value, returning it base64
encoded.");
+ return
Base64.getEncoder().encodeToString(response.secretBinary().asByteArray());
+ }
+ log.logError("Secret '" + secretId + "' holds neither a string nor a
binary value");
+ return null;
+ }
+
+ /**
+ * Secrets Manager bills per API call and variable resolution happens often,
so the client is
+ * built once and kept. It is rebuilt when the resolved configuration
changes, which can happen
+ * when the configuration fields themselves contain variables.
+ */
+ protected SecretsManagerClient getClient(IVariables variables) throws
HopException {
+ String actualRegion = variables.resolve(region);
+ String actualAuthType = variables.resolve(authenticationType);
+ String actualAccessKey = variables.resolve(accessKey);
+ String actualSecretKey = variables.resolve(secretKey);
+ String actualSessionToken = variables.resolve(sessionToken);
+ String actualCredentialsFile = variables.resolve(credentialsFile);
+ String actualProfileName = variables.resolve(profileName);
+ String actualEndpoint = variables.resolve(endpointOverride);
+
+ String signature =
+ String.join(
+ "\t",
+ Const.NVL(actualRegion, ""),
+ Const.NVL(actualAuthType, ""),
+ Const.NVL(actualAccessKey, ""),
+ Const.NVL(actualSecretKey, ""),
+ Const.NVL(actualSessionToken, ""),
+ Const.NVL(actualCredentialsFile, ""),
+ Const.NVL(actualProfileName, ""),
+ Const.NVL(actualEndpoint, ""));
+
+ synchronized (clientLock) {
+ if (client != null && signature.equals(clientSignature)) {
+ return client;
+ }
+
+ if (client != null) {
+ // The configuration changed, so anything we cached for it is stale
too.
+ client.close();
+ cache.clear();
+ }
+
+ SecretsManagerClientBuilder builder = SecretsManagerClient.builder();
+ builder.region(resolveRegion(actualRegion));
+ builder.credentialsProvider(
+ buildCredentialsProvider(
+ actualAuthType,
+ actualAccessKey,
+ actualSecretKey,
+ actualSessionToken,
+ actualCredentialsFile,
+ actualProfileName));
+ if (StringUtils.isNotEmpty(actualEndpoint)) {
+ builder.endpointOverride(URI.create(actualEndpoint));
+ }
+
+ client = builder.build();
+ clientSignature = signature;
+ return client;
+ }
+ }
+
+ /**
+ * The AWS SDK needs a region to build a client. When none is configured we
let the SDK look one
+ * up the way the AWS CLI does, through AWS_REGION or the active profile.
+ */
+ private Region resolveRegion(String actualRegion) throws HopException {
+ String code = regionCode(actualRegion);
+ if (StringUtils.isNotEmpty(code)) {
+ return Region.of(code);
+ }
+ try {
+ return new DefaultAwsRegionProviderChain().getRegion();
+ } catch (Exception e) {
+ throw new HopException(
+ "No AWS region is configured in this variable resolver and none
could be found in the "
+ + "environment. Set the region option or the AWS_REGION
environment variable.",
+ e);
+ }
+ }
+
+ /**
+ * The region combo box shows the code together with the description AWS
gives it, so a stored
+ * value can read "eu-west-1 - Europe (Ireland)". A region code never
contains whitespace, which
+ * makes the first word the code whichever of the two forms was saved.
+ */
+ static String regionCode(String region) {
+ if (StringUtils.isEmpty(region)) {
+ return region;
+ }
+ int index = region.indexOf(REGION_DESCRIPTION_SEPARATOR);
+ return index < 0 ? region.trim() : region.substring(0, index).trim();
+ }
+
+ AwsCredentialsProvider buildCredentialsProvider(
+ String actualAuthType,
+ String actualAccessKey,
+ String actualSecretKey,
+ String actualSessionToken,
+ String actualCredentialsFile,
+ String actualProfileName)
+ throws HopException {
+
+ AwsSecretsManagerAuthType authType = parseAuthType(actualAuthType);
+
+ switch (authType) {
+ case ACCESS_KEYS:
+ if (StringUtils.isEmpty(actualAccessKey) ||
StringUtils.isEmpty(actualSecretKey)) {
+ throw new HopException(
+ "An access key and a secret key are required when the
authentication type is "
+ + AwsSecretsManagerAuthType.ACCESS_KEYS.name());
+ }
+ String decryptedSecretKey =
Encr.decryptPasswordOptionallyEncrypted(actualSecretKey);
+ if (StringUtils.isEmpty(actualSessionToken)) {
+ return StaticCredentialsProvider.create(
+ AwsBasicCredentials.create(actualAccessKey, decryptedSecretKey));
+ }
+ return StaticCredentialsProvider.create(
+ AwsSessionCredentials.create(
+ actualAccessKey,
+ decryptedSecretKey,
+ Encr.decryptPasswordOptionallyEncrypted(actualSessionToken)));
+
+ case CREDENTIALS_FILE:
+ ProfileCredentialsProvider.Builder profileBuilder =
ProfileCredentialsProvider.builder();
+ if (StringUtils.isNotEmpty(actualCredentialsFile)) {
+ profileBuilder.profileFile(
+ ProfileFile.builder()
+ .content(Paths.get(actualCredentialsFile))
+ .type(ProfileFile.Type.CREDENTIALS)
+ .build());
+ }
+ profileBuilder.profileName(
+ StringUtils.isEmpty(actualProfileName) ? "default" :
actualProfileName);
+ return profileBuilder.build();
+
+ default:
+ return DefaultCredentialsProvider.create();
+ }
+ }
+
+ AwsSecretsManagerAuthType parseAuthType(String actualAuthType) throws
HopException {
+ if (StringUtils.isEmpty(actualAuthType)) {
+ return AwsSecretsManagerAuthType.AUTOMATIC;
+ }
+ try {
+ return
AwsSecretsManagerAuthType.valueOf(actualAuthType.trim().toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new HopException(
+ "Unknown AWS authentication type '"
+ + actualAuthType
+ + "'. Valid values are: "
+ + String.join(", ", authTypeNames()),
+ e);
+ }
+ }
+
+ private static List<String> authTypeNames() {
+ List<String> names = new ArrayList<>();
+ for (AwsSecretsManagerAuthType type : AwsSecretsManagerAuthType.values()) {
+ names.add(type.name());
+ }
+ return names;
+ }
+
+ /**
+ * Populates the region combo box from the regions the AWS SDK knows about,
each with the
+ * description AWS gives it, so that "eu-west-1" reads as "eu-west-1 -
Europe (Ireland)".
+ */
+ public List<String> getRegions(ILogChannel logChannel, IHopMetadataProvider
metadataProvider) {
+ List<String> regions = new ArrayList<>();
+ // An empty entry leaves the region to the environment.
+ regions.add("");
+ Region.regions().stream()
+ .map(Region::id)
+ .sorted()
+ .forEach(id -> regions.add(id + REGION_DESCRIPTION_SEPARATOR +
describeRegion(id)));
+ return regions;
+ }
+
+ private String describeRegion(String id) {
+ try {
+ return RegionMetadata.of(Region.of(id)).description();
+ } catch (Exception e) {
+ // A region the SDK knows by id but carries no description for.
+ return id;
+ }
+ }
+
+ /** Populates the authentication type combo box. */
+ public List<String> getAuthenticationTypes(
+ ILogChannel logChannel, IHopMetadataProvider metadataProvider) {
+ return authTypeNames();
+ }
+
+ @Override
+ public void widgetsCreated(GuiCompositeWidgets compositeWidgets) {
+ hideFieldsThatDoNotApply(compositeWidgets);
+ }
+
+ @Override
+ public void widgetsPopulated(GuiCompositeWidgets compositeWidgets) {
+ hideFieldsThatDoNotApply(compositeWidgets);
+ }
+
+ @Override
+ public void widgetModified(
+ GuiCompositeWidgets compositeWidgets, Control changedWidget, String
widgetId) {
+ if (ID_AUTHENTICATION_TYPE.equals(widgetId)) {
+ hideFieldsThatDoNotApply(compositeWidgets);
+ }
+ }
+
+ @Override
+ public void persistContents(GuiCompositeWidgets compositeWidgets) {
+ // Not needed, the editor reads the widgets back itself.
+ }
+
+ /**
+ * Keeps the editor down to the credentials that the chosen authentication
type actually uses. The
+ * three types have nothing in common, so leaving all of them on screen
means most of the fields
+ * can only ever stay empty.
+ */
+ private void hideFieldsThatDoNotApply(GuiCompositeWidgets compositeWidgets) {
+ AwsSecretsManagerAuthType authType = readAuthType(compositeWidgets);
+
+ Set<String> hidden = new HashSet<>();
+ if (authType != AwsSecretsManagerAuthType.ACCESS_KEYS) {
+ hidden.add(ID_ACCESS_KEY);
+ hidden.add(ID_SECRET_KEY);
+ hidden.add(ID_SESSION_TOKEN);
+ }
+ if (authType != AwsSecretsManagerAuthType.CREDENTIALS_FILE) {
+ hidden.add(ID_CREDENTIALS_FILE);
+ hidden.add(ID_PROFILE_NAME);
+ }
+ compositeWidgets.setWidgetsHidden(this, hidden);
+ }
+
+ /**
+ * Reads the authentication type from the combo box rather than from this
object, since the widget
+ * is what the user just changed and the metadata is only written back later.
+ */
+ private AwsSecretsManagerAuthType readAuthType(GuiCompositeWidgets
compositeWidgets) {
+ Control control =
compositeWidgets.getWidgetsMap().get(ID_AUTHENTICATION_TYPE);
+ if (control instanceof Combo combo) {
+ try {
+ return AwsSecretsManagerAuthType.valueOf(combo.getText());
+ } catch (IllegalArgumentException e) {
+ // Nothing picked yet, so fall through to what the metadata holds.
+ }
+ }
+ try {
+ return parseAuthType(authenticationType);
+ } catch (HopException e) {
+ return AwsSecretsManagerAuthType.AUTOMATIC;
+ }
+ }
+
+ @Override
+ public String getPluginId() {
+ return "AwsSecretsManager";
+ }
+
+ @Override
+ public String getPluginName() {
+ return "AWS Secrets Manager Variable Resolver";
+ }
+
+ /** A secret value with the point in time after which it should be looked up
again. */
+ private static class CachedSecret {
+ private final String value;
+ private final long expiryTime;
+
+ private CachedSecret(String value, long expiryTime) {
+ this.value = value;
+ this.expiryTime = expiryTime;
+ }
+
+ private boolean isExpired() {
+ return System.currentTimeMillis() >= expiryTime;
+ }
+ }
+}
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_de_DE.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_de_DE.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_de_DE.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_en_US.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_en_US.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_en_US.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_AR.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_AR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_AR.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_ES.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_ES.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_ES.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_fr_FR.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_fr_FR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_fr_FR.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_it_IT.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_it_IT.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_it_IT.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ja_JP.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ja_JP.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ja_JP.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ko_KR.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ko_KR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ko_KR.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_nl_NL.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_nl_NL.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_nl_NL.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_no_NO.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_no_NO.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_no_NO.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pl_PL.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pl_PL.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pl_PL.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_BR.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_BR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_BR.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_PT.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_PT.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_PT.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_zh_CN.properties
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_zh_CN.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_zh_CN.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets
the AWS SDK find credentials itself (environment, profile, container or
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key.
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative
Secrets Manager endpoint, for example a VPC endpoint or a local test service.
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in
front of every secret name, for example "production/". Lets you keep
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved
secret is reused before it is looked up again. 0 disables caching. Secrets
Manager bills per API call, so a small value reduces cost, but rotated secrets
take up to this long to be picked up.
diff --git
a/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolverTest.java
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolverTest.java
new file mode 100644
index 0000000000..5080ed51b7
--- /dev/null
+++
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolverTest.java
@@ -0,0 +1,477 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.hop.core.variables.resolver.aws;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.encryption.Encr;
+import org.apache.hop.core.encryption.TwoWayPasswordEncoderPluginType;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.util.EnvUtil;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
+import
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
+import
software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
+
+/**
+ * Unit tests for the AWS Secrets Manager variable resolver. The Secrets
Manager client is mocked so
+ * that no AWS account is needed.
+ */
+@ExtendWith(MockitoExtension.class)
+class AwsSecretsManagerVariableResolverTest {
+
+ @Mock private SecretsManagerClient mockClient;
+
+ private AwsSecretsManagerVariableResolver resolver;
+ private IVariables variables;
+
+ @BeforeAll
+ static void initHopEnvironment() throws HopException {
+ HopLogStore.init();
+ // The resolver decrypts secret keys through Encr, which needs its encoder
plugin registered.
+
PluginRegistry.addPluginType(TwoWayPasswordEncoderPluginType.getInstance());
+ PluginRegistry.init();
+
Encr.init(Const.NVL(EnvUtil.getSystemProperty(Const.HOP_PASSWORD_ENCODER_PLUGIN),
"Hop"));
+ }
+
+ @BeforeEach
+ void setUp() {
+ // Hand the resolver the mocked client instead of letting it build a real
one.
+ resolver =
+ new AwsSecretsManagerVariableResolver() {
+ @Override
+ protected SecretsManagerClient getClient(IVariables variables) {
+ return mockClient;
+ }
+ };
+ variables = new Variables();
+ }
+
+ private void respondWithString(String value) {
+ when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+
.thenReturn(GetSecretValueResponse.builder().secretString(value).build());
+ }
+
+ private GetSecretValueRequest captureRequest() {
+ ArgumentCaptor<GetSecretValueRequest> captor =
+ ArgumentCaptor.forClass(GetSecretValueRequest.class);
+ verify(mockClient).getSecretValue(captor.capture());
+ return captor.getValue();
+ }
+
+ @Test
+ void testPluginMetadata() {
+ assertEquals("AwsSecretsManager", resolver.getPluginId());
+ assertEquals("AWS Secrets Manager Variable Resolver",
resolver.getPluginName());
+ }
+
+ @Test
+ void testDefaults() {
+ assertEquals(AwsSecretsManagerAuthType.AUTOMATIC.name(),
resolver.getAuthenticationType());
+ assertEquals("AWSCURRENT", resolver.getVersionStage());
+ assertEquals("0", resolver.getCacheTtlSeconds());
+ }
+
+ @Test
+ void testGettersAndSetters() {
+ resolver.setRegion("eu-west-1");
+ resolver.setAccessKey("AKIAEXAMPLE");
+ resolver.setSecretKey("secret");
+ resolver.setSessionToken("token");
+ resolver.setCredentialsFile("/tmp/credentials");
+ resolver.setProfileName("hop");
+ resolver.setEndpointOverride("http://localhost:4566");
+ resolver.setSecretNamePrefix("production/");
+
+ assertEquals("eu-west-1", resolver.getRegion());
+ assertEquals("AKIAEXAMPLE", resolver.getAccessKey());
+ assertEquals("secret", resolver.getSecretKey());
+ assertEquals("token", resolver.getSessionToken());
+ assertEquals("/tmp/credentials", resolver.getCredentialsFile());
+ assertEquals("hop", resolver.getProfileName());
+ assertEquals("http://localhost:4566", resolver.getEndpointOverride());
+ assertEquals("production/", resolver.getSecretNamePrefix());
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ void testResolveWithoutSecretName(String secretName) throws Exception {
+ assertNull(resolver.resolve(secretName, variables));
+ verify(mockClient,
never()).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testResolveRejectsArn() throws Exception {
+ // An ARN contains colons, which the variable resolver expression syntax
splits on. Rather than
+ // silently looking up a truncated name we refuse it.
+ String arn =
"arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-secret-AbCdEf";
+
+ assertNull(resolver.resolve(arn, variables));
+ verify(mockClient,
never()).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testResolveRejectsTruncatedArn() throws Exception {
+ // What an ARN actually looks like by the time it reaches the resolver:
core splits the
+ // expression #{aws-secrets:arn:aws:secretsmanager:...} on ':' and hands
us only "arn".
+ assertNull(resolver.resolve("arn", variables));
+ verify(mockClient,
never()).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testResolveRejectsArnEvenWithPrefixConfigured() throws Exception {
+ resolver.setSecretNamePrefix("production/");
+
+ assertNull(resolver.resolve("arn", variables));
+ verify(mockClient,
never()).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testResolveReturnsSecretString() throws Exception {
+ respondWithString("p@ssw0rd");
+
+ assertEquals("p@ssw0rd", resolver.resolve("my-secret", variables));
+ assertEquals("my-secret", captureRequest().secretId());
+ }
+
+ @Test
+ void testResolveReturnsJsonUntouched() throws Exception {
+ // Picking a single key out of the JSON is done by the variable resolver
machinery in core, so
+ // the resolver hands back the payload as stored.
+ String json = "{\"username\":\"hop\",\"password\":\"secret\"}";
+ respondWithString(json);
+
+ assertEquals(json, resolver.resolve("db-credentials", variables));
+ }
+
+ @Test
+ void testResolveAppliesSecretNamePrefix() throws Exception {
+ resolver.setSecretNamePrefix("production/");
+ respondWithString("value");
+
+ resolver.resolve("database", variables);
+
+ assertEquals("production/database", captureRequest().secretId());
+ }
+
+ @Test
+ void testResolveResolvesVariablesInPrefix() throws Exception {
+ variables.setVariable("ENVIRONMENT", "staging");
+ resolver.setSecretNamePrefix("${ENVIRONMENT}/");
+ respondWithString("value");
+
+ resolver.resolve("database", variables);
+
+ assertEquals("staging/database", captureRequest().secretId());
+ }
+
+ @Test
+ void testResolveUsesAwsCurrentByDefault() throws Exception {
+ respondWithString("value");
+
+ resolver.resolve("my-secret", variables);
+
+ assertEquals("AWSCURRENT", captureRequest().versionStage());
+ }
+
+ @Test
+ void testResolveUsesConfiguredVersionStage() throws Exception {
+ resolver.setVersionStage("AWSPREVIOUS");
+ respondWithString("value");
+
+ resolver.resolve("my-secret", variables);
+
+ assertEquals("AWSPREVIOUS", captureRequest().versionStage());
+ }
+
+ @Test
+ void testResolveFallsBackToAwsCurrentOnEmptyVersionStage() throws Exception {
+ resolver.setVersionStage("");
+ respondWithString("value");
+
+ resolver.resolve("my-secret", variables);
+
+ assertEquals("AWSCURRENT", captureRequest().versionStage());
+ }
+
+ @Test
+ void testResolveEncodesBinarySecret() throws Exception {
+ byte[] binary = {1, 2, 3, 4, 5};
+ when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+ .thenReturn(
+
GetSecretValueResponse.builder().secretBinary(SdkBytes.fromByteArray(binary)).build());
+
+ assertEquals(
+ Base64.getEncoder().encodeToString(binary),
resolver.resolve("binary-secret", variables));
+ }
+
+ @Test
+ void testResolveDecodesBinarySecretBackToOriginal() throws Exception {
+ String original = "keystore-content";
+ when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+ .thenReturn(
+ GetSecretValueResponse.builder()
+ .secretBinary(SdkBytes.fromUtf8String(original))
+ .build());
+
+ String resolved = resolver.resolve("binary-secret", variables);
+
+ assertEquals(
+ original, new String(Base64.getDecoder().decode(resolved),
StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testResolveReturnsNullWhenSecretNotFound() throws Exception {
+ when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+ .thenThrow(ResourceNotFoundException.builder().message("not
found").build());
+
+ assertNull(resolver.resolve("missing-secret", variables));
+ }
+
+ @Test
+ void testResolveReturnsNullOnFailure() throws Exception {
+ when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+ .thenThrow(new RuntimeException("network down"));
+
+ assertNull(resolver.resolve("my-secret", variables));
+ }
+
+ @Test
+ void testCachingIsOffByDefault() throws Exception {
+ respondWithString("value");
+
+ resolver.resolve("my-secret", variables);
+ resolver.resolve("my-secret", variables);
+
+ verify(mockClient,
times(2)).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testCachingServesRepeatedLookupsFromCache() throws Exception {
+ resolver.setCacheTtlSeconds("60");
+ respondWithString("value");
+
+ assertEquals("value", resolver.resolve("my-secret", variables));
+ assertEquals("value", resolver.resolve("my-secret", variables));
+
+ verify(mockClient,
times(1)).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testCachingIsPerSecret() throws Exception {
+ resolver.setCacheTtlSeconds("60");
+ respondWithString("value");
+
+ resolver.resolve("first-secret", variables);
+ resolver.resolve("second-secret", variables);
+
+ verify(mockClient,
times(2)).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testExpiredCacheEntryIsLookedUpAgain() throws Exception {
+ // A TTL that has already elapsed by the time the entry is read.
+ resolver.setCacheTtlSeconds("-1");
+ respondWithString("value");
+
+ resolver.resolve("my-secret", variables);
+ resolver.resolve("my-secret", variables);
+
+ verify(mockClient,
times(2)).getSecretValue(any(GetSecretValueRequest.class));
+ }
+
+ @Test
+ void testAutomaticAuthTypeUsesCredentialChain() throws Exception {
+ assertInstanceOf(
+ DefaultCredentialsProvider.class,
+ resolver.buildCredentialsProvider(null, null, null, null, null, null));
+ }
+
+ @Test
+ void testAccessKeysAuthTypeUsesStaticCredentials() throws Exception {
+ AwsCredentialsProvider provider =
+ resolver.buildCredentialsProvider("ACCESS_KEYS", "AKIAEXAMPLE",
"secret", null, null, null);
+
+ assertInstanceOf(StaticCredentialsProvider.class, provider);
+ // Asserting the provider type alone would still pass with the key and the
secret swapped.
+ AwsCredentials credentials = provider.resolveCredentials();
+ assertEquals("AKIAEXAMPLE", credentials.accessKeyId());
+ assertEquals("secret", credentials.secretAccessKey());
+ }
+
+ @Test
+ void testAccessKeysAuthTypeAcceptsSessionToken() throws Exception {
+ AwsCredentialsProvider provider =
+ resolver.buildCredentialsProvider(
+ "ACCESS_KEYS", "AKIAEXAMPLE", "secret", "session-token", null,
null);
+
+ assertInstanceOf(StaticCredentialsProvider.class, provider);
+ AwsCredentials credentials = provider.resolveCredentials();
+ assertInstanceOf(AwsSessionCredentials.class, credentials);
+ assertEquals("AKIAEXAMPLE", credentials.accessKeyId());
+ assertEquals("secret", credentials.secretAccessKey());
+ assertEquals("session-token", ((AwsSessionCredentials)
credentials).sessionToken());
+ }
+
+ @Test
+ void testAccessKeysAreDecryptedWhenStoredEncrypted() throws Exception {
+ // Hop's metadata serializer normally decrypts these, but a hand written
or older metadata file
+ // can still carry the encrypted form.
+ AwsCredentialsProvider provider =
+ resolver.buildCredentialsProvider(
+ "ACCESS_KEYS",
+ "AKIAEXAMPLE",
+ Encr.encryptPasswordIfNotUsingVariables("secret"),
+ null,
+ null,
+ null);
+
+ assertEquals("secret", provider.resolveCredentials().secretAccessKey());
+ }
+
+ @Test
+ void testAccessKeysAuthTypeRequiresBothKeys() {
+ HopException e =
+ assertThrows(
+ HopException.class,
+ () ->
+ resolver.buildCredentialsProvider(
+ "ACCESS_KEYS", "AKIAEXAMPLE", null, null, null, null));
+ assertTrue(e.getMessage().contains("ACCESS_KEYS"));
+ }
+
+ @Test
+ void testCredentialsFileAuthTypeUsesProfileProvider() throws Exception {
+ assertInstanceOf(
+ ProfileCredentialsProvider.class,
+ resolver.buildCredentialsProvider("CREDENTIALS_FILE", null, null,
null, null, "hop"));
+ }
+
+ @Test
+ void testAuthTypeIsCaseInsensitive() throws Exception {
+ assertEquals(AwsSecretsManagerAuthType.ACCESS_KEYS,
resolver.parseAuthType("access_keys"));
+ }
+
+ @Test
+ void testEmptyAuthTypeFallsBackToDefault() throws Exception {
+ assertEquals(AwsSecretsManagerAuthType.AUTOMATIC,
resolver.parseAuthType(null));
+ assertEquals(AwsSecretsManagerAuthType.AUTOMATIC,
resolver.parseAuthType(""));
+ }
+
+ @Test
+ void testUnknownAuthTypeIsRejected() {
+ HopException e = assertThrows(HopException.class, () ->
resolver.parseAuthType("KERBEROS"));
+ assertTrue(e.getMessage().contains("KERBEROS"));
+ }
+
+ @Test
+ void testRegionComboShowsCodeAndDescription() {
+ assertTrue(resolver.getRegions(null, null).contains("eu-west-1 - Europe
(Ireland)"));
+ // The empty entry leaves the region to the environment.
+ assertTrue(resolver.getRegions(null, null).contains(""));
+ }
+
+ @Test
+ void testRegionStoresOnlyTheCode() {
+ // Whatever the combo hands over, the field and therefore the metadata
file keep just the code.
+ resolver.setRegion("eu-west-1 - Europe (Ireland)");
+ assertEquals("eu-west-1", resolver.getRegion());
+
+ resolver.setRegion("eu-west-1");
+ assertEquals("eu-west-1", resolver.getRegion());
+ }
+
+ @Test
+ void testRegionIsShownWithItsDescription() {
+ resolver.setRegion("eu-west-1");
+ assertEquals("eu-west-1 - Europe (Ireland)", resolver.getRegionLabel());
+ }
+
+ @Test
+ void testRegionLabelLeavesAVariableAlone() {
+ // A variable is not a region code, so there is nothing to decorate it
with.
+ resolver.setRegion("${AWS_REGION}");
+ assertEquals("${AWS_REGION}", resolver.getRegion());
+ assertEquals("${AWS_REGION}", resolver.getRegionLabel());
+ }
+
+ @Test
+ void testRegionLabelIsEmptyWhenNoRegionIsSet() {
+ assertEquals(null, resolver.getRegionLabel());
+ resolver.setRegion("");
+ assertEquals("", resolver.getRegionLabel());
+ }
+
+ @Test
+ void testRegionLabelRoundTripsThroughTheCombo() {
+ // What the getter shows must be something the setter can take back
without drift.
+ for (String item : resolver.getRegions(null, null)) {
+ resolver.setRegion(item);
+ assertEquals(item, resolver.getRegionLabel(), "round trip failed for
combo entry " + item);
+ }
+ }
+
+ @Test
+ void testRegionCodeIsReadBackFromTheDisplayedValue() {
+ assertEquals(
+ "eu-west-1", AwsSecretsManagerVariableResolver.regionCode("eu-west-1 -
Europe (Ireland)"));
+ // A hand written or variable driven value carries no description.
+ assertEquals("eu-west-1",
AwsSecretsManagerVariableResolver.regionCode("eu-west-1"));
+ assertEquals("", AwsSecretsManagerVariableResolver.regionCode(""));
+ assertNull(AwsSecretsManagerVariableResolver.regionCode(null));
+ }
+
+ @Test
+ void testComboBoxesArePopulated() {
+ assertEquals(
+ java.util.List.of("AUTOMATIC", "ACCESS_KEYS", "CREDENTIALS_FILE"),
+ resolver.getAuthenticationTypes(null, null));
+ }
+}
diff --git
a/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerWidgetVisibilityTest.java
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerWidgetVisibilityTest.java
new file mode 100644
index 0000000000..e935d79330
--- /dev/null
+++
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerWidgetVisibilityTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.hop.core.variables.resolver.aws;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.hop.core.gui.plugin.GuiRegistry;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.core.variables.resolver.VariableResolver;
+import org.apache.hop.ui.core.gui.GuiCompositeWidgets;
+import org.apache.hop.ui.hopgui.HopGuiEnvironment;
+import org.apache.hop.ui.testing.SwtBotTestBase;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The three authentication types have no credentials in common, so the editor
only shows the ones
+ * the chosen type can actually use. This checks that the others are out of
the way, and -- just as
+ * important -- that the options which always apply stay where they are.
+ */
+@Tag("uitest")
+class AwsSecretsManagerWidgetVisibilityTest extends SwtBotTestBase {
+
+ @BeforeAll
+ static void registerGuiPluginElements() throws Exception {
+ // Puts the @GuiWidgetElement annotations of this plugin into the
registry. Without it the
+ // composite comes up empty. The registry appends without checking for
duplicates, so this only
+ // runs when another test class in this module has not already done it.
+ //
+ if (GuiRegistry.getInstance()
+ .findGuiElements(
+ AwsSecretsManagerVariableResolver.class.getName(),
+ VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID)
+ == null) {
+ HopGuiEnvironment.init();
+ }
+ }
+
+ @Test
+ @DisplayName("AUTOMATIC shows no credential fields at all")
+ void automaticHidesEveryCredentialField() {
+ withWidgets(
+ resolver -> {
+ // Nothing configured: how a new resolver starts life.
+ },
+ (resolver, widgets) -> {
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_AUTHENTICATION_TYPE);
+
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_ACCESS_KEY);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_SECRET_KEY);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_SESSION_TOKEN);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_CREDENTIALS_FILE);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_PROFILE_NAME);
+ });
+ }
+
+ @Test
+ @DisplayName("ACCESS_KEYS shows the keys and not the credentials file")
+ void accessKeysShowsOnlyTheKeys() {
+ withWidgets(
+ resolver ->
resolver.setAuthenticationType(AwsSecretsManagerAuthType.ACCESS_KEYS.name()),
+ (resolver, widgets) -> {
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_ACCESS_KEY);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_SECRET_KEY);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_SESSION_TOKEN);
+
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_CREDENTIALS_FILE);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_PROFILE_NAME);
+ });
+ }
+
+ @Test
+ @DisplayName("CREDENTIALS_FILE shows the file and profile and not the keys")
+ void credentialsFileShowsOnlyTheFileFields() {
+ withWidgets(
+ resolver ->
+
resolver.setAuthenticationType(AwsSecretsManagerAuthType.CREDENTIALS_FILE.name()),
+ (resolver, widgets) -> {
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_CREDENTIALS_FILE);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_PROFILE_NAME);
+
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_ACCESS_KEY);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_SECRET_KEY);
+ assertHidden(widgets,
AwsSecretsManagerVariableResolver.ID_SESSION_TOKEN);
+ });
+ }
+
+ @Test
+ @DisplayName("the options that always apply are never hidden")
+ void generalOptionsStayVisibleForEveryAuthType() {
+ for (AwsSecretsManagerAuthType authType :
AwsSecretsManagerAuthType.values()) {
+ withWidgets(
+ resolver -> resolver.setAuthenticationType(authType.name()),
+ (resolver, widgets) -> {
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_REGION);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_AUTHENTICATION_TYPE);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_ENDPOINT_OVERRIDE);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_SECRET_NAME_PREFIX);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_VERSION_STAGE);
+ assertVisible(widgets,
AwsSecretsManagerVariableResolver.ID_CACHE_TTL_SECONDS);
+ });
+ }
+ }
+
+ private void withWidgets(
+ Consumer<AwsSecretsManagerVariableResolver> configure,
+ BiConsumer<AwsSecretsManagerVariableResolver, GuiCompositeWidgets>
assertions) {
+ ensureDisplay();
+
+ Shell shell = new Shell(display);
+ shell.setLayout(new FormLayout());
+ try {
+ AwsSecretsManagerVariableResolver resolver = new
AwsSecretsManagerVariableResolver();
+ configure.accept(resolver);
+
+ GuiCompositeWidgets widgets = new GuiCompositeWidgets(new Variables());
+ widgets.createCompositeWidgets(
+ resolver, null, shell,
VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID, null);
+ widgets.setWidgetsContents(resolver, shell,
VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID);
+ widgets.setWidgetsListener(resolver);
+ resolver.widgetsPopulated(widgets);
+
+ shell.layout(true, true);
+ shell.pack();
+
+ assertions.accept(resolver, widgets);
+ } finally {
+ if (!shell.isDisposed()) {
+ shell.dispose();
+ }
+ }
+ }
+
+ private void assertVisible(GuiCompositeWidgets widgets, String id) {
+ Control control = widgets.getWidgetsMap().get(id);
+ assertNotNull(control, "no widget registered for " + id);
+ assertTrue(control.getVisible(), id + " should be visible");
+ Control label = widgets.getLabelsMap().get(id);
+ if (label != null) {
+ assertTrue(label.getVisible(), "the label of " + id + " should be
visible");
+ }
+ }
+
+ private void assertHidden(GuiCompositeWidgets widgets, String id) {
+ Control control = widgets.getWidgetsMap().get(id);
+ assertNotNull(control, "no widget registered for " + id);
+ assertFalse(control.getVisible(), id + " should be hidden");
+ Control label = widgets.getLabelsMap().get(id);
+ if (label != null) {
+ assertFalse(label.getVisible(), "the label of " + id + " should be
hidden");
+ }
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
b/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
index a5f028aa23..dee712743d 100644
---
a/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
+++
b/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hop.core.Const;
import org.apache.hop.core.Props;
@@ -37,6 +38,7 @@ import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.gui.GuiCompositeWidgets;
import org.apache.hop.ui.core.gui.GuiCompositeWidgetsAdapter;
import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.core.gui.IGuiPluginCompositeWidgetsListener;
import org.apache.hop.ui.core.metadata.MetadataEditor;
import org.apache.hop.ui.core.metadata.MetadataManager;
import org.apache.hop.ui.hopgui.HopGui;
@@ -196,14 +198,7 @@ public class VariableResolverEditor extends
MetadataEditor<VariableResolver> {
null);
// Add listener to detect change
- guiCompositeWidgets.setWidgetsListener(
- new GuiCompositeWidgetsAdapter() {
- @Override
- public void widgetModified(
- GuiCompositeWidgets compositeWidgets, Control changedWidget,
String widgetId) {
- setChanged();
- }
- });
+ guiCompositeWidgets.setWidgetsListener(createWidgetsListener());
setWidgetsContent();
@@ -252,14 +247,7 @@ public class VariableResolverEditor extends
MetadataEditor<VariableResolver> {
wResolverSpecificComp,
VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
null);
- guiCompositeWidgets.setWidgetsListener(
- new GuiCompositeWidgetsAdapter() {
- @Override
- public void widgetModified(
- GuiCompositeWidgets compositeWidgets, Control changedWidget,
String widgetId) {
- setChanged();
- }
- });
+ guiCompositeWidgets.setWidgetsListener(createWidgetsListener());
// Put the data back
//
@@ -270,6 +258,50 @@ public class VariableResolverEditor extends
MetadataEditor<VariableResolver> {
busyChangingConnectionType.set(false);
}
+ /**
+ * The composite has a single listener slot, which this editor needs for its
own change tracking.
+ * Resolver plugins that implement {@link
IGuiPluginCompositeWidgetsListener} -- to enable, hide
+ * or otherwise adjust their own widgets -- are forwarded to from here;
without this their
+ * callbacks would never fire.
+ */
+ private IGuiPluginCompositeWidgetsListener createWidgetsListener() {
+ return new GuiCompositeWidgetsAdapter() {
+ @Override
+ public void widgetsCreated(GuiCompositeWidgets compositeWidgets) {
+ pluginWidgetsListener().ifPresent(listener ->
listener.widgetsCreated(compositeWidgets));
+ }
+
+ @Override
+ public void widgetsPopulated(GuiCompositeWidgets compositeWidgets) {
+ pluginWidgetsListener().ifPresent(listener ->
listener.widgetsPopulated(compositeWidgets));
+ }
+
+ @Override
+ public void widgetModified(
+ GuiCompositeWidgets compositeWidgets, Control changedWidget, String
widgetId) {
+ setChanged();
+ pluginWidgetsListener()
+ .ifPresent(
+ listener -> listener.widgetModified(compositeWidgets,
changedWidget, widgetId));
+ }
+
+ @Override
+ public void persistContents(GuiCompositeWidgets compositeWidgets) {
+ pluginWidgetsListener().ifPresent(listener ->
listener.persistContents(compositeWidgets));
+ }
+ };
+ }
+
+ private Optional<IGuiPluginCompositeWidgetsListener> pluginWidgetsListener()
{
+ VariableResolver resolver = getMetadata();
+ if (resolver == null) {
+ return Optional.empty();
+ }
+ return resolver.getIResolver() instanceof
IGuiPluginCompositeWidgetsListener listener
+ ? Optional.of(listener)
+ : Optional.empty();
+ }
+
private void enableFields() {
// Perhaps later
}