PYTHON-4330 Add Kubernetes Support for OIDC (#1759)
This commit is contained in:
parent
a9caaf0d6a
commit
57fd616ace
@ -520,6 +520,18 @@ functions:
|
||||
args:
|
||||
- .evergreen/run-mongodb-oidc-test.sh
|
||||
|
||||
"run oidc k8s auth test":
|
||||
- command: subprocess.exec
|
||||
type: test
|
||||
params:
|
||||
binary: bash
|
||||
working_dir: src
|
||||
env:
|
||||
OIDC_ENV: k8s
|
||||
include_expansions_in_env: ["DRIVERS_TOOLS", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "K8S_VARIANT"]
|
||||
args:
|
||||
- ${PROJECT_DIRECTORY}/.evergreen/run-mongodb-oidc-remote-test.sh
|
||||
|
||||
"run aws auth test with aws credentials as environment variables":
|
||||
- command: shell.exec
|
||||
type: test
|
||||
@ -873,6 +885,32 @@ task_groups:
|
||||
tasks:
|
||||
- oidc-auth-test-gcp
|
||||
|
||||
- name: testk8soidc_task_group
|
||||
setup_group:
|
||||
- func: fetch source
|
||||
- func: prepare resources
|
||||
- func: fix absolute paths
|
||||
- func: make files executable
|
||||
- command: ec2.assume_role
|
||||
params:
|
||||
role_arn: ${aws_test_secrets_role}
|
||||
duration_seconds: 1800
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
binary: bash
|
||||
args:
|
||||
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh
|
||||
teardown_task:
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
binary: bash
|
||||
args:
|
||||
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh
|
||||
setup_group_can_fail_task: true
|
||||
setup_group_timeout_secs: 1800
|
||||
tasks:
|
||||
- oidc-auth-test-k8s
|
||||
|
||||
- name: testoidc_task_group
|
||||
setup_group:
|
||||
- func: fetch source
|
||||
@ -1548,40 +1586,41 @@ tasks:
|
||||
|
||||
- name: "oidc-auth-test-azure"
|
||||
commands:
|
||||
- command: shell.exec
|
||||
- command: subprocess.exec
|
||||
type: test
|
||||
params:
|
||||
shell: bash
|
||||
script: |-
|
||||
set -o errexit
|
||||
. src/.evergreen/scripts/env.sh
|
||||
cd src
|
||||
git add .
|
||||
git commit -m "add files"
|
||||
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-python-driver.tgz
|
||||
git archive -o $AZUREOIDC_DRIVERS_TAR_FILE HEAD
|
||||
export AZUREOIDC_TEST_CMD="OIDC_ENV=azure ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh
|
||||
binary: bash
|
||||
working_dir: src
|
||||
env:
|
||||
OIDC_ENV: azure
|
||||
include_expansions_in_env: ["DRIVERS_TOOLS"]
|
||||
args:
|
||||
- ${PROJECT_DIRECTORY}/.evergreen/run-mongodb-oidc-remote-test.sh
|
||||
|
||||
- name: "oidc-auth-test-gcp"
|
||||
commands:
|
||||
- command: shell.exec
|
||||
- command: subprocess.exec
|
||||
type: test
|
||||
params:
|
||||
shell: bash
|
||||
script: |-
|
||||
set -o errexit
|
||||
. src/.evergreen/scripts/env.sh
|
||||
cd src
|
||||
git add .
|
||||
git commit -m "add files"
|
||||
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-python-driver.tgz
|
||||
git archive -o $GCPOIDC_DRIVERS_TAR_FILE HEAD
|
||||
# Define the command to run on the VM.
|
||||
# Ensure that we source the environment file created for us, set up any other variables we need,
|
||||
# and then run our test suite on the vm.
|
||||
export GCPOIDC_TEST_CMD="OIDC_ENV=gcp ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh
|
||||
binary: bash
|
||||
working_dir: src
|
||||
env:
|
||||
OIDC_ENV: gcp
|
||||
include_expansions_in_env: ["DRIVERS_TOOLS"]
|
||||
args:
|
||||
- ${PROJECT_DIRECTORY}/.evergreen/run-mongodb-oidc-remote-test.sh
|
||||
|
||||
- name: "oidc-auth-test-k8s"
|
||||
commands:
|
||||
- func: "run oidc k8s auth test"
|
||||
vars:
|
||||
K8S_VARIANT: eks
|
||||
- func: "run oidc k8s auth test"
|
||||
vars:
|
||||
K8S_VARIANT: gke
|
||||
- func: "run oidc k8s auth test"
|
||||
vars:
|
||||
K8S_VARIANT: aks
|
||||
# }}}
|
||||
- name: "coverage-report"
|
||||
tags: ["coverage"]
|
||||
@ -1740,20 +1779,6 @@ buildvariants:
|
||||
tasks:
|
||||
- name: "coverage-report"
|
||||
|
||||
- name: testazureoidc-variant
|
||||
display_name: "OIDC Auth Azure"
|
||||
run_on: ubuntu2204-small
|
||||
tasks:
|
||||
- name: testazureoidc_task_group
|
||||
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
|
||||
|
||||
- name: testgcpoidc-variant
|
||||
display_name: "OIDC Auth GCP"
|
||||
run_on: ubuntu2204-small
|
||||
tasks:
|
||||
- name: testgcpoidc_task_group
|
||||
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
|
||||
|
||||
- name: testgcpkms-variant
|
||||
display_name: "GCP KMS"
|
||||
run_on:
|
||||
|
||||
@ -955,12 +955,15 @@ buildvariants:
|
||||
VERSION: "8.0"
|
||||
|
||||
# Oidc auth tests
|
||||
- name: oidc-auth-rhel8
|
||||
- name: oidc-auth-ubuntu-22
|
||||
tasks:
|
||||
- name: testoidc_task_group
|
||||
display_name: OIDC Auth RHEL8
|
||||
- name: testazureoidc_task_group
|
||||
- name: testgcpoidc_task_group
|
||||
- name: testk8soidc_task_group
|
||||
display_name: OIDC Auth Ubuntu-22
|
||||
run_on:
|
||||
- rhel87-small
|
||||
- ubuntu2204-small
|
||||
batchtime: 20160
|
||||
- name: oidc-auth-macos
|
||||
tasks:
|
||||
|
||||
60
.evergreen/run-mongodb-oidc-remote-test.sh
Executable file
60
.evergreen/run-mongodb-oidc-remote-test.sh
Executable file
@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
set +x # Disable debug trace
|
||||
set -eu
|
||||
|
||||
echo "Running MONGODB-OIDC remote tests"
|
||||
|
||||
OIDC_ENV=${OIDC_ENV:-"test"}
|
||||
|
||||
# Make sure DRIVERS_TOOLS is set.
|
||||
if [ -z "$DRIVERS_TOOLS" ]; then
|
||||
echo "Must specify DRIVERS_TOOLS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set up the remote files to test.
|
||||
git add .
|
||||
git commit -m "add files" || true
|
||||
export TEST_TAR_FILE=/tmp/mongo-python-driver.tgz
|
||||
git archive -o $TEST_TAR_FILE HEAD
|
||||
|
||||
pushd $DRIVERS_TOOLS
|
||||
|
||||
if [ $OIDC_ENV == "test" ]; then
|
||||
echo "Test OIDC environment does not support remote test!"
|
||||
exit 1
|
||||
|
||||
elif [ $OIDC_ENV == "azure" ]; then
|
||||
export AZUREOIDC_DRIVERS_TAR_FILE=$TEST_TAR_FILE
|
||||
export AZUREOIDC_TEST_CMD="OIDC_ENV=azure ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
bash ./.evergreen/auth_oidc/azure/run-driver-test.sh
|
||||
|
||||
elif [ $OIDC_ENV == "gcp" ]; then
|
||||
export GCPOIDC_DRIVERS_TAR_FILE=$TEST_TAR_FILE
|
||||
export GCPOIDC_TEST_CMD="OIDC_ENV=gcp ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
bash ./.evergreen/auth_oidc/gcp/run-driver-test.sh
|
||||
|
||||
elif [ $OIDC_ENV == "k8s" ]; then
|
||||
# Make sure K8S_VARIANT is set.
|
||||
if [ -z "$K8S_VARIANT" ]; then
|
||||
echo "Must specify K8S_VARIANT"
|
||||
popd
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash ./.evergreen/auth_oidc/k8s/setup-pod.sh
|
||||
bash ./.evergreen/auth_oidc/k8s/run-self-test.sh
|
||||
export K8S_DRIVERS_TAR_FILE=$TEST_TAR_FILE
|
||||
export K8S_TEST_CMD="OIDC_ENV=k8s ./.evergreen/run-mongodb-oidc-test.sh"
|
||||
source ./.evergreen/auth_oidc/k8s/secrets-export.sh # for MONGODB_URI
|
||||
bash ./.evergreen/auth_oidc/k8s/run-driver-test.sh
|
||||
bash ./.evergreen/auth_oidc/k8s/teardown-pod.sh
|
||||
|
||||
else
|
||||
echo "Unrecognized OIDC_ENV $OIDC_ENV"
|
||||
pod
|
||||
exit 1
|
||||
fi
|
||||
|
||||
popd
|
||||
@ -21,6 +21,9 @@ elif [ $OIDC_ENV == "azure" ]; then
|
||||
elif [ $OIDC_ENV == "gcp" ]; then
|
||||
source ./secrets-export.sh
|
||||
|
||||
elif [ $OIDC_ENV == "k8s" ]; then
|
||||
echo "Running oidc on k8s"
|
||||
|
||||
else
|
||||
echo "Unrecognized OIDC_ENV $OIDC_ENV"
|
||||
exit 1
|
||||
|
||||
@ -615,10 +615,14 @@ def create_serverless_variants():
|
||||
|
||||
def create_oidc_auth_variants():
|
||||
variants = []
|
||||
for host in ["rhel8", "macos", "win64"]:
|
||||
other_tasks = ["testazureoidc_task_group", "testgcpoidc_task_group", "testk8soidc_task_group"]
|
||||
for host in ["ubuntu22", "macos", "win64"]:
|
||||
tasks = ["testoidc_task_group"]
|
||||
if host == "ubuntu22":
|
||||
tasks += other_tasks
|
||||
variants.append(
|
||||
create_variant(
|
||||
["testoidc_task_group"],
|
||||
tasks,
|
||||
get_display_name("OIDC Auth", host),
|
||||
host=host,
|
||||
batchtime=BATCHTIME_WEEK * 2,
|
||||
|
||||
@ -116,3 +116,17 @@ class _OIDCGCPCallback(OIDCCallback):
|
||||
def fetch(self, context: OIDCCallbackContext) -> OIDCCallbackResult:
|
||||
resp = _get_gcp_response(self.token_resource, context.timeout_seconds)
|
||||
return OIDCCallbackResult(access_token=resp["access_token"])
|
||||
|
||||
|
||||
class _OIDCK8SCallback(OIDCCallback):
|
||||
def fetch(self, context: OIDCCallbackContext) -> OIDCCallbackResult:
|
||||
return OIDCCallbackResult(access_token=_get_k8s_token())
|
||||
|
||||
|
||||
def _get_k8s_token() -> str:
|
||||
fname = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
for key in ["AZURE_FEDERATED_TOKEN_FILE", "AWS_WEB_IDENTITY_TOKEN_FILE"]:
|
||||
if key in os.environ:
|
||||
fname = os.environ[key]
|
||||
with open(fname) as fid:
|
||||
return fid.read()
|
||||
|
||||
@ -26,6 +26,7 @@ from bson import Binary
|
||||
from pymongo.auth_oidc_shared import (
|
||||
_OIDCAzureCallback,
|
||||
_OIDCGCPCallback,
|
||||
_OIDCK8SCallback,
|
||||
_OIDCProperties,
|
||||
_OIDCTestCallback,
|
||||
)
|
||||
@ -192,6 +193,9 @@ def _build_credentials_tuple(
|
||||
"GCP provider for MONGODB-OIDC requires a TOKEN_RESOURCE auth mechanism property"
|
||||
)
|
||||
callback = _OIDCGCPCallback(token_resource)
|
||||
elif environ == "k8s":
|
||||
passwd = None
|
||||
callback = _OIDCK8SCallback()
|
||||
else:
|
||||
raise ConfigurationError(f"unrecognized ENVIRONMENT for MONGODB-OIDC: {environ}")
|
||||
else:
|
||||
|
||||
@ -626,6 +626,26 @@
|
||||
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp",
|
||||
"valid": false,
|
||||
"credential": null
|
||||
},
|
||||
{
|
||||
"description": "should recognise the mechanism with k8s provider (MONGODB-OIDC)",
|
||||
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s",
|
||||
"valid": true,
|
||||
"credential": {
|
||||
"username": null,
|
||||
"password": null,
|
||||
"source": "$external",
|
||||
"mechanism": "MONGODB-OIDC",
|
||||
"mechanism_properties": {
|
||||
"ENVIRONMENT": "k8s"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "should throw an error for a username and password with k8s provider (MONGODB-OIDC)",
|
||||
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s",
|
||||
"valid": false,
|
||||
"credential": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ from bson import SON
|
||||
from pymongo import MongoClient
|
||||
from pymongo._azure_helpers import _get_azure_response
|
||||
from pymongo._gcp_helpers import _get_gcp_response
|
||||
from pymongo.auth_oidc_shared import _get_k8s_token
|
||||
from pymongo.cursor_shared import CursorType
|
||||
from pymongo.errors import AutoReconnect, ConfigurationError, OperationFailure
|
||||
from pymongo.hello import HelloCompat
|
||||
@ -84,6 +85,10 @@ class OIDCTestBase(PyMongoTestCase):
|
||||
opts = parse_uri(self.uri_single)["options"]
|
||||
token_aud = opts["authmechanismproperties"]["TOKEN_RESOURCE"]
|
||||
return _get_gcp_response(token_aud, username)["access_token"]
|
||||
elif ENVIRON == "k8s":
|
||||
return _get_k8s_token()
|
||||
else:
|
||||
raise ValueError(f"Unknown ENVIRON: {ENVIRON}")
|
||||
|
||||
@contextmanager
|
||||
def fail_point(self, command_args):
|
||||
@ -758,7 +763,9 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
kwargs["retryReads"] = False
|
||||
if not len(args):
|
||||
args = [self.uri_single]
|
||||
return MongoClient(*args, authmechanismproperties=props, **kwargs)
|
||||
client = MongoClient(*args, authmechanismproperties=props, **kwargs)
|
||||
self.addCleanup(client.close)
|
||||
return client
|
||||
|
||||
def test_1_1_callback_is_called_during_reauthentication(self):
|
||||
# Create a ``MongoClient`` configured with a custom OIDC callback that
|
||||
@ -768,8 +775,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
client.test.test.find_one()
|
||||
# Assert that the callback was called 1 time.
|
||||
self.assertEqual(self.request_called, 1)
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_1_2_callback_is_called_once_for_multiple_connections(self):
|
||||
# Create a ``MongoClient`` configured with a custom OIDC callback that
|
||||
@ -790,8 +795,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
thread.join()
|
||||
# Assert that the callback was called 1 time.
|
||||
self.assertEqual(self.request_called, 1)
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_2_1_valid_callback_inputs(self):
|
||||
# Create a MongoClient configured with an OIDC callback that validates its inputs and returns a valid access token.
|
||||
@ -800,8 +803,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
client.test.test.find_one()
|
||||
# Assert that the OIDC callback was called with the appropriate inputs, including the timeout parameter if possible. Ensure that there are no unexpected fields.
|
||||
self.assertEqual(self.request_called, 1)
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_2_2_oidc_callback_returns_null(self):
|
||||
# Create a MongoClient configured with an OIDC callback that returns null.
|
||||
@ -813,8 +814,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Perform a find operation that fails.
|
||||
with self.assertRaises(ValueError):
|
||||
client.test.test.find_one()
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_2_3_oidc_callback_returns_missing_data(self):
|
||||
# Create a MongoClient configured with an OIDC callback that returns data not conforming to the OIDCCredential with missing fields.
|
||||
@ -829,8 +828,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Perform a find operation that fails.
|
||||
with self.assertRaises(ValueError):
|
||||
client.test.test.find_one()
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_2_4_invalid_client_configuration_with_callback(self):
|
||||
# Create a MongoClient configured with an OIDC callback and auth mechanism property ENVIRONMENT:test.
|
||||
@ -870,8 +867,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
client.test.test.find_one()
|
||||
# Verify that the callback was called 1 time.
|
||||
self.assertEqual(self.request_called, 1)
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_3_2_authentication_failures_without_cached_tokens_returns_an_error(self):
|
||||
# Create a MongoClient configured with retryReads=false and an OIDC callback that always returns invalid access tokens.
|
||||
@ -889,8 +884,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
client.test.test.find_one()
|
||||
# Verify that the callback was called 1 time.
|
||||
self.assertEqual(callback.count, 1)
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_3_3_unexpected_error_code_does_not_clear_cache(self):
|
||||
# Create a ``MongoClient`` with a human callback that returns a valid token
|
||||
@ -916,9 +909,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Assert that the callback has been called once.
|
||||
self.assertEqual(self.request_called, 1)
|
||||
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_4_1_reauthentication_succeds(self):
|
||||
# Create a ``MongoClient`` configured with a custom OIDC callback that
|
||||
# implements the provider logic.
|
||||
@ -938,9 +928,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# handshake, and again during reauthentication).
|
||||
self.assertEqual(self.request_called, 2)
|
||||
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_4_2_read_commands_fail_if_reauthentication_fails(self):
|
||||
# Create a ``MongoClient`` whose OIDC callback returns one good token and then
|
||||
# bad tokens after the first call.
|
||||
@ -977,9 +964,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Verify that the callback was called 2 times.
|
||||
self.assertEqual(callback.count, 2)
|
||||
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_4_3_write_commands_fail_if_reauthentication_fails(self):
|
||||
# Create a ``MongoClient`` whose OIDC callback returns one good token and then
|
||||
# bad token after the first call.
|
||||
@ -1016,12 +1000,9 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Verify that the callback was called 2 times.
|
||||
self.assertEqual(callback.count, 2)
|
||||
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_4_4_speculative_authentication_should_be_ignored_on_reauthentication(self):
|
||||
# Create an OIDC configured client that can listen for `SaslStart` commands.
|
||||
listener = OvertCommandListener()
|
||||
listener = EventListener()
|
||||
client = self.create_client(event_listeners=[listener])
|
||||
|
||||
# Preload the *Client Cache* with a valid access token to enforce Speculative Authentication.
|
||||
@ -1061,9 +1042,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Assert there were `SaslStart` commands executed.
|
||||
assert any(event.command_name.lower() == "saslstart" for event in listener.started_events)
|
||||
|
||||
# Close the client.
|
||||
client.close()
|
||||
|
||||
def test_5_1_azure_with_no_username(self):
|
||||
if ENVIRON != "azure":
|
||||
raise unittest.SkipTest("Test is only supported on Azure")
|
||||
@ -1073,7 +1051,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
props = dict(TOKEN_RESOURCE=resource, ENVIRONMENT="azure")
|
||||
client = self.create_client(authMechanismProperties=props)
|
||||
client.test.test.find_one()
|
||||
client.close()
|
||||
|
||||
def test_5_2_azure_with_bad_username(self):
|
||||
if ENVIRON != "azure":
|
||||
@ -1086,7 +1063,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
client = self.create_client(username="bad", authmechanismproperties=props)
|
||||
with self.assertRaises(ValueError):
|
||||
client.test.test.find_one()
|
||||
client.close()
|
||||
|
||||
def test_speculative_auth_success(self):
|
||||
client1 = self.create_client()
|
||||
@ -1108,10 +1084,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
# Perform a find operation.
|
||||
client2.test.test.find_one()
|
||||
|
||||
# Close the clients.
|
||||
client2.close()
|
||||
client1.close()
|
||||
|
||||
def test_reauthentication_succeeds_multiple_connections(self):
|
||||
client1 = self.create_client()
|
||||
client2 = self.create_client()
|
||||
@ -1151,8 +1123,6 @@ class TestAuthOIDCMachine(OIDCTestBase):
|
||||
client2.test.test.find_one()
|
||||
|
||||
self.assertEqual(self.request_called, 3)
|
||||
client1.close()
|
||||
client2.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -137,6 +137,8 @@ elif OIDC_ENV == "gcp":
|
||||
"ENVIRONMENT": "gcp",
|
||||
"TOKEN_RESOURCE": os.environ["GCPOIDC_AUDIENCE"],
|
||||
}
|
||||
elif OIDC_ENV == "k8s":
|
||||
PLACEHOLDER_MAP["/uriOptions/authMechanismProperties"] = {"ENVIRONMENT": "k8s"}
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user