diff --git a/bom/pom.xml b/bom/pom.xml index d19dbfced6e..cac54b2a959 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -907,6 +907,11 @@ helidon-integrations-oci-sdk-cdi ${helidon.version} + + io.helidon.integrations.oci.sdk + helidon-integrations-oci-sdk-runtime + ${helidon.version} + io.helidon.integrations.oci.metrics helidon-integrations-oci-metrics diff --git a/integrations/oci/sdk/pom.xml b/integrations/oci/sdk/pom.xml index d850de4cff3..7267c24724f 100644 --- a/integrations/oci/sdk/pom.xml +++ b/integrations/oci/sdk/pom.xml @@ -33,6 +33,7 @@ cdi + runtime diff --git a/integrations/oci/sdk/runtime/README.md b/integrations/oci/sdk/runtime/README.md new file mode 100644 index 00000000000..8aaea313ec4 --- /dev/null +++ b/integrations/oci/sdk/runtime/README.md @@ -0,0 +1,6 @@ +# helidon-integrations-oci-sdk-runtime + +This module offers integration for the OCI runtime environment. Most of the documentation for this be found under [helidon-integrations-oci-sdk](../). + +The [OciExtension](src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java) class is the +main access point for programmatically obtaining the global [OciConfig](src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfigBlueprint.java) configuration. The javadoc also provides guidelines for configuration. diff --git a/integrations/oci/sdk/runtime/etc/spotbugs/exclude.xml b/integrations/oci/sdk/runtime/etc/spotbugs/exclude.xml new file mode 100644 index 00000000000..e832dfab49e --- /dev/null +++ b/integrations/oci/sdk/runtime/etc/spotbugs/exclude.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/integrations/oci/sdk/runtime/pom.xml b/integrations/oci/sdk/runtime/pom.xml new file mode 100644 index 00000000000..be15aa2770a --- /dev/null +++ b/integrations/oci/sdk/runtime/pom.xml @@ -0,0 +1,85 @@ + + + + + + io.helidon.integrations.oci.sdk + helidon-integrations-oci-sdk-project + 3.2.3-SNAPSHOT + ../pom.xml + + 4.0.0 + + helidon-integrations-oci-sdk-runtime + Helidon Integrations OCI Runtime + + Helidon Framework Runtime support for the OCI SDK + + + etc/spotbugs/exclude.xml + + + + + com.oracle.oci.sdk + oci-java-sdk-common + compile + + + io.helidon.common + helidon-common + + + io.helidon.config + helidon-config + + + io.helidon.config + helidon-config-yaml + + + io.helidon.config + helidon-config-metadata + true + + + jakarta.inject + jakarta.inject-api + compile + + + io.helidon.common.testing + helidon-common-testing-junit5 + test + + + org.hamcrest + hamcrest-all + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java new file mode 100644 index 00000000000..907c2b5e491 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Supplier; + +import com.oracle.bmc.ConfigFileReader; +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider; +import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; +import com.oracle.bmc.auth.SimplePrivateKeySupplier; +import com.oracle.bmc.auth.StringPrivateKeySupplier; +import jakarta.inject.Provider; + +/** + * This (overridable) provider will provide the default implementation for {@link AbstractAuthenticationDetailsProvider}. + * + * @see OciExtension + * @see OciConfig + */ +class OciAuthenticationDetailsProvider implements Provider { + static final System.Logger LOGGER = System.getLogger(OciAuthenticationDetailsProvider.class.getName()); + + static final String KEY_AUTH_STRATEGY = "auth-strategy"; + static final String KEY_AUTH_STRATEGIES = "auth-strategies"; + static final String TAG_RESOURCE_PRINCIPAL_VERSION = "OCI_RESOURCE_PRINCIPAL_VERSION"; + static final String VAL_AUTO = "auto"; + static final String VAL_CONFIG = "config"; + static final String VAL_CONFIG_FILE = "config-file"; + // com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider + static final String VAL_INSTANCE_PRINCIPALS = "instance-principals"; + // com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider + static final String VAL_RESOURCE_PRINCIPAL = "resource-principal"; + + // order is important here - see the tests and the docs + static final List ALL_STRATEGIES = List.of(VAL_INSTANCE_PRINCIPALS, + VAL_RESOURCE_PRINCIPAL, + VAL_CONFIG, + VAL_CONFIG_FILE); + + OciAuthenticationDetailsProvider() { + } + + @Override + public AbstractAuthenticationDetailsProvider get() { + OciConfig ociConfig = OciExtension.ociConfig(); + return select(ociConfig, true).authStrategy().select(ociConfig); + } + + /** + * Supply the selected strategy given the global OCI configuration. If one is named outright then use it, otherwise hunt for + * the appropriate authentication strategy from the list of {@code auth-strategies} explicitly or implicitly defined. + * + * @param ociConfig the oci configuration + * @param verifyIsAvailable flag to indicate whether the provider should be checked for availability + * @return the configured, or else most applicable OCI auth strategy + */ + static Supply select(OciConfig ociConfig, + boolean verifyIsAvailable) { + Optional authStrategy = ociConfig.authStrategy(); + if (authStrategy.isPresent() && !authStrategy.get().equalsIgnoreCase(AuthStrategy.AUTO.id)) { + return new Supply(AuthStrategy.fromNameOrId(authStrategy.get()).orElseThrow(), ociConfig); + } + + List strategies = AuthStrategy.convert(ociConfig.potentialAuthStrategies()); + for (AuthStrategy s : strategies) { + if (!verifyIsAvailable || s.isAvailable(ociConfig)) { + LOGGER.log(System.Logger.Level.DEBUG, "Using authentication strategy " + s + + "; selected AbstractAuthenticationDetailsProvider " + s.type().getTypeName()); + return new Supply(s, ociConfig); + } else { + LOGGER.log(System.Logger.Level.TRACE, "Skipping authentication strategy " + s + " because it is not available"); + } + } + throw new NoSuchElementException("No instances of " + + AbstractAuthenticationDetailsProvider.class.getName() + + " available for use. Verify your configuration named: " + + OciConfig.CONFIG_KEY); + } + + static boolean canReadPath(String pathName) { + return (pathName != null && Path.of(pathName).toFile().canRead()); + } + + static String userHomePrivateKeyPath(OciConfig ociConfig) { + return Paths.get(System.getProperty("user.home"), ".oci", ociConfig.authKeyFile()).toString(); + } + + + enum AuthStrategy { + /** + * Auto selection of the auth strategy. + */ + AUTO(VAL_AUTO, + AbstractAuthenticationDetailsProvider.class, + (ociConfig) -> true, + (ociConfig) -> OciAuthenticationDetailsProvider.select(ociConfig, true).get()), + + /** + * Corresponds to {@link SimpleAuthenticationDetailsProvider}. + * + * @see OciConfig#simpleConfigIsPresent() + */ + CONFIG(VAL_CONFIG, + SimpleAuthenticationDetailsProvider.class, + OciConfig::simpleConfigIsPresent, + (ociConfig) -> { + SimpleAuthenticationDetailsProvider.SimpleAuthenticationDetailsProviderBuilder b = + SimpleAuthenticationDetailsProvider.builder(); + ociConfig.authTenantId().ifPresent(b::tenantId); + ociConfig.authUserId().ifPresent(b::userId); + ociConfig.authRegion().ifPresent(it -> b.region(Region.fromRegionCodeOrId(it))); + ociConfig.authFingerprint().ifPresent(b::fingerprint); + ociConfig.authPassphrase().ifPresent(chars -> b.passPhrase(String.valueOf(chars))); + ociConfig.authPrivateKey() + .ifPresentOrElse(pk -> b.privateKeySupplier(new StringPrivateKeySupplier(String.valueOf(pk))), + () -> b.privateKeySupplier(new SimplePrivateKeySupplier( + userHomePrivateKeyPath(ociConfig)))); + return b.build(); + }), + + /** + * Corresponds to {@link ConfigFileAuthenticationDetailsProvider}, i.e., "$HOME/.oci/config/. + * + * @see OciConfig#fileConfigIsPresent() + */ + CONFIG_FILE(VAL_CONFIG_FILE, + ConfigFileAuthenticationDetailsProvider.class, + (configBean) -> configBean.fileConfigIsPresent() + && (configBean.configPath().isEmpty() || canReadPath(configBean.configPath().orElse(null))), + (configBean) -> { + // https://github.com/oracle/oci-java-sdk/blob/master/bmc-common/src/main/java/com/oracle/bmc/auth/ConfigFileAuthenticationDetailsProvider.java + // https://github.com/oracle/oci-java-sdk/blob/master/bmc-examples/src/main/java/ObjectStorageSyncExample.java + try { + if (configBean.configPath().isPresent()) { + return new ConfigFileAuthenticationDetailsProvider(configBean.configPath().get(), + configBean.configProfile().orElseThrow()); + } else { + return new ConfigFileAuthenticationDetailsProvider(ConfigFileReader.parseDefault()); + } + } catch (IOException e) { + throw new UncheckedIOException(e.getMessage(), e); + } + }), + + /** + * Corresponds to {@link InstancePrincipalsAuthenticationDetailsProvider}. + * + * @see OciConfig + */ + INSTANCE_PRINCIPALS(VAL_INSTANCE_PRINCIPALS, + InstancePrincipalsAuthenticationDetailsProvider.class, + OciAvailabilityDefault::runningOnOci, + (configBean) -> InstancePrincipalsAuthenticationDetailsProvider.builder().build()), + + /** + * Corresponds to {@link ResourcePrincipalAuthenticationDetailsProvider}. + * + * @see OciConfig + */ + RESOURCE_PRINCIPAL(VAL_RESOURCE_PRINCIPAL, + ResourcePrincipalAuthenticationDetailsProvider.class, + (configBean) -> { + // https://github.com/oracle/oci-java-sdk/blob/v2.19.0/bmc-common/src/main/java/com/oracle/bmc/auth/ResourcePrincipalAuthenticationDetailsProvider.java#L246-L251 + return (System.getenv(TAG_RESOURCE_PRINCIPAL_VERSION) != null); + }, + (configBean) -> ResourcePrincipalAuthenticationDetailsProvider.builder().build()); + + private final String id; + private final Class type; + private final Availability availability; + private final Selector selector; + + AuthStrategy(String id, + Class type, + Availability availability, + Selector selector) { + this.id = id; + this.type = type; + this.availability = availability; + this.selector = selector; + } + + String id() { + return id; + } + + Class type() { + return type; + } + + boolean isAvailable(OciConfig ociConfig) { + return availability.isAvailable(ociConfig); + } + + AbstractAuthenticationDetailsProvider select(OciConfig ociConfig) { + return selector.select(ociConfig); + } + + static Optional fromNameOrId(String nameOrId) { + try { + return Optional.of(valueOf(nameOrId)); + } catch (Exception e) { + return Arrays.stream(AuthStrategy.values()) + .filter(it -> nameOrId.equalsIgnoreCase(it.id()) + || nameOrId.equalsIgnoreCase(it.name())) + .findFirst(); + } + } + + static List convert(Collection authStrategies) { + return authStrategies.stream() + .map(AuthStrategy::fromNameOrId) + .map(Optional::orElseThrow) + .filter(s -> s != AuthStrategy.AUTO) + .toList(); + } + } + + + @FunctionalInterface + interface Availability { + boolean isAvailable(OciConfig ociConfig); + } + + + @FunctionalInterface + interface Selector { + AbstractAuthenticationDetailsProvider select(OciConfig ociConfig); + } + + static class Supply implements Supplier { + private final AuthStrategy authStrategy; + private final OciConfig ociConfig; + + private Supply(AuthStrategy authStrategy, + OciConfig ociConfig) { + this.authStrategy = authStrategy; + this.ociConfig = ociConfig; + } + + @Override + public AbstractAuthenticationDetailsProvider get() { + return authStrategy.select(ociConfig); + } + + public AuthStrategy authStrategy() { + return authStrategy; + } + } + +} diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java new file mode 100644 index 00000000000..1c0a3520a57 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +/** + * Provides a convenient contract for checking whether the current runtime environment is running on/inside an OCI compute node. + * + * @see OciExtension + */ +public interface OciAvailability { + + /** + * Returns true if the implementation determines it is running on/inside an OCI compute node. + * + * @param ociConfig the oci config bean + * @return true if there running on/inside an OCI compute node + */ + boolean isRunningOnOci(OciConfig ociConfig); + + /** + * Will source the config bean from {@link OciExtension#ociConfig()} to make the call to {@link #isRunningOnOci(OciConfig)}. + * + * @return true if there running on/inside an OCI compute node + */ + default boolean isRunningOnOci() { + return isRunningOnOci(OciExtension.ociConfig()); + } + +} diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailabilityDefault.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailabilityDefault.java new file mode 100644 index 00000000000..82e375b93aa --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailabilityDefault.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import com.oracle.bmc.Region; + +/** + * This (overridable) implementation will check the {@link OciConfig} for {@code IMDS} availability. And if it is found to be + * available, will also perform a secondary check on {@link Region#getRegionFromImds()} to ensure it returns a non-null value. + */ +class OciAvailabilityDefault implements OciAvailability { + + @Override + public boolean isRunningOnOci(OciConfig ociConfig) { + return runningOnOci(ociConfig); + } + + static boolean runningOnOci(OciConfig ociConfig) { + if (!canReach(ociConfig.imdsHostName(), (int) ociConfig.imdsTimeout().toMillis())) { + return false; + } + + return (Region.getRegionFromImds() != null); + } + + static boolean canReach(String address, + int timeoutInMills) { + if (address == null) { + return false; + } + + InetAddress imds; + try { + imds = InetAddress.getByName(address); + } catch (UnknownHostException unknownHostException) { + throw new UncheckedIOException(unknownHostException.getMessage(), unknownHostException); + } + + try { + return imds.isReachable(timeoutInMills); + } catch (ConnectException connectException) { + return false; + } catch (IOException ioException) { + throw new UncheckedIOException(ioException.getMessage(), ioException); + } + } + +} diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfig.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfig.java new file mode 100644 index 00000000000..67e6ff375a7 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfig.java @@ -0,0 +1,1199 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.util.Objects; +import java.util.Optional; + +import io.helidon.config.Config; + +/** + * Configuration used by {@link OciAuthenticationDetailsProvider}. + * + * @see OciExtension + * + * @see #builder() + * @see #create() + */ +public interface OciConfig extends OciConfigBlueprint { + /** + * Create a new fluent API builder to customize configuration. + * + * @return a new builder + */ + static Builder builder() { + return new Builder(); + } + + /** + * Create a new fluent API builder from an existing instance. + * + * @param instance an existing instance used as a base for the builder + * @return a builder based on an instance + */ + static Builder builder(OciConfig instance) { + return OciConfig.builder().from(instance); + } + + /** + * Create a new instance from configuration. + * + * @param config used to configure the new instance + * @return a new instance configured from configuration + */ + static OciConfig create(Config config) { + return OciConfig.builder().config(config).buildPrototype(); + } + + /** + * Create a new instance with default values. + * + * @return a new instance + */ + static OciConfig create() { + return OciConfig.builder().buildPrototype(); + } + + /** + * Fluent API builder base for {@link OciConfig}. + * + * @param type of the builder extending this abstract builder + * @param type of the prototype interface + */ + abstract class BuilderBase, PROTOTYPE extends OciConfig> + implements io.helidon.common.Builder { + private Config config; + private String authStrategy; + private final java.util.List authStrategies = new java.util.ArrayList<>(); + private String configPath; + private String configProfile = "DEFAULT"; + private String authFingerprint; + private String authKeyFile = "oci_api_key.pem"; + private String authPrivateKeyPath; + private char[] authPrivateKey; + private char[] authPassphrase; + private String authRegion; + private String authTenantId; + private String authUserId; + private String imdsHostName = "169.254.169.254"; + private java.time.Duration imdsTimeout = java.time.Duration.parse("PT0.1S"); + + /** + * Protected to support extensibility. + * + */ + protected BuilderBase() { + } + + /** + * Update this builder from an existing prototype instance. + * + * @param prototype existing prototype to update this builder from + * @return updated builder instance + */ + public BUILDER from(OciConfig prototype) { + authStrategy(prototype.authStrategy()); + addAuthStrategies(prototype.authStrategies()); + configPath(prototype.configPath()); + configProfile(prototype.configProfile()); + authFingerprint(prototype.authFingerprint()); + authKeyFile(prototype.authKeyFile()); + authPrivateKeyPath(prototype.authPrivateKeyPath()); + authPrivateKey(prototype.authPrivateKey()); + authPassphrase(prototype.authPassphrase()); + authRegion(prototype.authRegion()); + authTenantId(prototype.authTenantId()); + authUserId(prototype.authUserId()); + imdsHostName(prototype.imdsHostName()); + imdsTimeout(prototype.imdsTimeout()); + return identity(); + } + /** + * Update this builder from an existing prototype builder instance. + * + * @param builder existing builder prototype to update this builder from + * @return updated builder instance + */ + public BUILDER from(BuilderBase builder) { + builder.authStrategy().ifPresent(this::authStrategy); + addAuthStrategies(builder.authStrategies()); + builder.configPath().ifPresent(this::configPath); + builder.configProfile().ifPresent(this::configProfile); + builder.authFingerprint().ifPresent(this::authFingerprint); + authKeyFile(builder.authKeyFile()); + builder.authPrivateKeyPath().ifPresent(this::authPrivateKeyPath); + builder.authPrivateKey().ifPresent(this::authPrivateKey); + builder.authPassphrase().ifPresent(this::authPassphrase); + builder.authRegion().ifPresent(this::authRegion); + builder.authTenantId().ifPresent(this::authTenantId); + builder.authUserId().ifPresent(this::authUserId); + imdsHostName(builder.imdsHostName()); + imdsTimeout(builder.imdsTimeout()); + return identity(); + } + /** + * Handles providers and decorators. + */ + protected void preBuildPrototype() { + } + /** + * Validates required properties. + */ + protected void validatePrototype() { + } + /** + * Update builder from configuration (node of this type). + * If a value is present in configuration, it would override currently configured values. + * + * @param config configuration instance used to obtain values to update this builder + * @return updated builder instance + */ +// @Override + public BUILDER config(Config config) { + Objects.requireNonNull(config); + this.config = config; + config.get("auth-strategy").as(String.class).ifPresent(this::authStrategy); + config.get("auth-strategies").asList(String.class).ifPresent(this::authStrategies); + config.get("config.path").as(String.class).ifPresent(this::configPath); + config.get("config.profile").as(String.class).ifPresent(this::configProfile); + config.get("auth.fingerprint").as(String.class).ifPresent(this::authFingerprint); + config.get("auth.keyFile").as(String.class).ifPresent(this::authKeyFile); + config.get("auth.private-key-path").as(String.class).ifPresent(this::authPrivateKeyPath); + config.get("auth.private-key").asString().map(String::toCharArray).ifPresent(this::authPrivateKey); + config.get("auth.passphrase").asString().map(String::toCharArray).ifPresent(this::authPassphrase); + config.get("auth.region").as(String.class).ifPresent(this::authRegion); + config.get("auth.tenant-id").as(String.class).ifPresent(this::authTenantId); + config.get("auth.user-id").as(String.class).ifPresent(this::authUserId); + config.get("imds.hostname").as(String.class).ifPresent(this::imdsHostName); + config.get("imds.timeout.milliseconds").as(java.time.Duration.class).ifPresent(this::imdsTimeout); + return identity(); + } + /** + * The singular authentication strategy to apply. This will be preferred over {@link #authStrategies()} if both are + * present. + * + * @param authStrategy the singular authentication strategy to be applied + * @return updated builder instance + * @see #authStrategy() + */ + BUILDER authStrategy(Optional authStrategy) { + Objects.requireNonNull(authStrategy); + this.authStrategy = authStrategy.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authStrategy() + */ + public BUILDER clearAuthStrategy() { + this.authStrategy = null; + return identity(); + } + /** + * The singular authentication strategy to apply. This will be preferred over {@link #authStrategies()} if both are + * present. + * + * @param authStrategy the singular authentication strategy to be applied + * @return updated builder instance + * @see #authStrategy() + */ + public BUILDER authStrategy(String authStrategy) { + Objects.requireNonNull(authStrategy); + this.authStrategy = authStrategy; + return identity(); + } + /** + * The list of authentication strategies that will be attempted by + * {@link com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider} when one is + * called for. This is only used if {@link #authStrategy()} is not present. + * + *
    + *
  • {@code auto} - if present in the list, or if no value + * for this property exists, the behavior will be as if {@code + * config,config-file,instance-principals,resource-principal} + * were supplied instead.
  • + *
  • {@code config} - the + * {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code config-file} - the + * {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code instance-principals} - the + * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider} + * will be used.
  • + *
  • {@code resource-principal} - the + * {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider} + * will be used.
  • + *
