Merge 3006.x into 3007.x

This commit is contained in:
Pedro Algarvio 2024-03-14 13:06:00 +00:00
commit f7570047bd
No known key found for this signature in database
GPG key ID: BB36BF6584A298FF
54 changed files with 1466 additions and 299 deletions

View file

@ -1,7 +1,7 @@
### What does this PR do?
### What issues does this PR fix or reference?
Fixes:
Fixes
### Previous Behavior
Remove this section if not relevant

1
changelog/61807.fixed.md Normal file
View file

@ -0,0 +1 @@
pkg.refresh_db on Windows now honors saltenv

1
changelog/63667.fixed.md Normal file
View file

@ -0,0 +1 @@
Fix user and group management on Windows to handle the Everyone group

2
changelog/63848.fixed.md Normal file
View file

@ -0,0 +1,2 @@
Fixes an issue in pkg.refresh_db on Windows where new package definition
files were not being picked up on the first run

1
changelog/64933.fixed.md Normal file
View file

@ -0,0 +1 @@
Display a proper error when pki commands fail in the win_pki module

7
changelog/65611.fixed.md Normal file
View file

@ -0,0 +1,7 @@
When using s3fs, if files are deleted from the bucket, they were not deleted in
the master or minion local cache, which could lead to unexpected file copies or
even state applications. This change makes the local cache consistent with the
remote bucket by deleting files locally that are deleted from the bucket.
**NOTE** this could lead to **breakage** on your affected systems if it was
inadvertently depending on previously deleted files.

2
changelog/66049.fixed.md Normal file
View file

@ -0,0 +1,2 @@
Fixed an issue with file.directory state where paths would be modified in test
mode if backupname is used.

1
changelog/66705.fixed.md Normal file
View file

@ -0,0 +1 @@
backport the fix from #66164 to fix #65703. use OrderedDict to fix bad indexing.

View file

@ -397,16 +397,17 @@ winrepo_source_dir
:conf_minion:`winrepo_source_dir` (str)
The location of the .sls files on the Salt file server. This allows for using
different environments. Default is ``salt://win/repo-ng/``\.
The location of the .sls files on the Salt file server. Default is
``salt://win/repo-ng/``.
.. warning::
If the default for ``winrepo_dir_ng`` is changed, this setting may need to
be changed on each minion. The default setting for ``winrepo_dir_ng`` is
``/srv/salt/win/repo-ng``\. If that were changed to
``/srv/salt/new/repo-ng``\, then the ``winrepo_source_dir`` would need to be
If the default for ``winrepo_dir_ng`` is changed, then this setting will
also need to be changed on each minion. The default setting for
``winrepo_dir_ng`` is ``/srv/salt/win/repo-ng``. If that were changed to
``/srv/salt/new/repo-ng`` then the ``winrepo_source_dir`` would need to be
changed to ``salt://new/repo-ng``
.. _masterless-minion-config:
Masterless Minion Configuration
@ -430,7 +431,7 @@ winrepo_dir
This setting is maintained for backwards compatibility with legacy minions. It
points to the location in the ``file_roots`` where the winrepo files are kept.
The default is: ``C:\salt\srv\salt\win\repo``
The default is: ``C:\ProgramData\Salt Project\Salt\srv\salt\win\repo``
winrepo_dir_ng
--------------
@ -438,7 +439,7 @@ winrepo_dir_ng
:conf_minion:`winrepo_dir_ng` (str)
The location in the ``file_roots`` where the winrepo files are kept. The default
is ``C:\salt\srv\salt\win\repo-ng``\.
is ``C:\ProgramData\Salt Project\Salt\srv\salt\win\repo-ng``.
.. warning::
You can change the location of the winrepo directory. However, it must
@ -483,6 +484,137 @@ default is a list containing a single URL:
.. _usage:
Sample Configurations
*********************
Masterless
==========
The configs in this section are for working with winrepo on a Windows minion
using ``salt-call --local``.
Default Configuration
---------------------
This is the default configuration if nothing is configured in the minion config.
The config is shown here for clarity. These are the defaults:
.. code-block:: yaml
file_roots:
base:
- C:\ProgramData\Salt Project\Salt\srv\salt
winrepo_source_dir: 'salt://win/repo-ng'
winrepo_dir_ng: C:\ProgramData\Salt Project\Salt\srv\salt\win\repo-ng
The :mod:`winrepo.update_git_repos <salt.modules.winrepo.update_git_repos>`
command will clone the repository to ``win\repo-ng`` on the file_roots.
Multiple Salt Environments
--------------------------
This starts to get a little tricky. The winrepo repository doesn't
get cloned to each environment when you run
:mod:`winrepo.update_git_repos <salt.runners.winrepo.update_git_repos>`, so to
make this work, all environments share the same winrepo. Applying states using
the ``saltenv`` option will find the state files in the appropriate environment,
but the package definition files will always be pulled from the same location.
Therefore, you have to put the same winrepo location in each saltenv. Here's how
this would look:
.. code-block:: yaml
file_roots:
base:
- C:\ProgramData\Salt Project\Salt\srv\salt\base
- C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
test:
- C:\ProgramData\Salt Project\Salt\srv\salt\test
- C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
winrepo_source_dir: 'salt://salt-winrepo-ng'
winrepo_dir_ng: C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
winrepo_dir: C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
When you run
:mod:`winrepo.update_git_repos <salt.runners.winrepo.update_git_repos>` the
Git repository will be cloned to the location specified in the
``winrepo_dir_ng`` setting. I specified the ``winrepo_dir`` setting just so
everything gets cloned to the same place. The directory that gets cloned is
named ``salt-winrepo-ng`` so you specify that in the ``winrepo_source_dir``
setting.
The ``winrepo`` directory should only contain the package definition files. You
wouldn't want to place any states in the ``winrepo`` directory as they will be
available to both environments.
Master
======
When working in a Master/Minion environment you have to split up some of the
config settings between the master and the minion. Here are some sample configs
for winrepo in a Master/Minion environment.
Default Configuration
---------------------
This is the default configuration if nothing is configured. The config is shown
here for clarity. These are the defaults on the master:
.. code-block:: yaml
file_roots:
base:
- /srv/salt
winrepo_dir_ng: /srv/salt/win/repo-ng
This is the default in the minion config:
.. code-block:: yaml
winrepo_source_dir: 'salt://win/repo-ng'
The :mod:`winrepo.update_git_repos <salt.runners.winrepo.update_git_repos>`
command will clone the repository to ``win\repo-ng`` on the file_roots.
Multiple Salt Environments
--------------------------
To set up multiple saltenvs using a Master/Minion configuration set the
following in the master config:
.. code-block:: yaml
file_roots:
base:
- /srv/salt/base
- /srv/salt/winrepo
test:
- /srv/salt/test
- /srv/salt/winrepo
winrepo_dir_ng: /srv/salt/winrepo
winrepo_dir: /srv/salt/winrepo
Use the winrepo runner to set up the winrepo repository on the master.
.. code-block:: bash
salt-run winrepo.update_git_repos
The winrepo will be cloned to ``/srv/salt/winrepo`` under a directory named
``salt-winrepo-ng``.
Set the following on the minion config so the minion knows where to find the
package definition files in the file_roots:
.. code-block:: yaml
winrepo_source_dir: 'salt://salt-winrepo-ng'
The same stipulations apply in a Master/Minion configuration as they do in a
Masterless configuration
Usage
*****

View file

@ -5,7 +5,7 @@ pytest >= 7.2.0
pytest-salt-factories >= 1.0.0rc29
pytest-helpers-namespace >= 2019.1.8
pytest-subtests
pytest-timeout
pytest-timeout >= 2.3.1
pytest-httpserver
pytest-custom-exit-code >= 0.3
flaky

View file

@ -45,6 +45,7 @@ vcert; sys_platform != 'win32'
virtualenv>=20.3.0
watchdog>=0.9.0
xmldiff>=2.4
textfsm
# Available template libraries that can be used
genshi>=0.7.3
cheetah3>=3.2.2

View file

@ -126,14 +126,14 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.10/darwin.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -299,7 +299,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -375,9 +375,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -477,7 +477,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -489,6 +489,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -125,14 +125,14 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.10/freebsd.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -303,7 +303,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -379,9 +379,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -482,7 +482,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -494,6 +494,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -141,14 +141,14 @@ exceptiongroup==1.1.1
# pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.10/linux.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -329,7 +329,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -413,9 +413,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -539,7 +539,7 @@ slack-bolt==1.18.0
# via -r requirements/static/ci/linux.in
slack-sdk==3.21.3
# via slack-bolt
smmap==5.0.0
smmap==5.0.1
# via gitdb
sniffio==1.3.0
# via
@ -556,6 +556,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -124,13 +124,15 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.10/windows.txt
# aiohttp
# aiosignal
future==1.0.0
# via textfsm
genshi==0.7.7
# via -r requirements/static/ci/common.in
geomet==0.2.1.post1
@ -256,7 +258,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -332,9 +334,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -434,6 +436,7 @@ six==1.15.0
# python-dateutil
# pyvmomi
# pywinrm
# textfsm
# websocket-client
smmap==5.0.1
# via gitdb
@ -445,6 +448,8 @@ tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/windows.txt
# portend
textfsm==1.1.3
# via -r requirements/static/ci/common.in
timelib==0.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/windows.txt

