Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/pyinfra/api/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from __future__ import annotations

from functools import wraps
from inspect import signature
from inspect import Parameter, signature
from io import StringIO
from types import FunctionType
from typing import TYPE_CHECKING, Any, cast
Expand Down Expand Up @@ -300,7 +300,7 @@ def decorated_func(*args: P.args, **kwargs: P.kwargs) -> OperationMeta:

# Attach normal args, if we're auto-naming this operation
if add_args:
op_meta = attach_args(op_meta, args, kwargs)
op_meta = attach_args(op_meta, func, args, kwargs)

# Check if we're actually running the operation on this host
# Run once and we've already added meta for this op? Stop here.
Expand Down Expand Up @@ -488,13 +488,21 @@ def _get_arg_value(arg):
return arg


def attach_args(op_meta, args, kwargs):
def attach_args(op_meta, func, args, kwargs):
for arg in args:
if arg not in op_meta.args:
op_meta.args.append(str(_get_arg_value(arg)))

# Attach keyword args
named_parameters = {
key
for key, parameter in signature(func).parameters.items()
if parameter.kind is not Parameter.VAR_KEYWORD
}
for key, value in kwargs.items():
if key not in named_parameters:
continue

arg = "=".join((str(key), str(_get_arg_value(value))))
if arg not in op_meta.args:
op_meta.args.append(arg)
Expand Down
24 changes: 24 additions & 0 deletions tests/test_api/test_api_operations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import defaultdict
from io import StringIO
from os import path
from unittest import TestCase
from unittest.mock import mock_open, patch
Expand Down Expand Up @@ -118,6 +119,29 @@ def test_op(self):

disconnect_all(state)

def test_template_op_auto_name_does_not_include_data_kwargs(self):
inventory = make_inventory()
state = State(inventory, Config())
state.current_stage = StateStage.Prepare
connect_all(state)

add_op(
state,
files.template,
StringIO("password={{ password }}"),
"/etc/config.toml",
password="!CLEARTEXTPASSWORD",
)

[op_hash] = state.get_op_order()
op_args = state.op_meta[op_hash].args

assert any(arg.startswith("StringIO(hash=") for arg in op_args)
assert "/etc/config.toml" in op_args
assert "password=!CLEARTEXTPASSWORD" not in op_args

disconnect_all(state)

@patch("pyinfra.api.util.open", mock_open(read_data="test!"), create=True)
@patch("pyinfra.operations.files.os.path.isfile", lambda *args, **kwargs: True)
def test_file_upload_op(self):
Expand Down
Loading