+ *

+ * If there are many strategy descriptors supplied, the + * first one that is deemed to be available or suitable will + * be used and all others will be ignored. + * + * @param authStrategies the list of authentication strategies that will be applied, defaulting to {@code auto} + * @return updated builder instance + * @see #authStrategies() + */ + public BUILDER authStrategies(java.util.List authStrategies) { + Objects.requireNonNull(authStrategies); + this.authStrategies.clear(); + this.authStrategies.addAll(authStrategies); + return identity(); + } + /** + * The list of authentication strategies that will be attempted by + * {@link com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider} when one is + * called for. This is only used if {@link #authStrategy()} is not present. + * + *

    + *
  • {@code auto} - if present in the list, or if no value + * for this property exists, the behavior will be as if {@code + * config,config-file,instance-principals,resource-principal} + * were supplied instead.
  • + *
  • {@code config} - the + * {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code config-file} - the + * {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code instance-principals} - the + * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider} + * will be used.
  • + *
  • {@code resource-principal} - the + * {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider} + * will be used.
  • + *
+ *

+ * If there are many strategy descriptors supplied, the + * first one that is deemed to be available or suitable will + * be used and all others will be ignored. + * + * @param authStrategies the list of authentication strategies that will be applied, defaulting to {@code auto} + * @return updated builder instance + * @see #authStrategies() + */ + public BUILDER addAuthStrategies(java.util.List authStrategies) { + Objects.requireNonNull(authStrategies); + this.authStrategies.addAll(authStrategies); + return identity(); + } + /** + * The OCI configuration profile path. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property must also be present and then the + * {@linkplain com.oracle.bmc.ConfigFileReader#parse(String)} + * method will be passed this value. It is expected to be passed with a + * valid OCI configuration file path. + * + * @param configPath the OCI configuration profile path + * @return updated builder instance + * @see #configPath() + */ + BUILDER configPath(Optional configPath) { + Objects.requireNonNull(configPath); + this.configPath = configPath.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #configPath() + */ + public BUILDER clearConfigPath() { + this.configPath = null; + return identity(); + } + /** + * The OCI configuration profile path. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property must also be present and then the + * {@linkplain com.oracle.bmc.ConfigFileReader#parse(String)} + * method will be passed this value. It is expected to be passed with a + * valid OCI configuration file path. + * + * @param configPath the OCI configuration profile path + * @return updated builder instance + * @see #configPath() + */ + public BUILDER configPath(String configPath) { + Objects.requireNonNull(configPath); + this.configPath = configPath; + return identity(); + } + /** + * The OCI configuration/auth profile name. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property may also be optionally provided in order to override the default + * {@value #DEFAULT_PROFILE_NAME}. + * + * @param configProfile the optional OCI configuration/auth profile name + * @return updated builder instance + * @see #configProfile() + */ + BUILDER configProfile(Optional configProfile) { + Objects.requireNonNull(configProfile); + this.configProfile = configProfile.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #configProfile() + */ + public BUILDER clearConfigProfile() { + this.configProfile = null; + return identity(); + } + /** + * The OCI configuration/auth profile name. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property may also be optionally provided in order to override the default + * {@value #DEFAULT_PROFILE_NAME}. + * + * @param configProfile the optional OCI configuration/auth profile name + * @return updated builder instance + * @see #configProfile() + */ + public BUILDER configProfile(String configProfile) { + Objects.requireNonNull(configProfile); + this.configProfile = configProfile; + return identity(); + } + /** + * The OCI authentication fingerprint. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the API signing key's fingerprint. + * See {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getFingerprint()} for more details. + * + * @param authFingerprint the OCI authentication fingerprint + * @return updated builder instance + * @see #authFingerprint() + */ + BUILDER authFingerprint(Optional authFingerprint) { + Objects.requireNonNull(authFingerprint); + this.authFingerprint = authFingerprint.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authFingerprint() + */ + public BUILDER clearAuthFingerprint() { + this.authFingerprint = null; + return identity(); + } + /** + * The OCI authentication fingerprint. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the API signing key's fingerprint. + * See {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getFingerprint()} for more details. + * + * @param authFingerprint the OCI authentication fingerprint + * @return updated builder instance + * @see #authFingerprint() + */ + public BUILDER authFingerprint(String authFingerprint) { + Objects.requireNonNull(authFingerprint); + this.authFingerprint = authFingerprint; + return identity(); + } + /** + * The OCI authentication key file. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file must exist in the + * {@code user.home} directory. Alternatively, this property can be set using either {@link #authPrivateKey()} or + * using {@link #authPrivateKeyPath()}. + * + * @param authKeyFile the OCI authentication key file + * @return updated builder instance + * @see #authKeyFile() + */ + public BUILDER authKeyFile(String authKeyFile) { + Objects.requireNonNull(authKeyFile); + this.authKeyFile = authKeyFile; + return identity(); + } + /** + * The OCI authentication key file path. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file path is + * an alternative for using {@link #authKeyFile()} where the file must exist in the {@code user.home} directory. + * Alternatively, this property can be set using {@link #authPrivateKey()}. + * + * @param authPrivateKeyPath the OCI authentication key file path + * @return updated builder instance + * @see #authPrivateKeyPath() + */ + BUILDER authPrivateKeyPath(Optional authPrivateKeyPath) { + Objects.requireNonNull(authPrivateKeyPath); + this.authPrivateKeyPath = authPrivateKeyPath.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authPrivateKeyPath() + */ + public BUILDER clearAuthPrivateKeyPath() { + this.authPrivateKeyPath = null; + return identity(); + } + /** + * The OCI authentication key file path. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file path is + * an alternative for using {@link #authKeyFile()} where the file must exist in the {@code user.home} directory. + * Alternatively, this property can be set using {@link #authPrivateKey()}. + * + * @param authPrivateKeyPath the OCI authentication key file path + * @return updated builder instance + * @see #authPrivateKeyPath() + */ + public BUILDER authPrivateKeyPath(String authPrivateKeyPath) { + Objects.requireNonNull(authPrivateKeyPath); + this.authPrivateKeyPath = authPrivateKeyPath; + return identity(); + } + /** + * The OCI authentication private key. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. Alternatively, this property + * can be set using either {@link #authKeyFile()} residing in the {@code user.home} directory, or using + * {@link #authPrivateKeyPath()}. + * + * @param authPrivateKey the OCI authentication private key + * @return updated builder instance + * @see #authPrivateKey() + */ + BUILDER authPrivateKey(Optional authPrivateKey) { + Objects.requireNonNull(authPrivateKey); + this.authPrivateKey = authPrivateKey.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authPrivateKey() + */ + public BUILDER clearAuthPrivateKey() { + this.authPrivateKey = null; + return identity(); + } + /** + * The OCI authentication private key. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. Alternatively, this property + * can be set using either {@link #authKeyFile()} residing in the {@code user.home} directory, or using + * {@link #authPrivateKeyPath()}. + * + * @param authPrivateKey the OCI authentication private key + * @return updated builder instance + * @see #authPrivateKey() + */ + public BUILDER authPrivateKey(char[] authPrivateKey) { + Objects.requireNonNull(authPrivateKey); + this.authPrivateKey = authPrivateKey; + return identity(); + } + /** + * The OCI authentication private key. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. Alternatively, this property + * can be set using either {@link #authKeyFile()} residing in the {@code user.home} directory, or using + * {@link #authPrivateKeyPath()}. + * + * @param authPrivateKey the OCI authentication private key + * @return updated builder instance + * @see #authPrivateKey() + */ + public BUILDER authPrivateKey(String authPrivateKey) { + Objects.requireNonNull(authPrivateKey); + this.authPrivateKey = authPrivateKey.toCharArray(); + return identity(); + } + /** + * The OCI authentication passphrase. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPassphraseCharacters()}. + * + * @param authPassphrase the OCI authentication passphrase + * @return updated builder instance + * @see #authPassphrase() + */ + BUILDER authPassphrase(Optional authPassphrase) { + Objects.requireNonNull(authPassphrase); + this.authPassphrase = authPassphrase.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authPassphrase() + */ + public BUILDER clearAuthPassphrase() { + this.authPassphrase = null; + return identity(); + } + /** + * The OCI authentication passphrase. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPassphraseCharacters()}. + * + * @param authPassphrase the OCI authentication passphrase + * @return updated builder instance + * @see #authPassphrase() + */ + public BUILDER authPassphrase(char[] authPassphrase) { + Objects.requireNonNull(authPassphrase); + this.authPassphrase = authPassphrase; + return identity(); + } + /** + * The OCI authentication passphrase. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPassphraseCharacters()}. + * + * @param authPassphrase the OCI authentication passphrase + * @return updated builder instance + * @see #authPassphrase() + */ + public BUILDER authPassphrase(String authPassphrase) { + Objects.requireNonNull(authPassphrase); + this.authPassphrase = authPassphrase.toCharArray(); + return identity(); + } + /** + * The OCI region. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, either this property or {@link com.oracle.bmc.auth.RegionProvider} must be provide a value in order + * to set the {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getRegion()}. + * + * @param authRegion the OCI region + * @return updated builder instance + * @see #authRegion() + */ + BUILDER authRegion(Optional authRegion) { + Objects.requireNonNull(authRegion); + this.authRegion = authRegion.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authRegion() + */ + public BUILDER clearAuthRegion() { + this.authRegion = null; + return identity(); + } + /** + * The OCI region. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, either this property or {@link com.oracle.bmc.auth.RegionProvider} must be provide a value in order + * to set the {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getRegion()}. + * + * @param authRegion the OCI region + * @return updated builder instance + * @see #authRegion() + */ + public BUILDER authRegion(String authRegion) { + Objects.requireNonNull(authRegion); + this.authRegion = authRegion; + return identity(); + } + /** + * The OCI tenant id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getTenantId()}. + * + * @param authTenantId the OCI tenant id + * @return updated builder instance + * @see #authTenantId() + */ + BUILDER authTenantId(Optional authTenantId) { + Objects.requireNonNull(authTenantId); + this.authTenantId = authTenantId.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authTenantId() + */ + public BUILDER clearAuthTenantId() { + this.authTenantId = null; + return identity(); + } + /** + * The OCI tenant id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getTenantId()}. + * + * @param authTenantId the OCI tenant id + * @return updated builder instance + * @see #authTenantId() + */ + public BUILDER authTenantId(String authTenantId) { + Objects.requireNonNull(authTenantId); + this.authTenantId = authTenantId; + return identity(); + } + /** + * The OCI user id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getUserId()}. + * + * @param authUserId the OCI user id + * @return updated builder instance + * @see #authUserId() + */ + BUILDER authUserId(Optional authUserId) { + Objects.requireNonNull(authUserId); + this.authUserId = authUserId.orElse(null); + return identity(); + } + /** + * Clear existing value of this property. + * @return updated builder instance + * @see #authUserId() + */ + public BUILDER clearAuthUserId() { + this.authUserId = null; + return identity(); + } + /** + * The OCI user id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getUserId()}. + * + * @param authUserId the OCI user id + * @return updated builder instance + * @see #authUserId() + */ + public BUILDER authUserId(String authUserId) { + Objects.requireNonNull(authUserId); + this.authUserId = authUserId; + return identity(); + } + /** + * The OCI IMDS hostname. + *