View file

@ -123,14 +123,14 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.11/darwin.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -296,7 +296,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -376,9 +376,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -478,7 +478,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -490,6 +490,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -122,14 +122,14 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.11/freebsd.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -300,7 +300,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -380,9 +380,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -484,7 +484,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -496,6 +496,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -136,14 +136,14 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.11/linux.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -324,7 +324,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -412,9 +412,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -539,7 +539,7 @@ slack-bolt==1.18.0
# via -r requirements/static/ci/linux.in
slack-sdk==3.21.3
# via slack-bolt
smmap==5.0.0
smmap==5.0.1
# via gitdb
sniffio==1.3.0
# via
@ -556,6 +556,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -121,13 +121,15 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.11/windows.txt
# aiohttp
# aiosignal
future==1.0.0
# via textfsm
genshi==0.7.7
# via -r requirements/static/ci/common.in
geomet==0.2.1.post1
@ -253,7 +255,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -333,9 +335,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -435,6 +437,7 @@ six==1.15.0
# python-dateutil
# pyvmomi
# pywinrm
# textfsm
# websocket-client
smmap==5.0.1
# via gitdb
@ -446,6 +449,8 @@ tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.11/windows.txt
# portend
textfsm==1.1.3
# via -r requirements/static/ci/common.in
timelib==0.3.0
# via
# -c requirements/static/ci/../pkg/py3.11/windows.txt

View file

@ -170,7 +170,7 @@ filelock==3.13.1
# via
# -c requirements/static/ci/py3.12/linux.txt
# virtualenv
flaky==3.7.0
flaky==3.8.1
# via
# -c requirements/static/ci/py3.12/linux.txt
# -r requirements/pytest.txt
@ -180,7 +180,7 @@ frozenlist==1.4.1
# -c requirements/static/ci/py3.12/linux.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# -c requirements/static/ci/py3.12/linux.txt
# napalm
@ -416,7 +416,7 @@ platformdirs==4.0.0
# via
# -c requirements/static/ci/py3.12/linux.txt
# virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via
# -c requirements/static/ci/py3.12/linux.txt
# pytest
@ -543,11 +543,11 @@ pytest-system-statistics==1.0.2
# via
# -c requirements/static/ci/py3.12/linux.txt
# pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via
# -c requirements/static/ci/py3.12/linux.txt
# -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -c requirements/static/ci/py3.12/linux.txt
# -r requirements/pytest.txt
@ -690,7 +690,7 @@ smbprotocol==1.10.1
# via
# -r requirements/static/ci/cloud.in
# pypsexec
smmap==5.0.0
smmap==5.0.1
# via
# -c requirements/static/ci/py3.12/linux.txt
# gitdb
@ -710,6 +710,7 @@ tempora==5.3.0
textfsm==1.1.3
# via
# -c requirements/static/ci/py3.12/linux.txt
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -123,14 +123,14 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.12/darwin.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -296,7 +296,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -376,9 +376,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -478,7 +478,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -490,6 +490,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -122,14 +122,14 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.12/freebsd.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -300,7 +300,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -380,9 +380,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -484,7 +484,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -496,6 +496,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -192,7 +192,7 @@ frozenlist==1.4.1
# -c requirements/static/ci/py3.12/linux.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# -c requirements/static/ci/py3.12/linux.txt
# napalm
@ -691,7 +691,7 @@ slack-sdk==3.21.3
# via
# -c requirements/static/ci/py3.12/linux.txt
# slack-bolt
smmap==5.0.0
smmap==5.0.1
# via
# -c requirements/static/ci/py3.12/linux.txt
# gitdb
@ -717,6 +717,7 @@ tempora==5.3.0
textfsm==1.1.3
# via
# -c requirements/static/ci/py3.12/linux.txt
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -136,14 +136,14 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.12/linux.txt
# aiohttp
# aiosignal
future==0.18.3
future==1.0.0
# via
# napalm
# textfsm
@ -324,7 +324,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -412,9 +412,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==1.4.2
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -539,7 +539,7 @@ slack-bolt==1.18.0
# via -r requirements/static/ci/linux.in
slack-sdk==3.21.3
# via slack-bolt
smmap==5.0.0
smmap==5.0.1
# via gitdb
sniffio==1.3.0
# via
@ -556,6 +556,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -121,13 +121,15 @@ etcd3-py==0.1.6
# via -r requirements/static/ci/common.in
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.12/windows.txt
# aiohttp
# aiosignal
future==1.0.0
# via textfsm
genshi==0.7.7
# via -r requirements/static/ci/common.in
geomet==0.2.1.post1
@ -253,7 +255,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -333,9 +335,9 @@ pytest-subtests==0.4.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -435,6 +437,7 @@ six==1.15.0
# python-dateutil
# pyvmomi
# pywinrm
# textfsm
# websocket-client
smmap==5.0.1
# via gitdb
@ -446,6 +449,8 @@ tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.12/windows.txt
# portend
textfsm==1.1.3
# via -r requirements/static/ci/common.in
timelib==0.3.0
# via
# -c requirements/static/ci/../pkg/py3.12/windows.txt

View file

@ -125,7 +125,7 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
@ -307,7 +307,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -383,9 +383,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -486,7 +486,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -498,6 +498,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -136,7 +136,7 @@ exceptiongroup==1.1.1
# pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
@ -326,7 +326,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -410,9 +410,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -533,7 +533,7 @@ slack-bolt==1.18.0
# via -r requirements/static/ci/linux.in
slack-sdk==3.21.3
# via slack-bolt
smmap==5.0.0
smmap==5.0.1
# via gitdb
sniffio==1.3.0
# via
@ -550,6 +550,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -124,13 +124,15 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.8/windows.txt
# aiohttp
# aiosignal
future==1.0.0
# via textfsm
genshi==0.7.7
# via -r requirements/static/ci/common.in
geomet==0.2.1.post1
@ -260,7 +262,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -336,9 +338,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -439,6 +441,7 @@ six==1.15.0
# python-dateutil
# pyvmomi
# pywinrm
# textfsm
# websocket-client
smmap==5.0.1
# via gitdb
@ -450,6 +453,8 @@ tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.8/windows.txt
# portend
textfsm==1.1.3
# via -r requirements/static/ci/common.in
timelib==0.3.0
# via
# -c requirements/static/ci/../pkg/py3.8/windows.txt

View file

@ -126,7 +126,7 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
@ -299,7 +299,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -375,9 +375,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -477,7 +477,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -489,6 +489,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -125,7 +125,7 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
@ -303,7 +303,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -379,9 +379,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -482,7 +482,7 @@ six==1.16.0
# transitions
# vcert
# websocket-client
smmap==5.0.0
smmap==5.0.1
# via gitdb
sqlparse==0.4.4
# via -r requirements/static/ci/common.in
@ -494,6 +494,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -136,7 +136,7 @@ exceptiongroup==1.1.1
# pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
@ -322,7 +322,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -406,9 +406,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -529,7 +529,7 @@ slack-bolt==1.18.0
# via -r requirements/static/ci/linux.in
slack-sdk==3.21.3
# via slack-bolt
smmap==5.0.0
smmap==5.0.1
# via gitdb
sniffio==1.3.0
# via
@ -546,6 +546,7 @@ tempora==5.3.0
# portend
textfsm==1.1.3
# via
# -r requirements/static/ci/common.in
# napalm
# netmiko
# ntc-templates

View file

@ -124,13 +124,15 @@ exceptiongroup==1.1.1
# via pytest
filelock==3.13.1
# via virtualenv
flaky==3.7.0
flaky==3.8.1
# via -r requirements/pytest.txt
frozenlist==1.4.1
# via
# -c requirements/static/ci/../pkg/py3.9/windows.txt
# aiohttp
# aiosignal
future==1.0.0
# via textfsm
genshi==0.7.7
# via -r requirements/static/ci/common.in
geomet==0.2.1.post1
@ -256,7 +258,7 @@ pathspec==0.11.1
# via yamllint
platformdirs==4.0.0
# via virtualenv
pluggy==1.0.0
pluggy==1.4.0
# via pytest
portend==3.1.0
# via
@ -332,9 +334,9 @@ pytest-subtests==0.11.0
# via -r requirements/pytest.txt
pytest-system-statistics==1.0.2
# via pytest-salt-factories
pytest-timeout==2.1.0
pytest-timeout==2.3.1
# via -r requirements/pytest.txt
pytest==7.3.2
pytest==8.1.1
# via
# -r requirements/pytest.txt
# pytest-custom-exit-code
@ -435,6 +437,7 @@ six==1.15.0
# python-dateutil
# pyvmomi
# pywinrm
# textfsm
# websocket-client
smmap==5.0.1
# via gitdb
@ -446,6 +449,8 @@ tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.9/windows.txt
# portend
textfsm==1.1.3
# via -r requirements/static/ci/common.in
timelib==0.3.0
# via
# -c requirements/static/ci/../pkg/py3.9/windows.txt

