Skip to content

Commit 19ddf09

Browse files
authored
Add the ability to have activation config secrets in a separate file (#95)
* Add --secrets flag to dt-sdk run command * Add secrets.json to default .gitignore file * Add secrets.json to template, adjust activation.json template, update docs * Make secrets DebugClient argument optional, revert tests, add secrets.json to docs file structure chart
1 parent 372be51 commit 19ddf09

File tree

9 files changed

+63
-6
lines changed

9 files changed

+63
-6
lines changed

docs/guides/extension_structure.rst

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The basic bare-bone structure of an extension is as follows:
1414
├── my_extension
1515
│ ├── __init__.py
1616
│ └── __main__.py
17+
├── secrets.json
1718
└── setup.py
1819
1920
This structure can be generated by running the :doc:`/cli/create` command
@@ -195,7 +196,8 @@ Activation Config
195196

196197
This is a config file that can be used for local testing, when extension instance
197198
is launched using the :doc:`/cli/run` command. It must contain all of the mandatory
198-
fields as defined in the `Activation Schema`_.
199+
fields as defined in the `Activation Schema`_. This file also supports using secrets
200+
from the ``secrets.json`` file.
199201

200202
When extension is deployed to the Dynatrace environment and monitoring configuration
201203
is created, then the environment provides an individual activation config for each
@@ -215,12 +217,32 @@ Here is what a sample activation config looks like:
215217
{
216218
"url": "http://127.0.0.1:15672",
217219
"user": "guest",
218-
"password": "guest"
220+
"password": "{{myPassword}}"
219221
}
220222
]
221223
}
222224
}
223225
226+
227+
Secrets
228+
-----------------
229+
230+
``secrets.json``
231+
232+
This file defines the secrets that can be used in ``activation.json``. Specify
233+
each secret as key-value pair and then use ``{{key}}`` in the activation config
234+
file to use the corresponding secret's value. Only secrets of type ``string``
235+
are supported.
236+
237+
This file is by default in ``.gitignore``.
238+
239+
.. code:: json
240+
241+
{
242+
"myPassword": "secretPassword"
243+
}
244+
245+
224246
Setup.py
225247
--------
226248

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ structure:
7878
├── my_extension
7979
│ ├── __init__.py
8080
│ └── __main__.py
81+
├── secrets.json
8182
└── setup.py
8283
8384
.. admonition:: What do these files mean?

dynatrace_extension/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# SPDX-License-Identifier: MIT
44

55

6-
__version__ = "1.2.14"
6+
__version__ = "1.3.0"

dynatrace_extension/cli/create/extension_template/.gitignore.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,6 @@ cython_debug/
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161+
162+
# Secrets
163+
secrets.json

dynatrace_extension/cli/create/extension_template/activation.json.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{
99
"url": "http://127.0.0.1:15672",
1010
"user": "guest",
11-
"password": "guest"
11+
"password": "{{myPassword}}"
1212
}
1313
]
1414
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"myPassword": "secretPassword"
3+
}

dynatrace_extension/cli/main.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def version():
4040
def run(
4141
extension_dir: Path = typer.Argument("."),
4242
activation_config: str = "activation.json",
43+
secrets: str = "secrets.json",
4344
fast_check: bool = typer.Option(False, "--fastcheck"),
4445
local_ingest: bool = typer.Option(False, "--local-ingest"),
4546
local_ingest_port: int = typer.Option(14499, "--local-ingest-port"),
@@ -50,6 +51,7 @@ def run(
5051
5152
:param extension_dir: The directory of the extension, by default this is the current directory
5253
:param activation_config: The activation config file, by default this is activation.json
54+
:param secrets: The secrets file to be used to enrich the activation config, by default this is secrets.json
5355
:param fast_check: If true, run a fastcheck and exits
5456
:param local_ingest: If true, send metrics to localhost:14499 on top of printing them
5557
:param local_ingest_port: The port to send metrics to, by default this is 14499
@@ -59,7 +61,15 @@ def run(
5961
# This parses the yaml, which validates it before running
6062
extension_yaml = ExtensionYaml(extension_dir / "extension/extension.yaml")
6163
try:
62-
command = [sys.executable, "-m", extension_yaml.python.runtime.module, "--activationconfig", activation_config]
64+
command = [
65+
sys.executable,
66+
"-m",
67+
extension_yaml.python.runtime.module,
68+
"--activationconfig",
69+
activation_config,
70+
"--secrets",
71+
secrets,
72+
]
6373
if fast_check:
6474
command.append("--fastcheck")
6575
if local_ingest:

dynatrace_extension/sdk/communication.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,14 +347,24 @@ def __init__(
347347
activation_config_path: str,
348348
extension_config_path: str,
349349
logger: logging.Logger,
350+
secrets_path: str = "secrets.json",
350351
local_ingest: bool = False,
351352
local_ingest_port: int = 14499,
352353
print_metrics: bool = True,
353354
):
355+
356+
self.secrets = {}
357+
if secrets_path and Path(secrets_path).exists():
358+
with open(secrets_path) as f:
359+
self.secrets = json.load(f)
360+
354361
self.activation_config = {}
355362
if activation_config_path and Path(activation_config_path).exists():
356363
with open(activation_config_path) as f:
357-
self.activation_config = json.load(f)
364+
raw_activation_config = f.read()
365+
self.activation_config = json.loads(
366+
self.replace_secrets_in_activation_config(self.secrets, raw_activation_config)
367+
)
358368

359369
self.extension_config = ""
360370
if not extension_config_path:
@@ -453,6 +463,12 @@ def send_sfm_metrics(self, mint_lines: list[str]) -> MintResponse:
453463
def get_cluster_time_diff(self) -> int:
454464
return 0
455465

466+
def replace_secrets_in_activation_config(self, secrets: dict, activation_config_string: str) -> str:
467+
for secret_name, secret_value in secrets.items():
468+
activation_config_string = activation_config_string.replace(f"{{{{{secret_name}}}}}", str(secret_value))
469+
470+
return activation_config_string
471+
456472

457473
def divide_into_batches(
458474
items: Sequence[dict | str], max_size_bytes: int, join_with: str | None = None

dynatrace_extension/sdk/extension.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ def _parse_args(self):
715715
# Debug parameters, these are used when running the extension locally
716716
parser.add_argument("--extensionconfig", required=False, default=None)
717717
parser.add_argument("--activationconfig", required=False, default="activation.json")
718+
parser.add_argument("--secrets", required=False, default="secrets.json")
718719
parser.add_argument("--no-print-metrics", required=False, action="store_true")
719720

720721
args, unknown = parser.parse_known_args()
@@ -727,6 +728,7 @@ def _parse_args(self):
727728
activation_config_path=args.activationconfig,
728729
extension_config_path=args.extensionconfig,
729730
logger=api_logger,
731+
secrets_path=args.secrets,
730732
local_ingest=args.local_ingest,
731733
local_ingest_port=args.local_ingest_port,
732734
print_metrics=print_metrics,

0 commit comments

Comments
 (0)