+ * This configuration property is used to identify the metadata service url. + * + * @param imdsHostName the OCI IMDS hostname + * @return updated builder instance + * @see #imdsHostName() + */ + public BUILDER imdsHostName(String imdsHostName) { + Objects.requireNonNull(imdsHostName); + this.imdsHostName = imdsHostName; + return identity(); + } + /** + * The OCI IMDS connection timeout. This is used to auto-detect availability. + *

+ * This configuration property is used when attempting to connect to the metadata service. + * + * @param imdsTimeout the OCI IMDS connection timeout + * @return updated builder instance + * @see #imdsTimeout() + */ + public BUILDER imdsTimeout(java.time.Duration imdsTimeout) { + Objects.requireNonNull(imdsTimeout); + this.imdsTimeout = imdsTimeout; + return identity(); + } + /** + * The singular authentication strategy to apply. This will be preferred over {@link #authStrategies()} if both are + * present. + * + * @return the auth strategy + */ + public Optional authStrategy() { + return Optional.ofNullable(authStrategy); + } + /** + * The list of authentication strategies that will be attempted by + * {@link com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider} when one is + * called for. This is only used if {@link #authStrategy()} is not present. + * + *

    + *
  • {@code auto} - if present in the list, or if no value + * for this property exists, the behavior will be as if {@code + * config,config-file,instance-principals,resource-principal} + * were supplied instead.
  • + *
  • {@code config} - the + * {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code config-file} - the + * {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code instance-principals} - the + * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider} + * will be used.
  • + *
  • {@code resource-principal} - the + * {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider} + * will be used.
  • + *
+ *

+ * If there are many strategy descriptors supplied, the + * first one that is deemed to be available or suitable will + * be used and all others will be ignored. + * + * @return the auth strategies + */ + public java.util.List authStrategies() { + return authStrategies; + } + /** + * The OCI configuration profile path. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property must also be present and then the + * {@linkplain com.oracle.bmc.ConfigFileReader#parse(String)} + * method will be passed this value. It is expected to be passed with a + * valid OCI configuration file path. + * + * @return the config path + */ + public Optional configPath() { + return Optional.ofNullable(configPath); + } + /** + * The OCI configuration/auth profile name. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property may also be optionally provided in order to override the default + * {@value #DEFAULT_PROFILE_NAME}. + * + * @return the config profile + */ + public Optional configProfile() { + return Optional.ofNullable(configProfile); + } + /** + * The OCI authentication fingerprint. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the API signing key's fingerprint. + * See {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getFingerprint()} for more details. + * + * @return the auth fingerprint + */ + public Optional authFingerprint() { + return Optional.ofNullable(authFingerprint); + } + /** + * The OCI authentication key file. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file must exist in the + * {@code user.home} directory. Alternatively, this property can be set using either {@link #authPrivateKey()} or + * using {@link #authPrivateKeyPath()}. + * + * @return the auth key file + */ + public String authKeyFile() { + return authKeyFile; + } + /** + * The OCI authentication key file path. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file path is + * an alternative for using {@link #authKeyFile()} where the file must exist in the {@code user.home} directory. + * Alternatively, this property can be set using {@link #authPrivateKey()}. + * + * @return the auth private key path + */ + public Optional authPrivateKeyPath() { + return Optional.ofNullable(authPrivateKeyPath); + } + /** + * The OCI authentication private key. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. Alternatively, this property + * can be set using either {@link #authKeyFile()} residing in the {@code user.home} directory, or using + * {@link #authPrivateKeyPath()}. + * + * @return the auth private key + */ + public Optional authPrivateKey() { + return Optional.ofNullable(authPrivateKey); + } + /** + * The OCI authentication passphrase. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPassphraseCharacters()}. + * + * @return the auth passphrase + */ + public Optional authPassphrase() { + return Optional.ofNullable(authPassphrase); + } + /** + * The OCI region. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, either this property or {@link com.oracle.bmc.auth.RegionProvider} must be provide a value in order + * to set the {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getRegion()}. + * + * @return the auth region + */ + public Optional authRegion() { + return Optional.ofNullable(authRegion); + } + /** + * The OCI tenant id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getTenantId()}. + * + * @return the auth tenant id + */ + public Optional authTenantId() { + return Optional.ofNullable(authTenantId); + } + /** + * The OCI user id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getUserId()}. + * + * @return the auth user id + */ + public Optional authUserId() { + return Optional.ofNullable(authUserId); + } + /** + * The OCI IMDS hostname. + *

+ * This configuration property is used to identify the metadata service url. + * + * @return the imds host name + */ + public String imdsHostName() { + return imdsHostName; + } + /** + * The OCI IMDS connection timeout. This is used to auto-detect availability. + *

+ * This configuration property is used when attempting to connect to the metadata service. + * + * @return the imds timeout + */ + public java.time.Duration imdsTimeout() { + return imdsTimeout; + } + /** + * If this instance was configured, this would be the config instance used. + * + * @return config node used to configure this builder, or empty if not configured + */ + public Optional config() { + return Optional.ofNullable(config); + } + @Override + public String toString() { + return "OciConfigBuilder{" + + "authStrategy=" + authStrategy + "," + + "authStrategies=" + authStrategies + "," + + "configPath=" + configPath + "," + + "configProfile=" + configProfile + "," + + "authFingerprint=" + authFingerprint + "," + + "authKeyFile=" + authKeyFile + "," + + "authPrivateKeyPath=" + authPrivateKeyPath + "," + + "authPrivateKey=" + (authPrivateKey == null ? "null" : "****") + "," + + "authPassphrase=" + (authPassphrase == null ? "null" : "****") + "," + + "authRegion=" + authRegion + "," + + "authTenantId=" + authTenantId + "," + + "authUserId=" + authUserId + "," + + "imdsHostName=" + imdsHostName + "," + + "imdsTimeout=" + imdsTimeout + + "}"; + } + /** + * Generated implementation of the prototype, can be extended by descendant prototype implementations. + */ + protected static class OciConfigImpl implements OciConfig { + private final Optional authStrategy; + private final java.util.List authStrategies; + private final Optional configPath; + private final Optional configProfile; + private final Optional authFingerprint; + private final String authKeyFile; + private final Optional authPrivateKeyPath; + private final Optional authPrivateKey; + private final Optional authPassphrase; + private final Optional authRegion; + private final Optional authTenantId; + private final Optional authUserId; + private final String imdsHostName; + private final java.time.Duration imdsTimeout; + + /** + * Create an instance providing a builder. + * @param builder extending builder base of this prototype + */ + protected OciConfigImpl(BuilderBase builder) { + this.authStrategy = builder.authStrategy(); + this.authStrategies = java.util.List.copyOf(builder.authStrategies()); + this.configPath = builder.configPath(); + this.configProfile = builder.configProfile(); + this.authFingerprint = builder.authFingerprint(); + this.authKeyFile = builder.authKeyFile(); + this.authPrivateKeyPath = builder.authPrivateKeyPath(); + this.authPrivateKey = builder.authPrivateKey(); + this.authPassphrase = builder.authPassphrase(); + this.authRegion = builder.authRegion(); + this.authTenantId = builder.authTenantId(); + this.authUserId = builder.authUserId(); + this.imdsHostName = builder.imdsHostName(); + this.imdsTimeout = builder.imdsTimeout(); + } + + @Override + public Optional authStrategy() { + return authStrategy; + } + + @Override + public java.util.List authStrategies() { + return authStrategies; + } + + @Override + public Optional configPath() { + return configPath; + } + + @Override + public Optional configProfile() { + return configProfile; + } + + @Override + public Optional authFingerprint() { + return authFingerprint; + } + + @Override + public String authKeyFile() { + return authKeyFile; + } + + @Override + public Optional authPrivateKeyPath() { + return authPrivateKeyPath; + } + + @Override + public Optional authPrivateKey() { + return authPrivateKey; + } + + @Override + public Optional authPassphrase() { + return authPassphrase; + } + + @Override + public Optional authRegion() { + return authRegion; + } + + @Override + public Optional authTenantId() { + return authTenantId; + } + + @Override + public Optional authUserId() { + return authUserId; + } + + @Override + public String imdsHostName() { + return imdsHostName; + } + + @Override + public java.time.Duration imdsTimeout() { + return imdsTimeout; + } + @Override + public String toString() { + return "OciConfig{" + + "authStrategy=" + authStrategy + "," + + "authStrategies=" + authStrategies + "," + + "configPath=" + configPath + "," + + "configProfile=" + configProfile + "," + + "authFingerprint=" + authFingerprint + "," + + "authKeyFile=" + authKeyFile + "," + + "authPrivateKeyPath=" + authPrivateKeyPath + "," + + "authPrivateKey=" + (authPrivateKey.isPresent() ? "****" : "null") + "," + + "authPassphrase=" + (authPassphrase.isPresent() ? "****" : "null") + "," + + "authRegion=" + authRegion + "," + + "authTenantId=" + authTenantId + "," + + "authUserId=" + authUserId + "," + + "imdsHostName=" + imdsHostName + "," + + "imdsTimeout=" + imdsTimeout + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof OciConfig other)) { + return false; + } + return Objects.equals(authStrategy, other.authStrategy()) + && Objects.equals(authStrategies, other.authStrategies()) + && Objects.equals(configPath, other.configPath()) + && Objects.equals(configProfile, other.configProfile()) + && Objects.equals(authFingerprint, other.authFingerprint()) + && Objects.equals(authKeyFile, other.authKeyFile()) + && Objects.equals(authPrivateKeyPath, other.authPrivateKeyPath()) + && Objects.equals(authPrivateKey, other.authPrivateKey()) + && Objects.equals(authPassphrase, other.authPassphrase()) + && Objects.equals(authRegion, other.authRegion()) + && Objects.equals(authTenantId, other.authTenantId()) + && Objects.equals(authUserId, other.authUserId()) + && Objects.equals(imdsHostName, other.imdsHostName()) + && Objects.equals(imdsTimeout, other.imdsTimeout()); + } + + @Override + public int hashCode() { + return Objects.hash(authStrategy, authStrategies, configPath, configProfile, authFingerprint, authKeyFile, + authPrivateKeyPath, authPrivateKey, authPassphrase, authRegion, authTenantId, authUserId, + imdsHostName, imdsTimeout); + } + } + } + + /** + * Fluent API builder for {@link OciConfig}. + */ + class Builder extends BuilderBase implements io.helidon.common.Builder { + private Builder() { + } + + /** + * Builds the prototype. + * + * @return the prototype + */ + public OciConfig buildPrototype() { + preBuildPrototype(); + validatePrototype(); + return new OciConfigImpl(this); + } + + @Override + public OciConfig build() { + return buildPrototype(); + } + + } +} diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfigBlueprint.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfigBlueprint.java new file mode 100644 index 00000000000..f8fe1a6df99 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciConfigBlueprint.java @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; + +import io.helidon.config.metadata.ConfiguredOption; +import io.helidon.config.metadata.ConfiguredValue; + +import com.oracle.bmc.ConfigFileReader; + +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.ALL_STRATEGIES; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.VAL_AUTO; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.VAL_CONFIG; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.VAL_CONFIG_FILE; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.VAL_INSTANCE_PRINCIPALS; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.VAL_RESOURCE_PRINCIPAL; + +/** + * Configuration used by {@link OciAuthenticationDetailsProvider}. + *

+ * Access the global {@link OciConfig} using the {@link OciExtension#ociConfig()} method. + * The configuration for this is delivered via a special {@value OciExtension#DEFAULT_OCI_GLOBAL_CONFIG_FILE} file. Minimally, + * this configuration file should have a key named {@value OciAuthenticationDetailsProvider#KEY_AUTH_STRATEGY} or else a + * list of auth strategies having a key named {@value OciAuthenticationDetailsProvider#KEY_AUTH_STRATEGIES}. In the later, all + * of the named auth strategies will be checked in the order they were specified for availability in the runtime environment (see + * details below). Here is an example for what the configuration would look like when a single auth strategy is explicitly + * configured : + *

+ *     # oci.yaml
+ *     auth-strategy : "config"
+ * 
+ * And here is another example when the runtime should search true multi auth strategies in order to select the first one + * available in the runtime environment: + *
+ *     # oci.yaml
+ *     # if instance-principals are available then use it, going down the chain checking for availability, etc.
+ *     auth-strategies: "instance-principals, config-file, resource-principal, config"
+ * 
+ * + *

+ * Each configured {@link OciAuthenticationDetailsProvider#KEY_AUTH_STRATEGY} has varying constraints: + *

    + *
  • instance-principals - the JVM must be able to detect that it is running on a OCI compute node instance.
  • + *
  • resource-principal - the env variable {@value OciAuthenticationDetailsProvider#TAG_RESOURCE_PRINCIPAL_VERSION} is + * required to be set in the runtime environment.
  • + *
  • config-file - the {@code $HOME/.oci/config} is available on the file system. This configuration also allows for the + * optional key named {@code config-profile} to be used to override the file location in the runtime environment.
  • + *
  • config - this configuration allows for these additional values to be set: {@code auth-tenant-id}, + * {@code auth-user-id}, {@code auth-region}, {@code auth-fingerprint}, {@code auth-passphrase()}, and + * {@code auth-private-key}. Note that this configuration is only recommended in a development (i.e., non-production) + * environment since it relies on these additional security-sensitive values to be set. Note that these values cannot be + * sourced out of the Vault since this configuration source is primordial - the vault is not accessible here.
  • + *
+ * See {@link #authStrategies()} for additional details. + *

+ * The default value for {@link OciAuthenticationDetailsProvider#KEY_AUTH_STRATEGY} is set to {@code auto}, meaning that + * the authentication strategy will follow a search heuristic to determine the appropriate setting. When running in the OCI + * runtime environment (i.e., the JVM is running on a detectable OCI compute node instance) then {@code instance-principals} + * is used, with a final fallback set to be {@code config-file} (i.e., $HOME/.oci/config). + * + * @see OciExtension + */ +interface OciConfigBlueprint { + /** + * Config key of this config. + */ + String CONFIG_KEY = "oci"; + /** + * Primary hostname of metadata service. + */ + String IMDS_HOSTNAME = "169.254.169.254"; + /** + * Redefine the constant, as it is private in BMC. + */ + String DEFAULT_PROFILE_NAME = "DEFAULT"; + + /** + * The singular authentication strategy to apply. This will be preferred over {@link #authStrategies()} if both are + * present. + * + * @return the singular authentication strategy to be applied + */ + @ConfiguredOption(allowedValues = { + @ConfiguredValue(value = VAL_AUTO, description = "auto select first applicable"), + @ConfiguredValue(value = VAL_CONFIG, description = "simple authentication provider"), + @ConfiguredValue(value = VAL_CONFIG_FILE, description = "config file authentication provider"), + @ConfiguredValue(value = VAL_INSTANCE_PRINCIPALS, description = "instance principals authentication provider"), + @ConfiguredValue(value = VAL_RESOURCE_PRINCIPAL, description = "resource principals authentication provider"), + }) + Optional authStrategy(); + + /** + * The list of authentication strategies that will be attempted by + * {@link com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider} when one is + * called for. This is only used if {@link #authStrategy()} is not present. + * + *

    + *
  • {@code auto} - if present in the list, or if no value + * for this property exists.
  • + *
  • {@code config} - the + * {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code config-file} - the + * {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider} + * will be used, customized with other configuration + * properties described here.
  • + *
  • {@code instance-principals} - the + * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider} + * will be used.
  • + *
  • {@code resource-principal} - the + * {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider} + * will be used.
  • + *
+ *

+ * If there are more than one strategy descriptors defined, the + * first one that is deemed to be available/suitable will be used and all others will be ignored. + * + * @return the list of authentication strategies that will be applied, defaulting to {@code auto} + * @see io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.AuthStrategy + */ + @ConfiguredOption(allowedValues = { + @ConfiguredValue(value = VAL_AUTO, description = "auto select first applicable"), + @ConfiguredValue(value = VAL_CONFIG, description = "simple authentication provider"), + @ConfiguredValue(value = VAL_CONFIG_FILE, description = "config file authentication provider"), + @ConfiguredValue(value = VAL_INSTANCE_PRINCIPALS, description = "instance principals authentication provider"), + @ConfiguredValue(value = VAL_RESOURCE_PRINCIPAL, description = "resource principal authentication provider"), + }) + List authStrategies(); + + /** + * The OCI configuration profile path. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property must also be present and then the + * {@linkplain ConfigFileReader#parse(String)} + * method will be passed this value. It is expected to be passed with a + * valid OCI configuration file path. + * + * @return the OCI configuration profile path + */ + @ConfiguredOption(key = "config.path") + Optional configPath(); + + /** + * The OCI configuration/auth profile name. + *

+ * This configuration property has an effect only when {@code config-file} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #fileConfigIsPresent()}. + * When it is present, this property may also be optionally provided in order to override the default + * {@value #DEFAULT_PROFILE_NAME}. + * + * @return the optional OCI configuration/auth profile name + */ + @ConfiguredOption(value = DEFAULT_PROFILE_NAME, key = "config.profile") + Optional configProfile(); + + /** + * The OCI authentication fingerprint. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the API signing key's fingerprint. + * See {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getFingerprint()} for more details. + * + * @return the OCI authentication fingerprint + */ + @ConfiguredOption(key = "auth.fingerprint") + Optional authFingerprint(); + + /** + * The OCI authentication key file. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file must exist in the + * {@code user.home} directory. Alternatively, this property can be set using either {@link #authPrivateKey()} or + * using {@link #authPrivateKeyPath()}. + * + * @return the OCI authentication key file + */ + @ConfiguredOption(value = "oci_api_key.pem", key = "auth.keyFile") + String authKeyFile(); + + /** + * The OCI authentication key file path. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. This file path is + * an alternative for using {@link #authKeyFile()} where the file must exist in the {@code user.home} directory. + * Alternatively, this property can be set using {@link #authPrivateKey()}. + * + * @return the OCI authentication key file path + */ + @ConfiguredOption(key = "auth.private-key-path") + Optional authPrivateKeyPath(); + + /** + * The OCI authentication private key. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPrivateKey()}. Alternatively, this property + * can be set using either {@link #authKeyFile()} residing in the {@code user.home} directory, or using + * {@link #authPrivateKeyPath()}. + * + * @return the OCI authentication private key + */ + // See https://github.com/helidon-io/helidon/issues/6908 + @ConfiguredOption(key = "auth.private-key") +// @Prototype.Confidential + Optional authPrivateKey(); + + /** + * The OCI authentication passphrase. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPassphraseCharacters()}. + * + * @return the OCI authentication passphrase + */ + // See https://github.com/helidon-io/helidon/issues/6908 + @ConfiguredOption(key = "auth.passphrase") +// @Prototype.Confidential + Optional authPassphrase(); + + /** + * The OCI region. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, either this property or {@link com.oracle.bmc.auth.RegionProvider} must be provide a value in order + * to set the {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getRegion()}. + * + * @return the OCI region + */ + @ConfiguredOption(key = "auth.region") + Optional authRegion(); + + /** + * The OCI tenant id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. This is also known as {@link #simpleConfigIsPresent()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getTenantId()}. + * + * @return the OCI tenant id + */ + @ConfiguredOption(key = "auth.tenant-id") + Optional authTenantId(); + + /** + * The OCI user id. + *

+ * This configuration property has an effect only when {@code config} is, explicitly or implicitly, + * present in the value for the {@link #authStrategies()}. + * When it is present, this property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider#getUserId()}. + * + * @return the OCI user id + */ + @ConfiguredOption(key = "auth.user-id") + Optional authUserId(); + + /** + * The OCI IMDS hostname. + *

+ * This configuration property is used to identify the metadata service url. + * + * @return the OCI IMDS hostname + */ + @ConfiguredOption(value = IMDS_HOSTNAME, key = "imds.hostname") + String imdsHostName(); + + /** + * The OCI IMDS connection timeout. This is used to auto-detect availability. + *

+ * This configuration property is used when attempting to connect to the metadata service. + * + * @return the OCI IMDS connection timeout + */ + @ConfiguredOption(value = "PT0.1S", key = "imds.timeout.milliseconds") + Duration imdsTimeout(); + + /** + * The list of {@link io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.AuthStrategy} names + * (excluding {@link io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.AuthStrategy#AUTO}) that + * are potentially applicable for use. Here, "potentially applicable for use" means that it is set using the + * {@link #authStrategy()} attribute on this config bean. If not present then the fall-back looks to use the values + * explicitly or implicitly set by {@link #authStrategies()}. Note that the order of this list is important as it pertains + * to the search/strategy ordering. + * + * @return the list of potential auth strategies that are applicable + */ + default List potentialAuthStrategies() { + String authStrategy = authStrategy().orElse(null); + if (authStrategy != null + && !VAL_AUTO.equalsIgnoreCase(authStrategy) + && !authStrategy.isBlank()) { + if (!ALL_STRATEGIES.contains(authStrategy)) { + throw new IllegalStateException("Unknown auth strategy: " + authStrategy); + } + + return List.of(authStrategy); + } + + List result = new ArrayList<>(); + authStrategies().stream() + .map(String::trim) + .filter(Predicate.not(String::isBlank)) + .forEach(s -> { + if (!ALL_STRATEGIES.contains(s) && !VAL_AUTO.equals(s)) { + throw new IllegalStateException("Unknown auth strategy: " + s); + } + result.add(s); + }); + if (result.isEmpty() || result.contains(VAL_AUTO)) { + return ALL_STRATEGIES; + } + + return result; + } + + /** + * Determines whether sufficient configuration is present on this bean to be used for OCI's "file-based" authentication + * provider. This matches to the {@link io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.AuthStrategy#CONFIG_FILE}. + * + * @return true if there is sufficient attributes defined for file-based OCI authentication provider applicability + * @see OciAuthenticationDetailsProvider + * @see com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider + */ + default boolean fileConfigIsPresent() { + // the implementation will use ConfigFileAuthenticationDetailsProvider(ConfigFileReader.parseDefault()), so we are + // therefore relaxing the criteria here for matching, and instead just verifying that parseDefaults will "work" later + // as the fallback mechanism. + if ((configPath().isPresent() && !configProfile().get().isBlank()) + || (configProfile().isPresent() && configProfile().get().isBlank())) { + return true; + } + + try { + ConfigFileReader.ConfigFile ignoredCfgFile = ConfigFileReader.parseDefault(); + Objects.requireNonNull(ignoredCfgFile); + return true; + } catch (Exception e) { + OciAuthenticationDetailsProvider.LOGGER.log(System.Logger.Level.DEBUG, + "file config is not available: " + e.getMessage(), e); + return false; + } + } + + /** + * Determines whether sufficient configuration is present on this bean to be used for OCI's "simple" authentication provider. + * This matches to the {@link io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.AuthStrategy#CONFIG}. + * + * @return true if there is sufficient attributes defined for simple OCI authentication provider applicability + * @see OciAuthenticationDetailsProvider + */ + default boolean simpleConfigIsPresent() { + return authTenantId().isPresent() + && authUserId().isPresent() + && authPassphrase().isPresent() + && authFingerprint().isPresent() + // don't test region since it can alternatively come from the region provider + // && authRegion().isPresent() + && (authPrivateKey().isPresent() + || authPrivateKeyPath().isPresent()); + } + +} diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java new file mode 100644 index 00000000000..9ac0b23f646 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Supplier; + +import io.helidon.common.LazyValue; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; + +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.KEY_AUTH_STRATEGIES; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.KEY_AUTH_STRATEGY; +import static io.helidon.integrations.oci.sdk.runtime.OciAuthenticationDetailsProvider.select; +import static java.util.function.Predicate.not; + +/** + * This class enables configuration access for integration to the Oracle Cloud Infrastructure Java SDK. It is intended for + * non-Helidon MP, non-CDI usage scenarios. For usages that involve + * Helidon MP and CDI please refer to + * {@code io.helidon.integrations.oci.sdk.cdi.OciExtension} instead. This + * integration will follow the same terminology and usage pattern as specified + * for Helidon MP integration. + * + * @see Oracle Cloud Infrastructure Java SDK + */ +public final class OciExtension { + static final String DEFAULT_OCI_GLOBAL_CONFIG_FILE = "oci.yaml"; + static final System.Logger LOGGER = System.getLogger(OciExtension.class.getName()); + static final LazyValue DEFAULT_OCI_CONFIG_BEAN = LazyValue.create(() -> OciConfig.builder() + .authStrategies(Arrays.stream(OciAuthenticationDetailsProvider.AuthStrategy.values()) + .filter(not(it -> it == OciAuthenticationDetailsProvider.AuthStrategy.AUTO)) + .map(OciAuthenticationDetailsProvider.AuthStrategy::id) + .toList()) + .build()); + private static String overrideOciConfigFile; + private static volatile Supplier ociConfigSupplier; + + private OciExtension() { + } + + /** + * The configured authentication provider strategy type name. Note, however, that the authentication strategy returned may not + * necessarily be available. The configured authentication provider merely returns what is configured via + * {@value OciAuthenticationDetailsProvider#KEY_AUTH_STRATEGY} and/or + * {@value OciAuthenticationDetailsProvider#KEY_AUTH_STRATEGIES}. In order to additionally check if the provider is available, + * the {@code verifyIsAvailable} argument should be {@code true}. + * + * @param verifyIsAvailable flag to indicate whether the provider should be checked for availability + * @return the configured authentication type name + */ + public static Class + configuredAuthenticationDetailsProvider(boolean verifyIsAvailable) { + return select(ociConfig(), verifyIsAvailable).authStrategy().type(); + } + + /** + * Returns the global {@link OciConfig} bean that is currently defined in the bootstrap environment. + *

+ * The implementation will first look for an {@code oci.yaml} file, and if found will use that file to establish the global + * oci-specific bootstrap {@link io.helidon.config.spi.ConfigSource}. + *

+ * The final fallback mechanism will use an {@code auto} authentication strategy - see {@link OciConfigBlueprint} for details. + * + * @return the bootstrap oci config bean + * @see OciConfigBlueprint + * @see #ociConfigSupplier + */ + public static OciConfig ociConfig() { + Config config = configSupplier().get(); + if (isSufficientlyConfigured(config)) { + // we are good as-is + return OciConfig.create(config); + } + + // fallback + LOGGER.log(System.Logger.Level.DEBUG, "No bootstrap - using default oci config"); + return DEFAULT_OCI_CONFIG_BEAN.get(); + } + + /** + * The supplier for the globally configured OCI authentication provider. + * + * @return the supplier for the globally configured authentication provider + * @see #configSupplier() + */ + public static Supplier ociAuthenticationProvider() { + // note that in v4 will use service registry, but here in v3 no extensibility is offered + return () -> Objects.requireNonNull(new OciAuthenticationDetailsProvider().get()); + } + + /** + * The supplier for the raw config-backed by the OCI config source(s). + * + * @return the supplier for the raw config-backed by the OCI config source(s) + * @see #ociAuthenticationProvider() + * @see #configSupplier(Supplier) + */ + public static Supplier configSupplier() { + if (ociConfigSupplier == null) { + configSupplier(() -> { + // we do it this way to allow for any system and env vars to be used for the auth-strategy definition + // (not advertised in the javadoc) + String ociConfigFile = ociConfigFilename(); + return Config.create( + ConfigSources.classpath(ociConfigFile).optional(), + ConfigSources.file(ociConfigFile).optional()); + }); + } + + return ociConfigSupplier; + } + + /** + * Establishes the supplier for the raw config-backed by the OCI config source(s). + * + * @param configSupplier the config supplier + * @see #configSupplier() + */ + public static void configSupplier(Supplier configSupplier) { + ociConfigSupplier = configSupplier; + } + + /** + * Returns {@code true} if the given config is sufficiently configured in order to identity an OCI authentication strategy. + * If {@code false} then {@link OciAuthenticationDetailsProvider.AuthStrategy#AUTO} will be applied. + * + * @param config the config + * @return true if the given config can be used to identify an OCI authentication strategy + */ + static boolean isSufficientlyConfigured(Config config) { + return (config != null + && (config.get(KEY_AUTH_STRATEGY).exists() + || config.get(KEY_AUTH_STRATEGIES).exists())); + } + + // in support for testing a variant of oci.yaml + static void ociConfigFileName(String fileName) { + overrideOciConfigFile = fileName; + ociConfigSupplier = null; + } + + // in support for testing a variant of oci.yaml + static String ociConfigFilename() { + return (overrideOciConfigFile == null) ? DEFAULT_OCI_GLOBAL_CONFIG_FILE : overrideOciConfigFile; + } + +} diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/package-info.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/package-info.java new file mode 100644 index 00000000000..ebfc0a37202 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Helidon Runtime Integrations to support OCI SDK. + */ +package io.helidon.integrations.oci.sdk.runtime; diff --git a/integrations/oci/sdk/runtime/src/main/java/module-info.java b/integrations/oci/sdk/runtime/src/main/java/module-info.java new file mode 100644 index 00000000000..f17b3cea816 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/main/java/module-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Helidon Integrations to support OCI Runtime module. + */ +module io.helidon.integrations.oci.sdk.runtime { + requires static jakarta.inject; + requires static io.helidon.config.metadata; + + requires io.helidon.common; + requires transitive io.helidon.config; + requires oci.java.sdk.common; + + exports io.helidon.integrations.oci.sdk.runtime; +} diff --git a/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProviderTest.java b/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProviderTest.java new file mode 100644 index 00000000000..4faf90c0d35 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProviderTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.util.Objects; + +import io.helidon.config.Config; + +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class OciAuthenticationDetailsProviderTest { + + @BeforeEach + @AfterEach + void reset() { + OciExtension.ociConfigFileName(null); + } + + @Test + void testCanReadPath() { + MatcherAssert.assertThat(OciAuthenticationDetailsProvider.canReadPath("./target"), + is(true)); + MatcherAssert.assertThat(OciAuthenticationDetailsProvider.canReadPath("./~bogus~"), + is(false)); + } + + @Test + void testUserHomePrivateKeyPath() { + OciConfig ociConfig = Objects.requireNonNull(OciExtension.ociConfig()); + MatcherAssert.assertThat(OciAuthenticationDetailsProvider.userHomePrivateKeyPath(ociConfig), + endsWith("/.oci/oci_api_key.pem")); + + ociConfig = OciConfig.builder(ociConfig) + .configPath("/decoy/path") + .authKeyFile("key.pem") + .build(); + MatcherAssert.assertThat(OciAuthenticationDetailsProvider.userHomePrivateKeyPath(ociConfig), + endsWith("/.oci/key.pem")); + } + + @Test + void authStrategiesAvailability() { + Config config = OciExtensionTest.createTestConfig( + OciExtensionTest.ociAuthConfigStrategies(OciAuthenticationDetailsProvider.VAL_AUTO), + OciExtensionTest.ociAuthSimpleConfig("tenant", "user", "phrase", "fp", null, null, "region")) + .get(OciConfig.CONFIG_KEY); + OciConfig cfg = OciConfig.create(config); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.AUTO.isAvailable(cfg), + is(true)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.CONFIG.isAvailable(cfg), + is(false)); + // this code is dependent upon whether and OCI config-file is present - so leaving this commented out intentionally +// assertThat(OciAuthenticationDetailsProvider.AuthStrategy.CONFIG_FILE.isAvailable(cfg), +// is(true)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.INSTANCE_PRINCIPALS.isAvailable(cfg), + is(false)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.RESOURCE_PRINCIPAL.isAvailable(cfg), + is(false)); + + config = OciExtensionTest.createTestConfig( + OciExtensionTest.ociAuthConfigStrategies(OciAuthenticationDetailsProvider.VAL_AUTO), + OciExtensionTest.ociAuthConfigFile("./target", null), + OciExtensionTest.ociAuthSimpleConfig("tenant", "user", "phrase", "fp", "pk", "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.AUTO.isAvailable(cfg), + is(true)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.CONFIG.isAvailable(cfg), + is(true)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.CONFIG_FILE.isAvailable(cfg), + is(true)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.INSTANCE_PRINCIPALS.isAvailable(cfg), + is(false)); + assertThat(OciAuthenticationDetailsProvider.AuthStrategy.RESOURCE_PRINCIPAL.isAvailable(cfg), + is(false)); + } + + @Test + void selectionWhenNoConfigIsSet() { + Config config = Config.create(); + assertThat(OciExtension.isSufficientlyConfigured(config), + is(false)); + } + + @Test + void selectionWhenFileConfigIsSetWithAuto() { + Config config = OciExtensionTest.createTestConfig( + OciExtensionTest.ociAuthConfigStrategies(OciAuthenticationDetailsProvider.VAL_AUTO), + OciExtensionTest.ociAuthConfigFile("./target", "profile")) + .get("oci"); + assertThat(OciExtension.isSufficientlyConfigured(config), + is(true)); + } + + @Test + void selectionWhenSimpleConfigIsSetWithAuto() { + Config config = OciExtensionTest.createTestConfig( + OciExtensionTest.ociAuthConfigStrategies(OciAuthenticationDetailsProvider.VAL_AUTO), + OciExtensionTest.ociAuthSimpleConfig("tenant", "user", "passphrase", "fp", "privKey", null, "us-phoenix-1")) + .get("oci"); + assertThat(OciExtension.isSufficientlyConfigured(config), + is(true)); + + OciExtension.configSupplier(() -> config); + AbstractAuthenticationDetailsProvider authProvider = OciExtension.ociAuthenticationProvider().get(); + assertThat(authProvider.getClass(), + equalTo(SimpleAuthenticationDetailsProvider.class)); + SimpleAuthenticationDetailsProvider auth = (SimpleAuthenticationDetailsProvider) authProvider; + assertThat(auth.getTenantId(), + equalTo("tenant")); + assertThat(auth.getUserId(), + equalTo("user")); + assertThat(auth.getRegion(), + equalTo(Region.US_PHOENIX_1)); + assertThat(new String(auth.getPassphraseCharacters()), + equalTo("passphrase")); + } + +} diff --git a/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciAvailabilityTest.java b/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciAvailabilityTest.java new file mode 100644 index 00000000000..7e1a7f136a3 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciAvailabilityTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.util.Objects; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class OciAvailabilityTest { + + @Test + void isRunningOnOci() { + OciConfig ociConfigBean = Objects.requireNonNull(OciExtension.ociConfig()); + assertThat(OciAvailabilityDefault.runningOnOci(ociConfigBean), + is(false)); + } + +} diff --git a/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciExtensionTest.java b/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciExtensionTest.java new file mode 100644 index 00000000000..b87863e3d28 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/test/java/io/helidon/integrations/oci/sdk/runtime/OciExtensionTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.oci.sdk.runtime; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Supplier; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.config.MapConfigSource; + +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link OciExtension} and {@link OciConfig}. + */ +class OciExtensionTest { + + @AfterEach + void reset() { + OciExtension.ociConfigFileName(null); + } + + @Test + void ociConfig() { + assertThat(OciExtension.ociConfig(), + notNullValue()); + assertThat(OciExtension.ociConfig(), + equalTo(OciExtension.ociConfig())); + } + + @Test + void potentialAuthStrategies() { + Config config = createTestConfig(ociAuthConfigStrategies(null)) + .get(OciConfig.CONFIG_KEY); + OciConfig cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("instance-principals", "resource-principal", "config", "config-file")); + + config = createTestConfig(ociAuthConfigStrategies("auto")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("instance-principals", "resource-principal", "config", "config-file")); + + config = createTestConfig(ociAuthConfigStrategies(null, "instance-principals", "auto")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("instance-principals", "resource-principal", "config", "config-file")); + + config = createTestConfig(ociAuthConfigStrategies(null, "instance-principals", "resource-principal")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("instance-principals", "resource-principal")); + + config = createTestConfig(ociAuthConfigStrategies("config", "auto")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("config")); + + config = createTestConfig(ociAuthConfigStrategies("config", "config", "config-file")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("config")); + + config = createTestConfig(ociAuthConfigStrategies("auto", "config", "config-file")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("config", "config-file")); + + config = createTestConfig(ociAuthConfigStrategies("", "")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.potentialAuthStrategies(), + contains("instance-principals", "resource-principal", "config", "config-file")); + } + + @Test + void bogusAuthStrategyAttempted() { + Config config = createTestConfig(ociAuthConfigStrategies("bogus")) + .get(OciConfig.CONFIG_KEY); + OciConfig cfg = OciConfig.create(config); + IllegalStateException e = assertThrows(IllegalStateException.class, cfg::potentialAuthStrategies); + assertThat(e.getMessage(), + equalTo("Unknown auth strategy: bogus")); + + config = createTestConfig(ociAuthConfigStrategies(null, "config", "bogus")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + e = assertThrows(IllegalStateException.class, cfg::potentialAuthStrategies); + assertThat(e.getMessage(), + equalTo("Unknown auth strategy: bogus")); + } + + @Test + void fileConfigIsPresent() { + Config config = createTestConfig(ociAuthConfigFile("path", "profile")) + .get(OciConfig.CONFIG_KEY); + OciConfig cfg = OciConfig.create(config); + assertThat(cfg.fileConfigIsPresent(), + is(true)); + assertThat(cfg.configProfile().orElseThrow(), + equalTo("profile")); + + config = createTestConfig(ociAuthConfigFile("path", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.fileConfigIsPresent(), is(true)); + assertThat(cfg.configProfile().orElseThrow(), + equalTo("DEFAULT")); + + config = createTestConfig(ociAuthConfigFile("", "")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + // the ConfigFile type provider always works, since OCI SDK API assumes that too + assertThat(cfg.fileConfigIsPresent(), + is(true)); + + config = createTestConfig(ociAuthConfigFile(null, null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + // this will be true if there is a ~/.oci/config based configuration, false otherwise + // assertThat(cfg.fileConfigIsPresent(), + // is(true)); + } + + @Test + void simpleConfigIsPresent() { + Config config = createTestConfig(ociAuthSimpleConfig("tenant", "user", "phrase", "fp", "pk", "pkp", "region")) + .get(OciConfig.CONFIG_KEY); + OciConfig cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(true)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", "user", "phrase", "fp", "pk", "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(true)); + + config = createTestConfig(ociAuthSimpleConfig(null, "user", "phrase", "fp", "pk", "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(false)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", null, "phrase", "fp", "pk", "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(false)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", "user", null, "fp", "pk", "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(false)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", "user", "phrase", null, "pk", "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(false)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", "user", "phrase", "fp", null, "pkp", null)) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(true)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", "user", "phrase", "fp", "pk", null, "region")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(true)); + + config = createTestConfig(ociAuthSimpleConfig("tenant", "user", "phrase", "fp", null, null, "region")) + .get(OciConfig.CONFIG_KEY); + cfg = OciConfig.create(config); + assertThat(cfg.simpleConfigIsPresent(), + is(false)); + } + + @Test + void defaultOciConfigAttributes() { + assertThat(OciExtension.ociConfig().authKeyFile(), + equalTo("oci_api_key.pem")); + assertThat(OciExtension.ociConfig().imdsHostName(), + equalTo(OciConfig.IMDS_HOSTNAME)); + assertThat(OciExtension.ociConfig().imdsTimeout().toMillis(), + equalTo(100L)); + } + + @Test + void ociYamlConfigFile() { + // setup (tear down happens after each run) + OciExtension.ociConfigFileName("test-oci-resource-principal.yaml"); + + OciConfig ociConfig = OciExtension.ociConfig(); + assertThat(ociConfig.authStrategy(), + optionalValue(equalTo("resource-principal"))); + + // note that we can't actually instantiate these when there is no auth provider configured in the environment + IllegalArgumentException e = assertThrows(java.lang.IllegalArgumentException.class, + () -> OciExtension.ociAuthenticationProvider().get()); + assertThat(e.getMessage(), + equalTo(OciAuthenticationDetailsProvider.TAG_RESOURCE_PRINCIPAL_VERSION + " environment variable missing")); + assertThat(OciExtension.configuredAuthenticationDetailsProvider(false), + equalTo(ResourcePrincipalAuthenticationDetailsProvider.class)); + + OciExtension.ociConfigFileName("test-oci-config-file.yaml"); + Supplier authProvider = OciExtension.ociAuthenticationProvider(); + + try { + // in the case where there is actually an oci configuration in this environment + AbstractAuthenticationDetailsProvider auth = authProvider.get(); + assertThat(auth, + instanceOf(ConfigFileAuthenticationDetailsProvider.class)); + assertThat(OciExtension.configuredAuthenticationDetailsProvider(true), + equalTo(ConfigFileAuthenticationDetailsProvider.class)); + assertThat(OciExtension.configuredAuthenticationDetailsProvider(false), + equalTo(ConfigFileAuthenticationDetailsProvider.class)); + } catch (NoSuchElementException ispe) { + assertThat(ispe.getMessage(), + equalTo("No instances of com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider available for use. " + + "Verify your configuration named: oci")); + assertThat(OciExtension.configuredAuthenticationDetailsProvider(false), + equalTo(ConfigFileAuthenticationDetailsProvider.class)); + } + } + + @Test + void ociRawConfigShouldBeCached() { + assertSame(Objects.requireNonNull(OciExtension.configSupplier()), + OciExtension.configSupplier(), + "The oci configuration from the config source should be cached"); + } + + static Config createTestConfig(MapConfigSource.Builder... builders) { + return Config.builder(builders) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + } + + static MapConfigSource.Builder ociAuthConfigStrategies(String strategy, + String... strategies) { + Map map = new HashMap<>(); + if (strategy != null) { + map.put(OciConfig.CONFIG_KEY + ".auth-strategy", strategy); + } + if (strategies != null) { + map.put(OciConfig.CONFIG_KEY + ".auth-strategies", String.join(",", strategies)); + } + return ConfigSources.create(map, "config-oci-auth-strategies"); + } + + static MapConfigSource.Builder ociAuthConfigFile(String configPath, + String profile) { + Map map = new HashMap<>(); + if (configPath != null) { + map.put(OciConfig.CONFIG_KEY + ".config.path", configPath); + } + if (profile != null) { + map.put(OciConfig.CONFIG_KEY + ".config.profile", String.join(",", profile)); + } + return ConfigSources.create(map, "config-oci-auth-config"); + } + + static MapConfigSource.Builder ociAuthSimpleConfig(String tenantId, + String userId, + String passPhrase, + String fingerPrint, + String privateKey, + String privateKeyPath, + String region) { + Map map = new HashMap<>(); + if (tenantId != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.tenant-id", tenantId); + } + if (userId != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.user-id", userId); + } + if (passPhrase != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.passphrase", passPhrase); + } + if (fingerPrint != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.fingerprint", fingerPrint); + } + if (privateKey != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.private-key", privateKey); + } + if (privateKeyPath != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.private-key-path", privateKeyPath); + } + if (region != null) { + map.put(OciConfig.CONFIG_KEY + ".auth.region", region); + } + return ConfigSources.create(map, "config-oci-auth-simple"); + } + +} diff --git a/integrations/oci/sdk/runtime/src/test/resources/test-oci-config-file.yaml b/integrations/oci/sdk/runtime/src/test/resources/test-oci-config-file.yaml new file mode 100644 index 00000000000..c03f5d6e2c0 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/test/resources/test-oci-config-file.yaml @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# should resolve to "config-file" as the configured (by probably not available) auth strategy +auth-strategies: ["config-file", "resource-principal", "config", "instance-principals"] diff --git a/integrations/oci/sdk/runtime/src/test/resources/test-oci-resource-principal.yaml b/integrations/oci/sdk/runtime/src/test/resources/test-oci-resource-principal.yaml new file mode 100644 index 00000000000..dff6ca17bd7 --- /dev/null +++ b/integrations/oci/sdk/runtime/src/test/resources/test-oci-resource-principal.yaml @@ -0,0 +1,17 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +auth-strategy: "resource-principal"