View file

@ -135,6 +135,7 @@ def update():
cached_file_path = _get_cached_file_name(
bucket, saltenv, file_path
)
log.debug("%s - %s : %s", bucket, saltenv, file_path)
# load the file from S3 if it's not in the cache or it's old
@ -356,6 +357,7 @@ def _init():
# check mtime of the buckets files cache
metadata = None
try:
if os.path.getmtime(cache_file) > exp:
metadata = _read_buckets_cache_file(cache_file)
@ -366,6 +368,8 @@ def _init():
# bucket files cache expired or does not exist
metadata = _refresh_buckets_cache_file(cache_file)
_prune_deleted_files(metadata)
return metadata
@ -374,7 +378,6 @@ def _get_cache_dir():
Return the path to the s3cache dir
"""
# Or is that making too many assumptions?
return os.path.join(__opts__["cachedir"], "s3cache")
@ -383,26 +386,15 @@ def _get_cached_file_name(bucket_name, saltenv, path):
Return the cached file name for a bucket path file
"""
file_path = os.path.join(_get_cache_dir(), saltenv, bucket_name, path)
# make sure bucket and saltenv directories exist
if not os.path.exists(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
return file_path
return os.path.join(_get_cache_dir(), saltenv, bucket_name, path)
def _get_buckets_cache_filename():
"""
Return the filename of the cache for bucket contents.
Create the path if it does not exist.
"""
cache_dir = _get_cache_dir()
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
return os.path.join(cache_dir, "buckets_files.cache")
return os.path.join(_get_cache_dir(), "buckets_files.cache")
def _refresh_buckets_cache_file(cache_file):
@ -423,6 +415,7 @@ def _refresh_buckets_cache_file(cache_file):
path_style,
https_enable,
) = _get_s3_key()
metadata = {}
# helper s3 query function
@ -571,10 +564,72 @@ def _refresh_buckets_cache_file(cache_file):
return metadata
def _prune_deleted_files(metadata):
cache_dir = _get_cache_dir()
cached_files = set()
roots = set()
if _is_env_per_bucket():
for env, env_data in metadata.items():
for bucket_meta in env_data:
for bucket, bucket_data in bucket_meta.items():
root = os.path.join(cache_dir, env, bucket)
if os.path.exists(root):
roots.add(root)
for meta in bucket_data:
path = meta["Key"]
cached_files.add(path)
else:
for env, env_data in metadata.items():
for bucket in _get_buckets():
root = os.path.join(cache_dir, bucket)
if os.path.exists(root):
roots.add(root)
for meta in env_data:
cached_files.add(meta["Key"])
if log.isEnabledFor(logging.DEBUG):
import pprint
log.debug("cached file list:\n%s", pprint.pformat(cached_files))
for root in roots:
for base, dirs, files in os.walk(root):
for file_name in files:
path = os.path.join(base, file_name)
relpath = os.path.relpath(path, root)
if relpath not in cached_files:
log.debug("File '%s' not found in cached file list", path)
log.info(
"File '%s' was deleted from bucket, deleting local copy",
relpath,
)
os.unlink(path)
dirname = os.path.dirname(path)
# delete empty dirs all the way up to the cache dir
while dirname != cache_dir and len(os.listdir(dirname)) == 0:
log.debug("Directory '%s' is now empty, removing", dirname)
os.rmdir(dirname)
dirname = os.path.dirname(dirname)
def _write_buckets_cache_file(metadata, cache_file):
"""
Write the contents of the buckets cache file
"""
cache_dir = _get_cache_dir()
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
if os.path.isfile(cache_file):
os.remove(cache_file)
@ -591,6 +646,10 @@ def _read_buckets_cache_file(cache_file):
log.debug("Reading buckets cache file")
if not os.path.exists(cache_file):
log.debug("Cache file does not exist")
return None
with salt.utils.files.fopen(cache_file, "rb") as fp_:
try:
data = pickle.load(fp_)
@ -698,6 +757,13 @@ def _get_file_from_s3(metadata, saltenv, bucket_name, path, cached_file_path):
Checks the local cache for the file, if it's old or missing go grab the
file from S3 and update the cache
"""
# make sure bucket and saltenv directories exist
target_dir = os.path.dirname(cached_file_path)
if not os.path.exists(target_dir):
os.makedirs(target_dir)
(
key,
keyid,

View file

@ -20,6 +20,7 @@ import re
import shutil
import tempfile
import time
from collections import OrderedDict
from urllib.error import HTTPError
from urllib.request import Request as _Request
from urllib.request import urlopen as _urlopen
@ -204,23 +205,24 @@ if not HAS_APT:
repo_line.append(self.type)
opts = _get_opts(self.line)
if self.architectures:
archs = ",".join(self.architectures)
opts["arch"]["full"] = f"arch={archs}"
if "arch" not in opts:
opts["arch"] = {}
opts["arch"]["full"] = f"arch={','.join(self.architectures)}"
opts["arch"]["value"] = self.architectures
if self.signedby:
if "signedby" not in opts:
opts["signedby"] = {}
opts["signedby"]["full"] = f"signed-by={self.signedby}"
opts["signedby"]["value"] = self.signedby
ordered_opts = [
opt_type for opt_type, opt in opts.items() if opt["full"] != ""
]
ordered_opts = []
for opt in opts.values():
if opt["full"] != "":
ordered_opts[opt["index"]] = opt["full"]
ordered_opts.append(opt["full"])
if ordered_opts:
repo_line.append("[{}]".format(" ".join(ordered_opts)))
repo_line.append(f"[{' '.join(ordered_opts)}]")
repo_line += [self.uri, self.dist, " ".join(self.comps)]
if self.comment:
@ -237,10 +239,12 @@ if not HAS_APT:
if repo_line[1].startswith("["):
repo_line = [x for x in (line.strip("[]") for line in repo_line) if x]
opts = _get_opts(self.line)
self.architectures.extend(opts["arch"]["value"])
self.signedby = opts["signedby"]["value"]
for opt in opts:
opt = opts[opt]["full"]
if "arch" in opts:
self.architectures.extend(opts["arch"]["value"])
if "signedby" in opts:
self.signedby = opts["signedby"]["value"]
for opt in opts.values():
opt = opt["full"]
if opt:
try:
repo_line.pop(repo_line.index(opt))
@ -1743,31 +1747,27 @@ def _get_opts(line):
Return all opts in [] for a repo line
"""
get_opts = re.search(r"\[(.*=.*)\]", line)
ret = {
"arch": {"full": "", "value": "", "index": 0},
"signedby": {"full": "", "value": "", "index": 0},
}
ret = OrderedDict()
if not get_opts:
return ret
opts = get_opts.group(0).strip("[]")
architectures = []
for idx, opt in enumerate(opts.split()):
for opt in opts.split():
if opt.startswith("arch"):
architectures.extend(opt.split("=", 1)[1].split(","))
ret["arch"] = {}
ret["arch"]["full"] = opt
ret["arch"]["value"] = architectures
ret["arch"]["index"] = idx
elif opt.startswith("signed-by"):
ret["signedby"] = {}
ret["signedby"]["full"] = opt
ret["signedby"]["value"] = opt.split("=", 1)[1]
ret["signedby"]["index"] = idx
else:
other_opt = opt.split("=", 1)[0]
ret[other_opt] = {}
ret[other_opt]["full"] = opt
ret[other_opt]["value"] = opt.split("=", 1)[1]
ret[other_opt]["index"] = idx
return ret
@ -1780,7 +1780,11 @@ def _split_repo_str(repo):
if not HAS_APT:
signedby = entry.signedby
else:
signedby = _get_opts(line=repo)["signedby"].get("value", "")
opts = _get_opts(line=repo)
if "signedby" in opts:
signedby = opts["signedby"].get("value", "")
else:
signedby = ""
if signedby:
# python3-apt does not support signedby. So if signedby
# is in the repo we have to check our code to see if the
@ -1948,7 +1952,12 @@ def list_repos(**kwargs):
if not HAS_APT:
signedby = source.signedby
else:
signedby = _get_opts(line=source.line)["signedby"].get("value", "")
opts = _get_opts(line=source.line)
if "signedby" in opts:
signedby = opts["signedby"].get("value", "")
else:
signedby = ""
repo = {}
repo["file"] = source.file
repo["comps"] = getattr(source, "comps", [])
@ -2968,7 +2977,11 @@ def mod_repo(repo, saltenv="base", aptkey=True, **kwargs):
if not HAS_APT:
signedby = mod_source.signedby
else:
signedby = _get_opts(repo)["signedby"].get("value", "")
opts = _get_opts(repo)
if "signedby" in opts:
signedby = opts["signedby"].get("value", "")
else:
signedby = ""
return {
repo: {
@ -3069,7 +3082,11 @@ def _expand_repo_def(os_name, os_codename=None, **kwargs):
signedby = source_entry.signedby
kwargs["signedby"] = signedby
else:
signedby = _get_opts(repo)["signedby"].get("value", "")
opts = _get_opts(repo)
if "signedby" in opts:
signedby = opts["signedby"].get("value", "")
else:
signedby = ""
_source_entry = source_list.add(
type=source_entry.type,

View file

@ -19,7 +19,7 @@ inside the renderer (Jinja, Mako, Genshi, etc.).
import logging
import os
from salt.utils.files import fopen
import salt.utils.files
try:
import textfsm
@ -188,11 +188,14 @@ def extract(template_path, raw_text=None, raw_text_file=None, saltenv="base"):
# Disabling pylint W8470 to nto complain about fopen.
# Unfortunately textFSM needs the file handle rather than the content...
# pylint: disable=W8470
tpl_file_handle = fopen(tpl_cached_path, "r")
# pylint: disable=W8470
log.debug(tpl_file_handle.read())
tpl_file_handle.seek(0) # move the object position back at the top of the file
fsm_handler = textfsm.TextFSM(tpl_file_handle)
with salt.utils.files.fopen(tpl_cached_path, "r") as tpl_file_handle:
# pylint: disable=W8470
tpl_file_data = tpl_file_handle.read()
log.debug(tpl_file_data)
tpl_file_handle.seek(
0
) # move the object position back at the top of the file
fsm_handler = textfsm.TextFSM(tpl_file_handle)
except textfsm.TextFSMTemplateError as tfte:
log.error("Unable to parse the TextFSM template", exc_info=True)
ret["comment"] = (

View file

@ -47,6 +47,7 @@ import time
import urllib.parse
from functools import cmp_to_key
import salt.fileserver
import salt.payload
import salt.syspaths
import salt.utils.args
@ -907,7 +908,7 @@ def refresh_db(**kwargs):
The database is stored in a serialized format located by default at the
following location:
``C:\salt\var\cache\salt\minion\files\base\win\repo-ng\winrepo.p``
``C:\ProgramData\Salt Project\Salt\var\cache\salt\minion\files\base\win\repo-ng\winrepo.p``
This module performs the following steps to generate the software metadata
database:
@ -915,7 +916,7 @@ def refresh_db(**kwargs):
- Fetch the package definition files (.sls) from `winrepo_source_dir`
(default `salt://win/repo-ng`) and cache them in
`<cachedir>\files\<saltenv>\<winrepo_source_dir>`
(default: ``C:\salt\var\cache\salt\minion\files\base\win\repo-ng``)
(default: ``C:\ProgramData\Salt Project\Salt\var\cache\salt\minion\files\base\win\repo-ng``)
- Call :py:func:`pkg.genrepo <salt.modules.win_pkg.genrepo>` to parse the
package definition files and generate the repository metadata database
file (`winrepo.p`)
@ -976,7 +977,7 @@ def refresh_db(**kwargs):
.. warning::
When calling this command from a state using `module.run` be sure to
pass `failhard: False`. Otherwise the state will report failure if it
pass `failhard: False`. Otherwise, the state will report failure if it
encounters a bad software definition file.
CLI Example:
@ -1020,6 +1021,11 @@ def refresh_db(**kwargs):
"Failed to clear one or more winrepo cache files", info={"failed": failed}
)
# Clear the cache so that newly copied package definitions will be picked up
fileserver = salt.fileserver.Fileserver(__opts__)
load = {"saltenv": saltenv, "fsbackend": None}
fileserver.clear_file_list_cache(load=load)
# Cache repo-ng locally
log.info("Fetching *.sls files from %s", repo_details.winrepo_source_dir)
try:
@ -1170,10 +1176,11 @@ def genrepo(**kwargs):
if name.endswith(".sls"):
total_files_processed += 1
_repo_process_pkg_sls(
os.path.join(root, name),
os.path.join(short_path, name),
ret,
successful_verbose,
filename=os.path.join(root, name),
short_path_name=os.path.join(short_path, name),
ret=ret,
successful_verbose=successful_verbose,
saltenv=saltenv,
)
with salt.utils.files.fopen(repo_details.winrepo_file, "wb") as repo_cache:
@ -1212,7 +1219,9 @@ def genrepo(**kwargs):
return results
def _repo_process_pkg_sls(filename, short_path_name, ret, successful_verbose):
def _repo_process_pkg_sls(
filename, short_path_name, ret, successful_verbose, saltenv="base"
):
renderers = salt.loader.render(__opts__, __salt__)
def _failed_compile(prefix_msg, error_msg):
@ -1227,6 +1236,7 @@ def _repo_process_pkg_sls(filename, short_path_name, ret, successful_verbose):
__opts__["renderer"],
__opts__.get("renderer_blacklist", ""),
__opts__.get("renderer_whitelist", ""),
saltenv=saltenv,
)
except SaltRenderError as exc:
return _failed_compile("Failed to compile", exc)
@ -2359,7 +2369,23 @@ def _get_name_map(saltenv="base"):
def get_package_info(name, saltenv="base"):
"""
Return package info. Returns empty map if package not available.
Get information about the package as found in the winrepo database
Args:
name (str): The name of the package
saltenv (str): The salt environment to use. Default is "base"
Returns:
dict: A dictionary of package info, empty if package not available
CLI Example:
.. code-block:: bash
salt '*' pkg.get_package_info chrome
"""
return _get_package_info(name=name, saltenv=saltenv)

View file

@ -23,7 +23,7 @@ import salt.utils.json
import salt.utils.platform
import salt.utils.powershell
import salt.utils.versions
from salt.exceptions import SaltInvocationError
from salt.exceptions import CommandExecutionError, SaltInvocationError
_DEFAULT_CONTEXT = "LocalMachine"
_DEFAULT_FORMAT = "cer"
@ -73,15 +73,19 @@ def _cmd_run(cmd, as_json=False):
"".join(cmd_full), shell="powershell", python_shell=True
)
if cmd_ret["retcode"] != 0:
_LOG.error("Unable to execute command: %s\nError: %s", cmd, cmd_ret["stderr"])
if cmd_ret["stderr"]:
raise CommandExecutionError(
"Unable to execute command: {}\nError: {}".format(cmd, cmd_ret["stderr"])
)
if as_json:
try:
items = salt.utils.json.loads(cmd_ret["stdout"], strict=False)
return items
except ValueError:
_LOG.error("Unable to parse return data as Json.")
raise CommandExecutionError(
"Unable to parse return data as JSON:\n{}".format(cmd_ret["stdout"])
)
return cmd_ret["stdout"]

View file

@ -4005,13 +4005,25 @@ def directory(
if not force:
return _error(
ret,
"File exists where the backup target {} should go".format(
backupname
),
f"File exists where the backup target {backupname} should go",
)
if __opts__["test"]:
ret["changes"][
"forced"
] = f"Existing file at backup path {backupname} would be removed"
else:
__salt__["file.remove"](backupname)
os.rename(name, backupname)
if __opts__["test"]:
ret["changes"]["backup"] = f"{name} would be renamed to {backupname}"
ret["changes"][name] = {"directory": "new"}
ret["comment"] = (
f"{name} would be backed up and replaced with a new directory"
)
ret["result"] = None
return ret
else:
os.rename(name, backupname)
elif force:
# Remove whatever is in the way
if os.path.isfile(name):

View file

@ -184,11 +184,23 @@ def get_sam_name(username):
.. note:: Long computer names are truncated to 15 characters
"""
# Some special identity groups require special handling. They do not have
# the domain prepended to the name. They should be added here as they are
# discovered. Use the SID to be locale agnostic.
# Everyone: S-1-1-0
special_id_groups = ["S-1-1-0"]
try:
sid_obj = win32security.LookupAccountName(None, username)[0]
except pywintypes.error:
return "\\".join([platform.node()[:15].upper(), username])
sid = win32security.ConvertSidToStringSid(sid_obj)
username, domain, _ = win32security.LookupAccountSid(None, sid_obj)
if sid in special_id_groups:
return username
return "\\".join([domain, username])

View file

@ -10,7 +10,7 @@ import re
import shutil
import stat
import sys
from functools import lru_cache, partial, wraps
from functools import lru_cache
from unittest import TestCase # pylint: disable=blacklisted-module
import _pytest.logging
@ -448,8 +448,6 @@ def pytest_collection_modifyitems(config, items):
groups_collection_modifyitems(config, items)
from_filenames_collection_modifyitems(config, items)
log.warning("Mofifying collected tests to keep track of fixture usage")
timeout_marker_tests_paths = (
str(PYTESTS_DIR / "pkg"),
str(PYTESTS_DIR / "scenarios"),
@ -478,103 +476,6 @@ def pytest_collection_modifyitems(config, items):
# Default to counting only the test execution for the timeouts, ie,
# withough including the fixtures setup time towards the timeout.
item.add_marker(pytest.mark.timeout(90, func_only=True))
for fixture in item.fixturenames:
if fixture not in item._fixtureinfo.name2fixturedefs:
continue
for fixturedef in item._fixtureinfo.name2fixturedefs[fixture]:
if fixturedef.scope != "package":
continue
try:
fixturedef.finish.__wrapped__
except AttributeError:
original_func = fixturedef.finish
def wrapper(func, fixturedef):
@wraps(func)
def wrapped(self, request, nextitem=False):
try:
return self._finished
except AttributeError:
if nextitem:
fpath = pathlib.Path(self.baseid).resolve()
tpath = pathlib.Path(
nextitem.fspath.strpath
).resolve()
try:
tpath.relative_to(fpath)
# The test module is within the same package that the fixture is
if (
not request.session.shouldfail
and not request.session.shouldstop
):
log.debug(
"The next test item is still under the"
" fixture package path. Not"
" terminating %s",
self,
)
return
except ValueError:
pass
log.debug("Finish called on %s", self)
try:
return func(request)
except (
BaseException # pylint: disable=broad-except
) as exc:
pytest.fail(
"Failed to run finish() on {}: {}".format(
fixturedef, exc
),
pytrace=True,
)
finally:
self._finished = True
return partial(wrapped, fixturedef)
fixturedef.finish = wrapper(fixturedef.finish, fixturedef)
try:
fixturedef.finish.__wrapped__
except AttributeError:
fixturedef.finish.__wrapped__ = original_func
@pytest.hookimpl(trylast=True, hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):
"""
implements the runtest_setup/call/teardown protocol for
the given test item, including capturing exceptions and calling
reporting hooks.
:arg item: test item for which the runtest protocol is performed.
:arg nextitem: the scheduled-to-be-next test item (or None if this
is the end my friend). This argument is passed on to
:py:func:`pytest_runtest_teardown`.
:return boolean: True if no further hook implementations should be invoked.
Stops at first non-None result, see :ref:`firstresult`
"""
request = item._request
used_fixture_defs = []
for fixture in item.fixturenames:
if fixture not in item._fixtureinfo.name2fixturedefs:
continue
for fixturedef in reversed(item._fixtureinfo.name2fixturedefs[fixture]):
if fixturedef.scope != "package":
continue
used_fixture_defs.append(fixturedef)
try:
# Run the test
yield
finally:
for fixturedef in used_fixture_defs:
fixturedef.finish(request, nextitem=nextitem)
del request
del used_fixture_defs
def pytest_markeval_namespace(config):

View file

@ -17,6 +17,7 @@ salt/_logging/(impl|handlers).py:
salt/modules/(apkpkg|aptpkg|ebuildpkg|dpkg_lowpkg|freebsdpkg|mac_brew_pkg|mac_ports_pkg|openbsdpkg|opkg|pacmanpkg|pkgin|pkgng|pkg_resource|rpm_lowpkg|solarisipspkg|solarispkg|win_pkg|xbpspkg|yumpkg|zypperpkg)\.py:
- pytests.unit.states.test_pkg
- pytests.functional.modules.test_pkg
- pytests.functional.modules.test_win_pkg
- pytests.functional.states.test_pkg
- pytests.functional.states.pkgrepo.test_centos
- pytests.functional.states.pkgrepo.test_debian

View file

@ -156,6 +156,9 @@ def test_get_offset(timezone):
"""
Test timezone.get_offset
"""
pytz = pytest.importorskip("pytz")
now = datetime.datetime.now(tz=pytz.UTC)
ret = timezone.set_zone("Pacific/Wake")
assert ret
ret = timezone.get_offset()
@ -166,7 +169,11 @@ def test_get_offset(timezone):
assert ret
ret = timezone.get_offset()
assert isinstance(ret, str)
assert ret == "-0800"
if now.astimezone(pytz.timezone("America/Los_Angeles")).dst():
assert ret == "-0700"
else:
assert ret == "-0800"
@pytest.mark.usefixtures("_reset_zone")

View file

@ -0,0 +1,35 @@
import pytest
pytestmark = [
pytest.mark.windows_whitelisted,
pytest.mark.skip_unless_on_windows,
pytest.mark.slow_test,
]
@pytest.fixture(scope="module")
def pkg_def_contents(state_tree):
return r"""
my-software:
'1.0.1':
full_name: 'My Software'
installer: 'C:\files\mysoftware.msi'
install_flags: '/qn /norestart'
uninstaller: 'C:\files\mysoftware.msi'
uninstall_flags: '/qn /norestart'
msiexec: True
reboot: False
"""
@pytest.fixture(scope="module")
def pkg(modules):
yield modules.pkg
def test_refresh_db(pkg, pkg_def_contents, state_tree, minion_opts):
assert len(pkg.get_package_info("my-software")) == 0
repo_dir = state_tree / "win" / "repo-ng"
with pytest.helpers.temp_file("my-software.sls", pkg_def_contents, repo_dir):
pkg.refresh_db()
assert len(pkg.get_package_info("my-software")) == 1

View file

@ -436,3 +436,55 @@ def test_issue_12209_follow_symlinks(
assert one_group_check == state_file_account.group.name
two_group_check = modules.file.get_group(str(twodir), follow_symlinks=False)
assert two_group_check == state_file_account.group.name
@pytest.mark.parametrize("backupname_isfile", [False, True])
def test_directory_backupname_force_test_mode_noclobber(
file, tmp_path, backupname_isfile
):
"""
Ensure that file.directory does not make changes when backupname is used
alongside force=True and test=True.
See https://github.com/saltstack/salt/issues/66049
"""
source_dir = tmp_path / "source_directory"
source_dir.mkdir()
dest_dir = tmp_path / "dest_directory"
backupname = tmp_path / "backup_dir"
dest_dir.symlink_to(source_dir.resolve())
if backupname_isfile:
backupname.touch()
assert backupname.is_file()
ret = file.directory(
name=str(dest_dir),
allow_symlink=False,
force=True,
backupname=str(backupname),
test=True,
)
# Confirm None result
assert ret.result is None
try:
# Confirm dest_dir not modified
assert salt.utils.path.readlink(str(dest_dir)) == str(source_dir)
except OSError:
pytest.fail(f"{dest_dir} was modified")
# Confirm that comment and changes match what we expect
assert (
ret.comment
== f"{dest_dir} would be backed up and replaced with a new directory"
)
assert ret.changes[str(dest_dir)] == {"directory": "new"}
assert ret.changes["backup"] == f"{dest_dir} would be renamed to {backupname}"
if backupname_isfile:
assert ret.changes["forced"] == (
f"Existing file at backup path {backupname} would be removed"
)
else:
assert "forced" not in ret.changes

View file

@ -78,6 +78,7 @@ def test_update(bucket, s3):
"top.sls": {"content": yaml.dump({"base": {"*": ["foo"]}})},
"foo.sls": {"content": yaml.dump({"nginx": {"pkg.installed": []}})},
"files/nginx.conf": {"content": "server {}"},
"files/conf.d/foo.conf": {"content": "server {}"},
}
make_keys(bucket, s3, keys)
@ -90,6 +91,41 @@ def test_update(bucket, s3):
s3fs.update()
verify_cache(bucket, keys)
# verify that when files get deleted from s3, they also get deleted in
# the local cache
delete_file = "files/nginx.conf"
del keys[delete_file]
s3.delete_object(Bucket=bucket, Key=delete_file)
s3fs.update()
verify_cache(bucket, keys)
cache_file = s3fs._get_cached_file_name(bucket, "base", delete_file)
assert not os.path.exists(cache_file)
# we want empty directories to get deleted from the local cache
# after this one, `files` should still exist
files_dir = os.path.dirname(cache_file)
assert os.path.exists(files_dir)
# but after the last file is deleted, the directory and any parents
# should be deleted too
delete_file = "files/conf.d/foo.conf"
del keys[delete_file]
s3.delete_object(Bucket=bucket, Key=delete_file)
s3fs.update()
verify_cache(bucket, keys)
cache_file = s3fs._get_cached_file_name(bucket, "base", delete_file)
assert not os.path.exists(cache_file)
# after this, `files/conf.d` and `files` should be deleted
conf_d_dir = os.path.dirname(cache_file)
assert not os.path.exists(conf_d_dir)
assert not os.path.exists(files_dir)
@pytest.mark.skip_on_fips_enabled_platform
def test_s3_hash(bucket, s3):
@ -124,8 +160,7 @@ def test_s3_hash(bucket, s3):
@pytest.mark.skip_on_fips_enabled_platform
def test_cache_round_trip(bucket):
metadata = {"foo": "bar"}
cache_file = s3fs._get_cached_file_name(bucket, "base", "somefile")
cache_file = s3fs._get_buckets_cache_filename()
s3fs._write_buckets_cache_file(metadata, cache_file)
assert s3fs._read_buckets_cache_file(cache_file) == metadata

View file

@ -2329,3 +2329,39 @@ def test_latest_version_calls_aptcache_once_per_run():
ret = aptpkg.latest_version("sudo", "unzip", refresh=False)
mock_apt_cache.assert_called_once()
assert ret == {"sudo": "6.0-23+deb10u3", "unzip": ""}
@pytest.mark.parametrize(
"oneline,result",
(
(
"deb [signed-by=/etc/apt/keyrings/example.key arch=amd64] https://example.com/pub/repos/apt xenial main",
{
"signedby": {
"full": "signed-by=/etc/apt/keyrings/example.key",
"value": "/etc/apt/keyrings/example.key",
},
"arch": {"full": "arch=amd64", "value": ["amd64"]},
},
),
(
"deb [arch=amd64 signed-by=/etc/apt/keyrings/example.key] https://example.com/pub/repos/apt xenial main",
{
"arch": {"full": "arch=amd64", "value": ["amd64"]},
"signedby": {
"full": "signed-by=/etc/apt/keyrings/example.key",
"value": "/etc/apt/keyrings/example.key",
},
},
),
(
"deb [arch=amd64] https://example.com/pub/repos/apt xenial main",
{
"arch": {"full": "arch=amd64", "value": ["amd64"]},
},
),
),
)
def test__get_opts(oneline, result):
ret = aptpkg._get_opts(oneline)
assert ret == result

View file

@ -0,0 +1,681 @@
"""
:codeauthor: Gareth J. Greenaway <ggreenaway@vmware.com>
"""
import pytest
import salt.modules.textfsm_mod as textfsm_mod
from tests.support.mock import MagicMock, mock_open, patch
textfsm = pytest.importorskip(
"textfsm", reason="Install textfsm to be able to run this test."
)
@pytest.fixture
def configure_loader_modules():
return {textfsm_mod: {"__opts__": {}}}
def test_dunder_virtual():
"""
Test __virtual__
"""
with patch.object(textfsm_mod, "HAS_TEXTFSM", False):
ret = textfsm_mod.__virtual__()
assert ret == (
False,
"The textfsm execution module failed to load: requires the textfsm library.",
)
def test_extract_cache_file_false():
"""
Test extract
"""
with patch.dict(
textfsm_mod.__salt__, {"cp.cache_file": MagicMock(return_value=False)}
):
ret = textfsm_mod.extract(
"salt://textfsm/juniper_version_template",
raw_text_file="s3://junos_ver.txt",
)
assert not ret["result"]
assert ret["out"] is None
assert (
ret["comment"]
== "Unable to read the TextFSM template from salt://textfsm/juniper_version_template"
)
def test_extract_cache_file_valid():
"""
Test extract
"""
with patch.dict(
textfsm_mod.__salt__,
{
"cp.cache_file": MagicMock(
return_value="/path/to/cache/juniper_version_template"
)
},
):
textfsm_template = r"""Value Chassis (\S+)
Value Required Model (\S+)
Value Boot (.*)
Value Base (.*)
Value Kernel (.*)
Value Crypto (.*)
Value Documentation (.*)
Value Routing (.*)
Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
raw_text = """Hostname: router.abc
Model: mx960
JUNOS Base OS boot [9.1S3.5]
JUNOS Base OS Software Suite [9.1S3.5]
JUNOS Kernel Software Suite [9.1S3.5]
JUNOS Crypto Software Suite [9.1S3.5]
JUNOS Packet Forwarding Engine Support (M/T Common) [9.1S3.5]
JUNOS Packet Forwarding Engine Support (MX Common) [9.1S3.5]
JUNOS Online Documentation [9.1S3.5]
JUNOS Routing Software Suite [9.1S3.5]"""
with patch("salt.utils.files.fopen", mock_open(read_data=textfsm_template)):
with patch.dict(
textfsm_mod.__salt__,
{"cp.get_file_str": MagicMock(return_value=raw_text)},
):
ret = textfsm_mod.extract(
"salt://textfsm/juniper_version_template",
raw_text_file="s3://junos_ver.txt",
)
assert ret == {
"result": True,
"comment": "",
"out": [
{
"chassis": "",
"model": "mx960",
"boot": "9.1S3.5",
"base": "9.1S3.5",
"kernel": "9.1S3.5",
"crypto": "9.1S3.5",
"documentation": "9.1S3.5",
"routing": "9.1S3.5",
}
],
}
with patch("salt.utils.files.fopen", mock_open(read_data=textfsm_template)):
with patch.dict(
textfsm_mod.__salt__,
{"cp.get_file_str": MagicMock(return_value=raw_text)},
):
ret = textfsm_mod.extract(
"salt://textfsm/juniper_version_template", raw_text=raw_text
)
assert ret == {
"result": True,
"comment": "",
"out": [
{
"chassis": "",
"model": "mx960",
"boot": "9.1S3.5",
"base": "9.1S3.5",
"kernel": "9.1S3.5",
"crypto": "9.1S3.5",
"documentation": "9.1S3.5",
"routing": "9.1S3.5",
}
],
}
def test_extract_cache_file_raw_text_get_file_str_false():
"""
Test extract
"""
with patch.dict(
textfsm_mod.__salt__,
{
"cp.cache_file": MagicMock(
return_value="/path/to/cache/juniper_version_template"
)
},
):
textfsm_template = r"""Value Chassis (\S+)
Value Required Model (\S+)
Value Boot (.*)
Value Base (.*)
Value Kernel (.*)
Value Crypto (.*)
Value Documentation (.*)
Value Routing (.*)
Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
raw_text = """Hostname: router.abc
Model: mx960
JUNOS Base OS boot [9.1S3.5]
JUNOS Base OS Software Suite [9.1S3.5]
JUNOS Kernel Software Suite [9.1S3.5]
JUNOS Crypto Software Suite [9.1S3.5]
JUNOS Packet Forwarding Engine Support (M/T Common) [9.1S3.5]
JUNOS Packet Forwarding Engine Support (MX Common) [9.1S3.5]
JUNOS Online Documentation [9.1S3.5]
JUNOS Routing Software Suite [9.1S3.5]"""
with patch("salt.utils.files.fopen", mock_open(read_data=textfsm_template)):
with patch.dict(
textfsm_mod.__salt__, {"cp.get_file_str": MagicMock(return_value=False)}
):
ret = textfsm_mod.extract(
"salt://textfsm/juniper_version_template",
raw_text_file="s3://junos_ver.txt",
)
assert ret == {
"result": False,
"comment": "Unable to read from s3://junos_ver.txt. Please specify a valid input file or text.",
"out": None,
}
def test_extract_cache_file_raw_text_exception():
"""
Test extract
"""
with patch.dict(
textfsm_mod.__salt__,
{
"cp.cache_file": MagicMock(
return_value="/path/to/cache/juniper_version_template"
)
},
):
textfsm_template = r"""Value Chassis (\S+)
Value Required Model (\S+)
Value Boot (.*)
Value Base (.*)
Value Kernel (.*)
Value Crypto (.*)
Value Documentation (.*)
Xalue Routing (.*)
Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
raw_text = """Hostname: router.abc
Model: mx960
JUNOS Base OS boot [9.1S3.5]
JUNOS Base OS Software Suite [9.1S3.5]
JUNOS Kernel Software Suite [9.1S3.5]
JUNOS Crypto Software Suite [9.1S3.5]
JUNOS Packet Forwarding Engine Support (M/T Common) [9.1S3.5]
JUNOS Packet Forwarding Engine Support (MX Common) [9.1S3.5]
JUNOS Online Documentation [9.1S3.5]
JUNOS Routing Software Suite [9.1S3.5]"""
with patch("salt.utils.files.fopen", mock_open(read_data=textfsm_template)):
with patch.dict(
textfsm_mod.__salt__, {"cp.get_file_str": MagicMock(return_value=False)}
):
ret = textfsm_mod.extract(
"salt://textfsm/juniper_version_template",
raw_text_file="s3://junos_ver.txt",
)
assert not ret["result"]
assert "Unable to parse the TextFSM template from " in ret["comment"]
assert ret["out"] is None
def test_extract_cache_file_raw_text_false():
"""
Test extract
"""
with patch.dict(
textfsm_mod.__salt__,
{
"cp.cache_file": MagicMock(
return_value="/path/to/cache/juniper_version_template"
)
},
):
textfsm_template = r"""Value Chassis (\S+)
Value Required Model (\S+)
Value Boot (.*)
Value Base (.*)
Value Kernel (.*)
Value Crypto (.*)
Value Documentation (.*)
Value Routing (.*)
Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
with patch("salt.utils.files.fopen", mock_open(read_data=textfsm_template)):
ret = textfsm_mod.extract(
"salt://textfsm/juniper_version_template", raw_text=""
)
assert ret == {
"result": False,
"comment": "Please specify a valid input file or text.",
"out": None,
}
def test_index_not_clitable():
"""
Test index
"""
with patch.object(textfsm_mod, "HAS_CLITABLE", False):
ret = textfsm_mod.index(
command="sh ver",
platform="Juniper",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="salt://textfsm/",
)
assert ret == {
"out": None,
"result": False,
"comment": "TextFSM does not seem that has clitable embedded.",
}
def test_index_no_textsm_path():
"""
Test index
"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True):
ret = textfsm_mod.index(
command="sh ver",
platform="Juniper",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="",
)
assert ret == {
"out": None,
"result": False,
"comment": "No TextFSM templates path specified. Please configure in opts/pillar/function args.",
}
def test_index_no_platform():
"""
Test index
"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True):
ret = textfsm_mod.index(
command="sh ver",
platform="",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="",
)
assert ret == {
"out": None,
"result": False,
"comment": "No platform specified, no platform grain identifier configured.",
}
def test_index_no_platform_name_grains():
"""
Test index
"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True):
with patch.dict(
textfsm_mod.__opts__, {"textfsm_platform_grain": "textfsm_platform_grain"}
):
ret = textfsm_mod.index(
command="sh ver",
platform="",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="",
)
assert ret == {
"out": None,
"result": False,
"comment": "Unable to identify the platform name using the textfsm_platform_grain grain.",
}
def test_index_platform_name_grains_no_cachedir():
"""
Test index
"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True), patch.dict(
textfsm_mod.__opts__, {"textfsm_platform_grain": "textfsm_platform_grain"}
), patch.dict(
textfsm_mod.__grains__,
{"textfsm_platform_grain": "textfsm_platform_grain"},
), patch.dict(
textfsm_mod.__salt__,
{"cp.cache_dir": MagicMock(return_value=False)},
):
ret = textfsm_mod.index(
command="sh ver",
platform="",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="salt://textfsm/",
)
assert ret == {
"out": None,
"result": False,
"comment": "Unable to fetch from salt://textfsm/. Is the TextFSM path correctly specified?",
}
def test_index_platform_name_grains_output_false():
"""
Test index
"""
mock_open_index = """
Template, Hostname, Vendor, Command
juniper_version_template, .*, Juniper, sh[[ow]] ve[[rsion]]"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True), patch.dict(
textfsm_mod.__opts__, {"textfsm_platform_grain": "textfsm_platform_grain"}
), patch.dict(
textfsm_mod.__grains__,
{"textfsm_platform_grain": "textfsm_platform_grain"},
), patch.dict(
textfsm_mod.__salt__,
{"cp.cache_dir": MagicMock(return_value="/path/to/cache/")},
), patch.object(
textfsm_mod.clitable,
"open",
mock_open(read_data=mock_open_index),
), patch.dict(
textfsm_mod.__salt__,
{"cp.get_file_str": MagicMock(return_value=False)},
):
ret = textfsm_mod.index(
command="sh ver",
platform="",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="salt://textfsm/",
)
assert ret == {
"out": None,
"result": False,
"comment": "Unable to read from salt://textfsm/juniper_version_example. Please specify a valid file or text.",
}
def test_index_platform_name_grains_no_output_specified():
"""
Test index
"""
mock_open_index = """
Template, Hostname, Vendor, Command
juniper_version_template, .*, Juniper, sh[[ow]] ve[[rsion]]"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True), patch.dict(
textfsm_mod.__opts__, {"textfsm_platform_grain": "textfsm_platform_grain"}
), patch.dict(
textfsm_mod.__grains__,
{"textfsm_platform_grain": "textfsm_platform_grain"},
), patch.dict(
textfsm_mod.__salt__,
{"cp.cache_dir": MagicMock(return_value="/path/to/cache/")},
), patch.object(
textfsm.clitable, "open", mock_open(read_data=mock_open_index)
), patch.dict(
textfsm_mod.__salt__,
{"cp.get_file_str": MagicMock(return_value=False)},
):
ret = textfsm_mod.index(
command="sh ver",
platform="",
textfsm_path="salt://textfsm/",
)
assert ret == {
"out": None,
"result": False,
"comment": "Please specify a valid output text or file",
}
def test_index_platform_name_grains_output_specified():
"""
Test index
"""
mock_open_index = """
Template, Hostname, Vendor, Command
juniper_version_template, .*, Juniper, sh[[ow]] ve[[rsion]]"""
juniper_version_template_one = r"""Value Chassis (\S+)
Value Required Model (\S+)
Value Boot (.*)
Value Base (.*)
Value Kernel (.*)
Value Crypto (.*)
Value Documentation (.*)
Value Routing (.*)
Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
juniper_version_template_two = r"""Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
output_text = """
Hostname: router.abc
Model: mx960
JUNOS Base OS boot [9.1S3.5]
JUNOS Base OS Software Suite [9.1S3.5]
JUNOS Kernel Software Suite [9.1S3.5]
JUNOS Crypto Software Suite [9.1S3.5]
JUNOS Packet Forwarding Engine Support (M/T Common) [9.1S3.5]
JUNOS Packet Forwarding Engine Support (MX Common) [9.1S3.5]
JUNOS Online Documentation [9.1S3.5]
JUNOS Routing Software Suite [9.1S3.5]"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True), patch.dict(
textfsm_mod.__opts__, {"textfsm_platform_grain": "textfsm_platform_grain"}
), patch.dict(
textfsm_mod.__grains__,
{"textfsm_platform_grain": "textfsm_platform_grain"},
), patch.dict(
textfsm_mod.__salt__,
{"cp.cache_dir": MagicMock(return_value="/path/to/cache/")},
):
mock_read_data = {
"/index": [mock_open_index],
"/juniper_version_template": [
juniper_version_template_one,
juniper_version_template_two,
],
}
with patch.object(
textfsm.clitable, "open", mock_open(read_data=mock_read_data)
), patch.dict(
textfsm_mod.__salt__,
{"cp.get_file_str": MagicMock(return_value=output_text)},
):
ret = textfsm_mod.index(
command="sh ver",
platform="",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="salt://textfsm/",
)
assert ret == {
"out": [
{
"chassis": "",
"model": "mx960",
"boot": "9.1S3.5",
"base": "9.1S3.5",
"kernel": "9.1S3.5",
"crypto": "9.1S3.5",
"documentation": "9.1S3.5",
"routing": "9.1S3.5",
}
],
"result": True,
"comment": "",
}
def test_index_platform_name_grains_output_specified_no_attribute():
"""
Test index
"""
mock_open_index = """
Template, Hostname, Vendor, Command
juniper_version_template, .*, Juniper, sh[[ow]] ve[[rsion]]"""
juniper_version_template_one = r"""Value Chassis (\S+)
Value Required Model (\S+)
Value Boot (.*)
Value Base (.*)
Value Kernel (.*)
Value Crypto (.*)
Value Documentation (.*)
Value Routing (.*)
Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
juniper_version_template_two = r"""Start
# Support multiple chassis systems.
^\S+:$$ -> Continue.Record
^${Chassis}:$$
^Model: ${Model}
^JUNOS Base OS boot \[${Boot}\]
^JUNOS Software Release \[${Base}\]
^JUNOS Base OS Software Suite \[${Base}\]
^JUNOS Kernel Software Suite \[${Kernel}\]
^JUNOS Crypto Software Suite \[${Crypto}\]
^JUNOS Online Documentation \[${Documentation}\]
^JUNOS Routing Software Suite \[${Routing}\]"""
output_text = """
Hostname: router.abc
Model: mx960
JUNOS Base OS boot [9.1S3.5]
JUNOS Base OS Software Suite [9.1S3.5]
JUNOS Kernel Software Suite [9.1S3.5]
JUNOS Crypto Software Suite [9.1S3.5]
JUNOS Packet Forwarding Engine Support (M/T Common) [9.1S3.5]
JUNOS Packet Forwarding Engine Support (MX Common) [9.1S3.5]
JUNOS Online Documentation [9.1S3.5]
JUNOS Routing Software Suite [9.1S3.5]"""
with patch.object(textfsm_mod, "HAS_CLITABLE", True), patch.dict(
textfsm_mod.__opts__, {"textfsm_platform_grain": "textfsm_platform_grain"}
), patch.dict(
textfsm_mod.__grains__,
{"textfsm_platform_grain": "textfsm_platform_grain"},
), patch.dict(
textfsm_mod.__salt__,
{"cp.cache_dir": MagicMock(return_value="/path/to/cache/")},
):
mock_read_data = {
"/index": [mock_open_index],
"/juniper_version_template": [
juniper_version_template_one,
juniper_version_template_two,
],
}
with patch.object(
textfsm.clitable, "open", mock_open(read_data=mock_read_data)
), patch.dict(
textfsm_mod.__salt__,
{"cp.get_file_str": MagicMock(return_value=output_text)},
):
ret = textfsm_mod.index(
command="sr ver",
platform="",
output_file="salt://textfsm/juniper_version_example",
textfsm_path="salt://textfsm/",
)
assert ret == {
"out": None,
"result": False,
"comment": "Unable to process the output: No template found for attributes: \"{'Command': 'sr ver', 'Platform': 'textfsm_platform_grain'}\"",
}

View file

@ -755,3 +755,21 @@ def test__reverse_cmp_pkg_versions(v1, v2, expected):
assert result == expected, "cmp({}, {}) should be {}, got {}".format(
v1, v2, expected, result
)
def test__repo_process_pkg_sls():
patch_render = patch("salt.loader.render")
patch_opts = patch.dict(win_pkg.__opts__, {"renderer": None})
patch_compile = patch("salt.template.compile_template", return_value="junk")
with patch_opts, patch_render as render, patch_compile as test:
ret = win_pkg._repo_process_pkg_sls(
filename="junk",
short_path_name="junk",
ret={},
successful_verbose=False,
saltenv="spongebob",
)
assert ret is False
test.assert_called_once_with(
"junk", render(), None, "", "", saltenv="spongebob"
)

View file

@ -1,10 +1,11 @@
"""
Test cases for salt.modules.win_pki
Test cases for salt.modules.win_pki
"""
import pytest
import salt.modules.win_pki as win_pki
from salt.exceptions import CommandExecutionError
from tests.support.mock import MagicMock, patch
@ -181,3 +182,46 @@ def test_remove_cert(thumbprint, certs):
"salt.modules.win_pki.get_certs", MagicMock(return_value=certs)
):
assert win_pki.remove_cert(thumbprint=thumbprint[::-1])
def test__cmd_run():
"""
Test the _cmd_run function
"""
mock_run = MagicMock(
return_value={"retcode": 0, "stderr": "", "stdout": "some result"}
)
with patch.dict(win_pki.__salt__, {"cmd.run_all": mock_run}):
result = win_pki._cmd_run(cmd="command")
assert result == "some result"
def test__cmd_run_as_json():
mock_run = MagicMock(
return_value={"retcode": 0, "stderr": "", "stdout": '{"key": "value"}'}
)
with patch.dict(win_pki.__salt__, {"cmd.run_all": mock_run}):
result = win_pki._cmd_run(cmd="command", as_json=True)
assert result == {"key": "value"}
def test__cmd_run_stderr():
mock_run = MagicMock(
return_value={"retcode": 0, "stderr": "some error", "stdout": ""}
)
with patch.dict(win_pki.__salt__, {"cmd.run_all": mock_run}):
with pytest.raises(CommandExecutionError) as exc_info:
win_pki._cmd_run(cmd="command")
expected = "Unable to execute command: command\nError: some error"
assert exc_info.value.args[0] == expected
def test__cmd_run_bad_json():
mock_run = MagicMock(
return_value={"retcode": 0, "stderr": "", "stdout": "not : valid\njson"}
)
with patch.dict(win_pki.__salt__, {"cmd.run_all": mock_run}):
with pytest.raises(CommandExecutionError) as exc_info:
win_pki._cmd_run(cmd="command", as_json=True)
expected = "Unable to parse return data as JSON:\nnot : valid\njson"
assert exc_info.value.args[0] == expected

View file

@ -1,3 +1,5 @@
import platform
import pytest
import salt.utils.win_functions as win_functions
@ -6,6 +8,11 @@ from tests.support.mock import MagicMock, patch
HAS_WIN32 = False
HAS_PYWIN = False
pytestmark = [
pytest.mark.windows_whitelisted,
pytest.mark.skip_unless_on_windows,
]
try:
import win32net
@ -32,7 +39,6 @@ except ImportError:
# Test cases for salt.utils.win_functions.
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_escape_argument_simple():
"""
Test to make sure we encode simple arguments correctly
@ -41,7 +47,6 @@ def test_escape_argument_simple():
assert encoded == "simple"
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_escape_argument_with_space():
"""
Test to make sure we encode arguments containing spaces correctly
@ -50,7 +55,6 @@ def test_escape_argument_with_space():
assert encoded == '^"with space^"'
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_escape_argument_simple_path():
"""
Test to make sure we encode simple path arguments correctly
@ -59,7 +63,6 @@ def test_escape_argument_simple_path():
assert encoded == "C:\\some\\path"
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_escape_argument_path_with_space():
"""
Test to make sure we encode path arguments containing spaces correctly
@ -68,7 +71,6 @@ def test_escape_argument_path_with_space():
assert encoded == '^"C:\\Some Path\\With Spaces^"'
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_broadcast_setting_change():
"""
Test to rehash the Environment variables
@ -76,14 +78,12 @@ def test_broadcast_setting_change():
assert win_functions.broadcast_setting_change()
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_get_user_groups():
groups = ["Administrators", "Users"]
with patch("win32net.NetUserGetLocalGroups", return_value=groups):
assert win_functions.get_user_groups("Administrator") == groups
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_get_user_groups_sid():
groups = ["Administrators", "Users"]
expected = ["S-1-5-32-544", "S-1-5-32-545"]
@ -91,14 +91,12 @@ def test_get_user_groups_sid():
assert win_functions.get_user_groups("Administrator", sid=True) == expected
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
def test_get_user_groups_system():
groups = ["SYSTEM"]
with patch("win32net.NetUserGetLocalGroups", return_value=groups):
assert win_functions.get_user_groups("SYSTEM") == groups
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
@pytest.mark.skipif(not HAS_WIN32, reason="Requires Win32 libraries")
def test_get_user_groups_unavailable_dc():
groups = ["Administrators", "Users"]
@ -109,7 +107,6 @@ def test_get_user_groups_unavailable_dc():
assert win_functions.get_user_groups("Administrator") == groups
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
@pytest.mark.skipif(not HAS_WIN32, reason="Requires Win32 libraries")
def test_get_user_groups_unknown_dc():
groups = ["Administrators", "Users"]
@ -120,7 +117,6 @@ def test_get_user_groups_unknown_dc():
assert win_functions.get_user_groups("Administrator") == groups
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
@pytest.mark.skipif(not HAS_WIN32, reason="Requires Win32 libraries")
def test_get_user_groups_missing_permission():
groups = ["Administrators", "Users"]
@ -131,7 +127,6 @@ def test_get_user_groups_missing_permission():
assert win_functions.get_user_groups("Administrator") == groups
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
@pytest.mark.skipif(not HAS_WIN32, reason="Requires Win32 libraries")
def test_get_user_groups_error():
win_error = WinError()
@ -142,7 +137,6 @@ def test_get_user_groups_error():
win_functions.get_user_groups("Administrator")
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
@pytest.mark.skipif(not HAS_PYWIN, reason="Requires pywintypes libraries")
def test_get_user_groups_local_pywin_error():
win_error = PyWinError()
@ -153,7 +147,6 @@ def test_get_user_groups_local_pywin_error():
win_functions.get_user_groups("Administrator")
@pytest.mark.skip_unless_on_windows(reason="Test is only applicable to Windows.")
@pytest.mark.skipif(not HAS_PYWIN, reason="Requires pywintypes libraries")
def test_get_user_groups_pywin_error():
win_error = PyWinError()
@ -163,3 +156,27 @@ def test_get_user_groups_pywin_error():
with patch("win32net.NetUserGetGroups", side_effect=mock_error):
with pytest.raises(PyWinError):
win_functions.get_user_groups("Administrator")
@pytest.mark.skipif(not HAS_PYWIN, reason="Requires pywintypes libraries")
def test_get_sam_name_lookup_fails():
win_error = PyWinError()
mock_error = MagicMock(side_effect=win_error)
with patch("win32security.LookupAccountName", side_effect=mock_error):
expected = "\\".join([platform.node()[:15].upper(), "junk"])
result = win_functions.get_sam_name("junk")
assert result == expected
@pytest.mark.skipif(not HAS_PYWIN, reason="Requires pywintypes libraries")
def test_get_sam_name_everyone():
expected = "Everyone"
result = win_functions.get_sam_name("Everyone")
assert result == expected
@pytest.mark.skipif(not HAS_PYWIN, reason="Requires pywintypes libraries")
def test_get_sam_name():
expected = "\\".join([platform.node()[:15], "Administrator"])
result = win_functions.get_sam_name("Administrator")
assert result == expected

View file

@ -71,7 +71,7 @@ class MockFH:
self.write = Mock(side_effect=self._write)
self.writelines = Mock(side_effect=self._writelines)
self.close = Mock()
self.seek = Mock()
self.seek = Mock(side_effect=self._seek)
self.__loc = 0
self.__read_data_ok = False
@ -219,6 +219,14 @@ class MockFH:
def __exit__(self, exc_type, exc_val, exc_tb): # pylint: disable=unused-argument
pass
# For some reason this gets called with additional args on Windows when
# running the following test:
# tests/pytests/unit/beacons/test_log_beacon.py::test_log_match
# Let's just absorb them with *args
def _seek(self, pos=0, *args):
self.__loc = pos
self.read_data_iter = self._iterate_read_data(self.read_data)
class MockCall:
def __init__(self, *args, **kwargs):

View file

@ -316,7 +316,7 @@ def debian(
_rpm_distro_info = {
"amazon": ["2", "2023"],
"redhat": ["7", "8", "9"],
"fedora": ["36", "37", "38"],
"fedora": ["36", "37", "38", "39"],
"photon": ["3", "4", "5"],
}

View file

@ -749,7 +749,6 @@ MISSING_EXAMPLES = {
"delete_advanced_configs",
"get_vm",
],
"salt/modules/win_pkg.py": ["get_package_info"],
"salt/modules/win_timezone.py": ["zone_compare"],
"salt/modules/zk_concurrency.py": [
"lock",

View file

@ -226,7 +226,11 @@ def download_file(
headers: dict[str, str] | None = None,
) -> pathlib.Path:
ctx.info(f"Downloading {dest.name!r} @ {url} ...")
curl = shutil.which("curl")
if sys.platform == "win32":
# We don't want to use curl on Windows, it doesn't work
curl = None
else:
curl = shutil.which("curl")
if curl is not None:
command = [curl, "-sS", "-L"]
if headers: