Merge branch '3006.x' into merge/3007.x/3006.x-last-merge

This commit is contained in:
Daniel A. Wozniak 2025-02-13 16:59:52 -07:00
commit d625eaeea8
62 changed files with 3547 additions and 4783 deletions

94
.github/actions/ssh-tunnel/README.md vendored Normal file
View file

@ -0,0 +1,94 @@
# SSH Tunnel
The ssh-tunnel action will create a reverse tunnel over webrtc to port 22 on the runner.
## Usage
In order to use this action you must have a sdp offer from your local host and a ssh key pair.
Start with creating an sdp offer on your local machine. Provide these values to the ssh-tunnel
action and wait for output from the action with the sdp reply. Provide the reply to the local
rtcforward.py process by pasting it to stdin. If all goes well the local port on your maching
will be forwarded to the ssh port on the runner.
### Getting an sdp offer
To get an sdp offer start rtcforward.py on you local machine with the offer command.
You can also specify which port on the local machine will be used for the tunnel.
``` bash
$ python3 .github/actions/ssh-tunnel/rtcforward.py offer --port 5222
```
rtcforward.py will create an offer an display it to your terminal. (This example offer has been truncated)
After showing the offer the `rtcforward.py` process will wait for a reply.
```
-- offer --
eyJzZHAiOiAidj0wXHJcbm89LSAzOTQ3Mzg4NjUzIDM5NDczODg2NTMgSU4gSVA0IDAuMC4wLjBcclxu
cz0tXHJcbnQ9MCAwXHJcbmE9Z3JvdXA6QlVORExFIDBcclxuYT1tc2lkLXNlbWFudGljOldNUyAqXHJc
bm09YXBwbGljYXRpb24gMzUyNjkgRFRMUy9TQ1RQIDUwMDBcclxuYz1JTiBJUDQgMTkyLjE2OC4wLjIw
IHVkcCAxNjk0NDk4ODE1IDE4NC4xNzkuMjEwLjE1MiAzNTI2OSB0eXAgc3JmbHggcmFkZHIgMTkyLjE2
OC4wLjIwMSBycG9ydCAzNTI2OVxyXG5hPWNhbmRpZGF0ZTozZWFjMzJiZTZkY2RkMTAwZDcwMTFiNWY0
NTo4Qzo2MDoxMTpFQTo3NzpDMTo5RTo1QTo3QzpDQzowRDowODpFQzo2NDowQToxM1xyXG5hPWZpbmdl
cnByaW50OnNoYS01MTIgNjY6MzI6RUQ6MDA6N0I6QjY6NTQ6NzA6MzE6OTA6M0I6Mjg6Q0I6QTk6REU6
MzQ6QjI6NDY6NzE6NUI6MjM6ODA6Nzg6Njg6RDA6QTA6QTg6MjU6QkY6MDQ6ODY6NUY6OTA6QUY6MUQ6
QjA6QzY6ODA6QUY6OTc6QTI6MkM6NDI6QUU6MkI6Q0Q6Mjk6RUQ6MkI6ODc6NTU6ODg6NDY6QTM6ODk6
OEY6ODk6OTE6QTE6QTI6NDM6NTc6M0E6MjZcclxuYT1zZXR1cDphY3RwYXNzXHJcbiIsICJ0eXBlIjog
Im9mZmVyIn0=
-- end offer --
-- Please enter a message from remote party --
```
### Getting an sdp answer
Provide the offer to the ssh-tunnel action. When the action runs, an answer to the offer will be generated.
In the action output you will see that the offer was recieved and the reply in the output.
```
-- Please enter a message from remote party --
-- Message received --
-- reply --
eyJzZHAiOiAidj0wXHJcbm89LSAzOTQ3Mzg3NDcxIDM5NDczODc0NzEgSU4gSVA0IDAuMC4wLjBcclxu
cz0tXHJcbnQ9MCAwXHJcbmE9Z3JvdXA6QlVORExFIDBcclxuYT1tc2lkLXNlbWFudGljOldNUyAqXHJc
bm09YXBwbGljYXRpb24gNTcwMzkgRFRMUy9TQ1RQIDUwMDBcclxuYz1JTiBJUDQgMTkyLjE2OC42NC4x
MFxyXG5hPW1pZDowXHJcbmE9c2N0cG1hcDo1MDAwIHdlYnJ0Yy1kYXRhY2hhbm5lbCA2NTUzNVxyXG5h
MTc6MEI6RTA6OTA6QUM6RjU6RTk6RUI6Q0E6RUE6NTY6REI6NTA6QTk6REY6NTU6MzY6MkM6REI6OUE6
MDc6Mzc6QTM6NDc6NjlcclxuYT1maW5nZXJwcmludDpzaGEtNTEyIDMyOjRDOjk0OkRDOjNFOkU5OkU3
OjNCOjc5OjI4OjZDOjc5OkFEOkVDOjIzOkJDOjRBOjRBOjE5OjlCOjg5OkE3OkE2OjZBOjAwOjJFOkM5
OkE0OjlEOjAwOjM0OjFFOjRDOkVGOjcwOkY5OkNBOjg0OjlEOjcxOjI5OkVCOkIxOkREOkFEOjg5OjUx
OkZFOjhCOjI3OjFDOjFBOkJEOjUxOjQ2OjE4OjBBOjhFOjVBOjI1OjQzOjQzOjZGOkRBXHJcbmE9c2V0
dXA6YWN0aXZlXHJcbiIsICJ0eXBlIjogImFuc3dlciJ9
-- end reply --
```
# Finalizing the tunnel
Paste the sdp reply from the running action into the running `rtcforward.py` process that created the offer.
After receiveing the offer you will see `-- Message received --` and tunnel will be created.
```
-- offer --
eyJzZHAiOiAidj0wXHJcbm89LSAzOTQ3Mzg4NjUzIDM5NDczODg2NTMgSU4gSVA0IDAuMC4wLjBcclxu
cz0tXHJcbnQ9MCAwXHJcbmE9Z3JvdXA6QlVORExFIDBcclxuYT1tc2lkLXNlbWFudGljOldNUyAqXHJc
bm09YXBwbGljYXRpb24gMzUyNjkgRFRMUy9TQ1RQIDUwMDBcclxuYz1JTiBJUDQgMTkyLjE2OC4wLjIw
IHVkcCAxNjk0NDk4ODE1IDE4NC4xNzkuMjEwLjE1MiAzNTI2OSB0eXAgc3JmbHggcmFkZHIgMTkyLjE2
OC4wLjIwMSBycG9ydCAzNTI2OVxyXG5hPWNhbmRpZGF0ZTozZWFjMzJiZTZkY2RkMTAwZDcwMTFiNWY0
NTo4Qzo2MDoxMTpFQTo3NzpDMTo5RTo1QTo3QzpDQzowRDowODpFQzo2NDowQToxM1xyXG5hPWZpbmdl
cnByaW50OnNoYS01MTIgNjY6MzI6RUQ6MDA6N0I6QjY6NTQ6NzA6MzE6OTA6M0I6Mjg6Q0I6QTk6REU6
MzQ6QjI6NDY6NzE6NUI6MjM6ODA6Nzg6Njg6RDA6QTA6QTg6MjU6QkY6MDQ6ODY6NUY6OTA6QUY6MUQ6
QjA6QzY6ODA6QUY6OTc6QTI6MkM6NDI6QUU6MkI6Q0Q6Mjk6RUQ6MkI6ODc6NTU6ODg6NDY6QTM6ODk6
OEY6ODk6OTE6QTE6QTI6NDM6NTc6M0E6MjZcclxuYT1zZXR1cDphY3RwYXNzXHJcbiIsICJ0eXBlIjog
Im9mZmVyIn0=
-- end offer --
-- Please enter a message from remote party --
eyJzZHAiOiAidj0wXHJcbm89LSAzOTQ3Mzg3NDcxIDM5NDczODc0NzEgSU4gSVA0IDAuMC4wLjBcclxu
cz0tXHJcbnQ9MCAwXHJcbmE9Z3JvdXA6QlVORExFIDBcclxuYT1tc2lkLXNlbWFudGljOldNUyAqXHJc
bm09YXBwbGljYXRpb24gNTcwMzkgRFRMUy9TQ1RQIDUwMDBcclxuYz1JTiBJUDQgMTkyLjE2OC42NC4x
MFxyXG5hPW1pZDowXHJcbmE9c2N0cG1hcDo1MDAwIHdlYnJ0Yy1kYXRhY2hhbm5lbCA2NTUzNVxyXG5h
MTc6MEI6RTA6OTA6QUM6RjU6RTk6RUI6Q0E6RUE6NTY6REI6NTA6QTk6REY6NTU6MzY6MkM6REI6OUE6
MDc6Mzc6QTM6NDc6NjlcclxuYT1maW5nZXJwcmludDpzaGEtNTEyIDMyOjRDOjk0OkRDOjNFOkU5OkU3
OjNCOjc5OjI4OjZDOjc5OkFEOkVDOjIzOkJDOjRBOjRBOjE5OjlCOjg5OkE3OkE2OjZBOjAwOjJFOkM5
OkE0OjlEOjAwOjM0OjFFOjRDOkVGOjcwOkY5OkNBOjg0OjlEOjcxOjI5OkVCOkIxOkREOkFEOjg5OjUx
OkZFOjhCOjI3OjFDOjFBOkJEOjUxOjQ2OjE4OjBBOjhFOjVBOjI1OjQzOjQzOjZGOkRBXHJcbmE9c2V0
dXA6YWN0aXZlXHJcbiIsICJ0eXBlIjogImFuc3dlciJ9
-- Message received --
```

107
.github/actions/ssh-tunnel/action.yml vendored Normal file
View file

@ -0,0 +1,107 @@
name: ssh-tunnel
description: SSH Reverse Tunnel
inputs:
public_key:
required: true
type: string
description: Public key to accept for reverse tunnel. Warning, this should not be the public key for the 'private_key' input.
offer:
required: true
type: string
description: RTC offer
debug:
required: false
type: bool
default: false
description: Run sshd with debug enabled.
runs:
using: composite
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install ssh
if: ${{ runner.os == 'Windows' }}
shell: powershell
run: |
python3.exe -m pip install requests
python3.exe .github/actions/ssh-tunnel/installssh.py
- name: Start SSH
shell: bash
run: |
if [ "$RUNNER_OS" = "Windows" ]; then
powershell.exe -command "Start-Service sshd"
elif [ "$RUNNER_OS" = "macOS" ]; then
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
else
sudo systemctl start ssh
fi
- name: Show sshd configuration
shell: bash
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
cat /etc/ssh/sshd_config
elif [ "$RUNNER_OS" = "macOS" ]; then
cat /private/etc/ssh/sshd_config
else
cat "C:\ProgramData\ssh\sshd_config"
fi
- name: Add ssh public key
shell: bash
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
mkdir -p /home/runner/.ssh
chmod 700 /home/runner/.ssh
touch /home/runner/.ssh/authorized_keys
echo "${{ inputs.public_key }}" | tee -a /home/runner/.ssh/authorized_keys
elif [ "$RUNNER_OS" = "macOS" ]; then
mkdir -p /Users/runner/.ssh
chmod 700 /Users/runner/.ssh
touch /Users/runner/.ssh/authorized_keys
echo "${{ inputs.public_key }}" | tee -a /Users/runner/.ssh/authorized_keys
else
echo "${{ inputs.public_key }}" | tee -a "C:\ProgramData\ssh\administrators_authorized_keys"
fi
- name: Stop SSHD
if: ${{ inputs.debug }}
shell: bash
run: |
if [ "${{ inputs.debug }}" = "true" ]; then
if [ "$RUNNER_OS" = "Windows" ]; then
powershell.exe -command "Stop-Service sshd"
elif [ "$RUNNER_OS" = "macOS" ]; then
sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
else
sudo systemctl stop ssh
fi
fi
- name: Create rtc tunnel
shell: bash
run: |
if [ "${{ inputs.debug }}" = "true" ]; then
if [ "$RUNNER_OS" = "Windows" ]; then
./OpenSSH-Win64/sshd.exe -d &
elif [ "$RUNNER_OS" = "macOS" ]; then
sudo /usr/sbin/sshd -d &
else
sudo mkdir -p /run/sshd
sudo chmod 755 /run/sshd
sudo /usr/sbin/sshd -d &
fi
fi
if [ "$RUNNER_OS" = "Windows" ]; then
python3 -m pip install aiortc
else
python3 -m pip install aiortc uvloop
fi
echo '${{ inputs.offer }}' | python .github/actions/ssh-tunnel/rtcforward.py --port 22 answer

View file

@ -0,0 +1,44 @@
"""
"""
import pathlib
import subprocess
import zipfile
import requests
fwrule = """
New-NetFirewallRule `
-Name sshd `
-DisplayName 'OpenSSH SSH Server' `
-Enabled True `
-Direction Inbound `
-Protocol TCP `
-Action Allow `
-LocalPort 22 `
-Program "{}"
"""
def start_ssh_server():
"""
Pretty print the GH Actions event.
"""
resp = requests.get(
"https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.8.1.0p1-Preview/OpenSSH-Win64.zip",
allow_redirects=True,
)
with open("openssh.zip", "wb") as fp:
fp.write(resp.content)
with zipfile.ZipFile("openssh.zip") as fp:
fp.extractall()
install_script = pathlib.Path("./OpenSSH-Win64/install-sshd.ps1").resolve()
print(f"{install_script}")
subprocess.call(["powershell.exe", f"{install_script}"])
with open("fwrule.ps1", "w") as fp:
fp.write(fwrule.format(install_script.parent / "sshd.exe"))
subprocess.call(["powershell.exe", f"fwrule.ps1"])
if __name__ == "__main__":
start_ssh_server()

361
.github/actions/ssh-tunnel/rtcforward.py vendored Normal file
View file

@ -0,0 +1,361 @@
import argparse
import asyncio
import base64
import concurrent
import io
import json
import logging
import os
import sys
import textwrap
import time
aiortc = None
try:
import aiortc.exceptions
from aiortc import RTCIceCandidate, RTCPeerConnection, RTCSessionDescription
from aiortc.contrib.signaling import BYE, add_signaling_arguments, create_signaling
except ImportError:
pass
uvloop = None
try:
import uvloop
except ImportError:
pass
if sys.platform == "win32":
if not aiortc:
print("Please run 'pip install aiortc' and try again.")
sys.exit(1)
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
else:
if not aiortc or not uvloop:
print("Please run 'pip install aiortc uvloop' and try again.")
sys.exit(1)
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
log = logging.getLogger(__name__)
def object_from_string(message_str):
message = json.loads(message_str)
if message["type"] in ["answer", "offer"]:
return RTCSessionDescription(**message)
elif message["type"] == "candidate" and message["candidate"]:
candidate = candidate_from_sdp(message["candidate"].split(":", 1)[1])
candidate.sdpMid = message["id"]
candidate.sdpMLineIndex = message["label"]
return candidate
elif message["type"] == "bye":
return BYE
def object_to_string(obj):
if isinstance(obj, RTCSessionDescription):
message = {"sdp": obj.sdp, "type": obj.type}
elif isinstance(obj, RTCIceCandidate):
message = {
"candidate": "candidate:" + candidate_to_sdp(obj),
"id": obj.sdpMid,
"label": obj.sdpMLineIndex,
"type": "candidate",
}
else:
assert obj is BYE
message = {"type": "bye"}
return json.dumps(message, sort_keys=True)
def print_pastable(data, message="offer"):
print(f"-- {message} --")
sys.stdout.flush()
print(f"{data}")
sys.stdout.flush()
print(f"-- end {message} --")
sys.stdout.flush()
class ProxyClient:
def __init__(self, args, channel):
self.args = args
self.channel = channel
def start(self):
self.channel.on("message")(self.on_message)
def on_message(self, message):
msg = json.loads(message)
key = msg["key"]
data = msg["data"]
log.debug("new connection messsage %s", key)
pc = RTCPeerConnection()
@pc.on("datachannel")
def on_channel(channel):
log.info("Sub channel established %s", key)
asyncio.ensure_future(self.handle_channel(channel))
async def finalize_connection():
obj = object_from_string(data)
if isinstance(obj, RTCSessionDescription):
await pc.setRemoteDescription(obj)
if obj.type == "offer":
# send answer
await pc.setLocalDescription(await pc.createAnswer())
msg = {"key": key, "data": object_to_string(pc.localDescription)}
self.channel.send(json.dumps(msg))
elif isinstance(obj, RTCIceCandidate):
await pc.addIceCandidate(obj)
elif obj is BYE:
log.warning("Exiting")
asyncio.ensure_future(finalize_connection())
async def handle_channel(self, channel):
try:
reader, writer = await asyncio.open_connection("127.0.0.1", self.args.port)
log.info("opened connection to port %s", self.args.port)
@channel.on("message")
def on_message(message):
log.debug("rtc to socket %r", message)
writer.write(message)
asyncio.ensure_future(writer.drain())
while True:
data = await reader.read(100)
if data:
log.debug("socket to rtc %r", data)
channel.send(data)
except Exception:
log.exception("WTF4")
class ProxyServer:
def __init__(self, args, channel):
self.args = args
self.channel = channel
self.connections = {}
async def start(self):
@self.channel.on("message")
def handle_message(message):
asyncio.ensure_future(self.handle_message(message))
self.server = await asyncio.start_server(
self.new_connection, "127.0.0.1", self.args.port
)
log.info("Listening on port %s", self.args.port)
async with self.server:
await self.server.serve_forever()
async def handle_message(self, message):
msg = json.loads(message)
key = msg["key"]
pc = self.connections[key].pc
channel = self.connections[key].channel
obj = object_from_string(msg["data"])
if isinstance(obj, RTCSessionDescription):
await pc.setRemoteDescription(obj)
if obj.type == "offer":
# send answer
await pc.setLocalDescription(await pc.createAnswer())
msg = {
"key": key,
"data": object_to_string(pc.localDescription),
}
self.channel.send(json.dumps(msg))
elif isinstance(obj, RTCIceCandidate):
await pc.addIceCandidate(obj)
elif obj is BYE:
print("Exiting")
async def new_connection(self, reader, writer):
try:
info = writer.get_extra_info("peername")
key = f"{info[0]}:{info[1]}"
log.info("Connection from %s", key)
pc = RTCPeerConnection()
channel = pc.createDataChannel("{key}")
async def readerproxy():
while True:
data = await reader.read(100)
if data:
log.debug("socket to rtc %r", data)
try:
channel.send(data)
except aiortc.exceptions.InvalidStateError:
log.error(
"Channel was in an invalid state %s, bailing reader coroutine",
key,
)
break
@channel.on("open")
def on_open():
asyncio.ensure_future(readerproxy())
@channel.on("message")
def on_message(message):
log.debug("rtc to socket %r", message)
writer.write(message)
asyncio.ensure_future(writer.drain())
self.connections[key] = ProxyConnection(pc, channel)
await pc.setLocalDescription(await pc.createOffer())
msg = {
"key": key,
"data": object_to_string(pc.localDescription),
}
log.debug("Send new offer")
self.channel.send(json.dumps(msg, sort_keys=True))
except Exception:
log.exception("WTF")
class ProxyConnection:
def __init__(self, pc, channel):
self.pc = pc
self.channel = channel
async def read_from_stdin():
loop = asyncio.get_event_loop()
line = await loop.run_in_executor(
None, input, "-- Please enter a message from remote party --\n"
)
data = line
while line:
try:
line = await loop.run_in_executor(None, input)
except EOFError:
break
data += line
print("-- Message received --")
return data
async def run_answer(pc, args):
"""
Top level offer answer server.
"""
@pc.on("datachannel")
def on_datachannel(channel):
log.info("Channel created")
client = ProxyClient(args, channel)
client.start()
data = await read_from_stdin()
data = base64.b64decode(data)
obj = object_from_string(data)
if isinstance(obj, RTCSessionDescription):
log.debug("received rtc session description")
await pc.setRemoteDescription(obj)
if obj.type == "offer":
await pc.setLocalDescription(await pc.createAnswer())
data = object_to_string(pc.localDescription)
data = base64.b64encode(data.encode())
data = os.linesep.join(textwrap.wrap(data.decode(), 80))
print_pastable(data, "reply")
elif isinstance(obj, RTCIceCandidate):
log.debug("received rtc ice candidate")
await pc.addIceCandidate(obj)
elif obj is BYE:
print("Exiting")
while True:
await asyncio.sleep(0.3)
async def run_offer(pc, args):
"""
Top level offer server this will estabilsh a data channel and start a tcp
server on the port provided. New connections to the server will start the
creation of a new rtc connectin and a new data channel used for proxying
the client's connection to the remote side.
"""
control_channel = pc.createDataChannel("main")
log.info("Created control channel.")
async def start_server():
"""
Start the proxy server. The proxy server will create a local port and
handle creation of additional rtc peer connections for each new client
to the proxy server port.
"""
server = ProxyServer(args, control_channel)
await server.start()
@control_channel.on("open")
def on_open():
"""
Start the proxy server when the control channel is connected.
"""
asyncio.ensure_future(start_server())
await pc.setLocalDescription(await pc.createOffer())
data = object_to_string(pc.localDescription).encode()
data = base64.b64encode(data)
data = os.linesep.join(textwrap.wrap(data.decode(), 80))
print_pastable(data, "offer")
data = await read_from_stdin()
data = base64.b64decode(data.encode())
obj = object_from_string(data)
if isinstance(obj, RTCSessionDescription):
log.debug("received rtc session description")
await pc.setRemoteDescription(obj)
if obj.type == "offer":
# send answer
await pc.setLocalDescription(await pc.createAnswer())
await signaling.send(pc.localDescription)
elif isinstance(obj, RTCIceCandidate):
log.debug("received rtc ice candidate")
await pc.addIceCandidate(obj)
elif obj is BYE:
print("Exiting")
while True:
await asyncio.sleep(0.3)
if __name__ == "__main__":
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
parser = argparse.ArgumentParser(description="Port proxy")
parser.add_argument("role", choices=["offer", "answer"])
parser.add_argument("--port", type=int, default=11224)
parser.add_argument("--verbose", "-v", action="count", default=None)
args = parser.parse_args()
if args.verbose is None:
logging.basicConfig(level=logging.WARNING)
elif args.verbose > 1:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
pc = RTCPeerConnection()
if args.role == "offer":
coro = run_offer(pc, args)
else:
coro = run_answer(pc, args)
# run event loop
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(coro)
except KeyboardInterrupt:
pass
finally:
loop.run_until_complete(pc.close())

View file

@ -41,9 +41,7 @@ jobs:
runs-on: ubuntu-22.04
environment: ci
outputs:
jobs: ${{ steps.define-jobs.outputs.jobs }}
changed-files: ${{ steps.process-changed-files.outputs.changed-files }}
testrun: ${{ steps.define-testrun.outputs.testrun }}
salt-version: ${{ steps.setup-salt-version.outputs.salt-version }}
cache-seed: ${{ steps.set-cache-seed.outputs.cache-seed }}
latest-release: ${{ steps.get-salt-releases.outputs.latest-release }}
@ -54,6 +52,8 @@ jobs:
config: ${{ steps.workflow-config.outputs.config }}
env:
LINUX_ARM_RUNNER: ${{ vars.LINUX_ARM_RUNNER }}
FULL_TESTRUN_SLUGS: ${{ vars.FULL_TESTRUN_SLUGS }}
PR_TESTRUN_SLUGS: ${{ vars.PR_TESTRUN_SLUGS }}
steps:
- uses: actions/checkout@v4
with:
@ -190,11 +190,6 @@ jobs:
run: |
echo '${{ steps.process-changed-files.outputs.changed-files }}' | jq -C '.'
- name: Define Jobs To Run
id: define-jobs
run: |
tools ci define-jobs ${{ github.event_name }} changed-files.json
- name: Get Salt Releases
id: get-salt-releases
env:
@ -209,23 +204,18 @@ jobs:
run: |
tools ci get-testing-releases ${{ join(fromJSON(steps.get-salt-releases.outputs.releases), ' ') }} --salt-version ${{ steps.setup-salt-version.outputs.salt-version }}
- name: Define Testrun
id: define-testrun
run: |
tools ci define-testrun ${{ github.event_name }} changed-files.json
- name: Define workflow config
id: workflow-config
run: |
tools ci workflow-config ${{ steps.setup-salt-version.outputs.salt-version }} ${{ github.event_name }} changed-files.json
- name: Check Contents of generated testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
run: |
cat testrun-changed-files.txt || true
- name: Upload testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
uses: actions/upload-artifact@v4
with:
name: testrun-changed-files.txt
@ -461,7 +451,7 @@ jobs:
build-pkgs-onedir:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -476,7 +466,7 @@ jobs:
linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }}
build-ci-deps:
name: CI Deps
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-deps-ci'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-deps-ci'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -504,7 +494,7 @@ jobs:
nox-version: 2022.8.7
python-version: "3.10"
cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.15
skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }}
skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] }}
testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }}
matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['pkg-test-matrix']) }}
linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }}
@ -519,10 +509,10 @@ jobs:
nox-session: ci-test-onedir
nox-version: 2022.8.7
python-version: "3.10"
testrun: ${{ needs.prepare-workflow.outputs.testrun }}
testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }}
salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}"
cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.15
skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }}
skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] }}
workflow-slug: ci
default-timeout: 180
matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['test-matrix']) }}
@ -530,7 +520,7 @@ jobs:
combine-all-code-coverage:
name: Combine Code Coverage
if: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] == false }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] == false }}
runs-on: ubuntu-22.04
env:
PIP_INDEX_URL: https://pypi.org/simple
@ -656,7 +646,6 @@ jobs:
retention-days: 7
if-no-files-found: error
include-hidden-files: true
set-pipeline-exit-status:
# This step is just so we can make github require this step, to pass checks
# on a pull request instead of requiring all
@ -688,8 +677,3 @@ jobs:
else
exit 0
fi
- name: Done
if: always()
run:
echo "All worflows finished"

View file

@ -98,9 +98,7 @@ jobs:
needs:
- workflow-requirements
outputs:
jobs: ${{ steps.define-jobs.outputs.jobs }}
changed-files: ${{ steps.process-changed-files.outputs.changed-files }}
testrun: ${{ steps.define-testrun.outputs.testrun }}
salt-version: ${{ steps.setup-salt-version.outputs.salt-version }}
cache-seed: ${{ steps.set-cache-seed.outputs.cache-seed }}
latest-release: ${{ steps.get-salt-releases.outputs.latest-release }}
@ -111,6 +109,8 @@ jobs:
config: ${{ steps.workflow-config.outputs.config }}
env:
LINUX_ARM_RUNNER: ${{ vars.LINUX_ARM_RUNNER }}
FULL_TESTRUN_SLUGS: ${{ vars.FULL_TESTRUN_SLUGS }}
PR_TESTRUN_SLUGS: ${{ vars.PR_TESTRUN_SLUGS }}
steps:
- uses: actions/checkout@v4
with:
@ -247,11 +247,6 @@ jobs:
run: |
echo '${{ steps.process-changed-files.outputs.changed-files }}' | jq -C '.'
- name: Define Jobs To Run
id: define-jobs
run: |
tools ci define-jobs${{ inputs.skip-salt-test-suite && ' --skip-tests' || '' }}${{ inputs.skip-salt-pkg-test-suite && ' --skip-pkg-tests' || '' }} ${{ github.event_name }} changed-files.json
- name: Get Salt Releases
id: get-salt-releases
env:
@ -266,23 +261,18 @@ jobs:
run: |
tools ci get-testing-releases ${{ join(fromJSON(steps.get-salt-releases.outputs.releases), ' ') }} --salt-version ${{ steps.setup-salt-version.outputs.salt-version }}
- name: Define Testrun
id: define-testrun
run: |
tools ci define-testrun ${{ github.event_name }} changed-files.json
- name: Define workflow config
id: workflow-config
run: |
tools ci workflow-config${{ inputs.skip-salt-test-suite && ' --skip-tests' || '' }}${{ inputs.skip-salt-pkg-test-suite && ' --skip-pkg-tests' || '' }} ${{ steps.setup-salt-version.outputs.salt-version }} ${{ github.event_name }} changed-files.json
- name: Check Contents of generated testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
run: |
cat testrun-changed-files.txt || true
- name: Upload testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
uses: actions/upload-artifact@v4
with:
name: testrun-changed-files.txt
@ -518,7 +508,7 @@ jobs:
build-pkgs-onedir:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -538,7 +528,7 @@ jobs:
build-pkgs-src:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -557,7 +547,7 @@ jobs:
secrets: inherit
build-ci-deps:
name: CI Deps
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-deps-ci'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-deps-ci'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -600,7 +590,7 @@ jobs:
nox-session: ci-test-onedir
nox-version: 2022.8.7
python-version: "3.10"
testrun: ${{ needs.prepare-workflow.outputs.testrun }}
testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }}
salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}"
cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.15
skip-code-coverage: true
@ -608,7 +598,6 @@ jobs:
default-timeout: 360
matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['test-matrix']) }}
linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }}
set-pipeline-exit-status:
# This step is just so we can make github require this step, to pass checks
# on a pull request instead of requiring all
@ -643,8 +632,3 @@ jobs:
else
exit 0
fi
- name: Done
if: always()
run:
echo "All worflows finished"

View file

@ -418,7 +418,6 @@ jobs:
TWINE_PASSWORD: "${{ steps.get-secrets.outputs.twine-password }}"
run: |
tools pkg pypi-upload artifacts/release/salt-${{ needs.prepare-workflow.outputs.salt-version }}.tar.gz
set-pipeline-exit-status:
# This step is just so we can make github require this step, to pass checks
# on a pull request instead of requiring all
@ -454,8 +453,3 @@ jobs:
else
exit 0
fi
- name: Done
if: always()
run:
echo "All worflows finished"

View file

@ -88,9 +88,7 @@ jobs:
needs:
- workflow-requirements
outputs:
jobs: ${{ steps.define-jobs.outputs.jobs }}
changed-files: ${{ steps.process-changed-files.outputs.changed-files }}
testrun: ${{ steps.define-testrun.outputs.testrun }}
salt-version: ${{ steps.setup-salt-version.outputs.salt-version }}
cache-seed: ${{ steps.set-cache-seed.outputs.cache-seed }}
latest-release: ${{ steps.get-salt-releases.outputs.latest-release }}
@ -101,6 +99,8 @@ jobs:
config: ${{ steps.workflow-config.outputs.config }}
env:
LINUX_ARM_RUNNER: ${{ vars.LINUX_ARM_RUNNER }}
FULL_TESTRUN_SLUGS: ${{ vars.FULL_TESTRUN_SLUGS }}
PR_TESTRUN_SLUGS: ${{ vars.PR_TESTRUN_SLUGS }}
steps:
- uses: actions/checkout@v4
with:
@ -237,11 +237,6 @@ jobs:
run: |
echo '${{ steps.process-changed-files.outputs.changed-files }}' | jq -C '.'
- name: Define Jobs To Run
id: define-jobs
run: |
tools ci define-jobs ${{ github.event_name }} changed-files.json
- name: Get Salt Releases
id: get-salt-releases
env:
@ -256,23 +251,18 @@ jobs:
run: |
tools ci get-testing-releases ${{ join(fromJSON(steps.get-salt-releases.outputs.releases), ' ') }} --salt-version ${{ steps.setup-salt-version.outputs.salt-version }}
- name: Define Testrun
id: define-testrun
run: |
tools ci define-testrun ${{ github.event_name }} changed-files.json
- name: Define workflow config
id: workflow-config
run: |
tools ci workflow-config ${{ steps.setup-salt-version.outputs.salt-version }} ${{ github.event_name }} changed-files.json
- name: Check Contents of generated testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
run: |
cat testrun-changed-files.txt || true
- name: Upload testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
uses: actions/upload-artifact@v4
with:
name: testrun-changed-files.txt
@ -508,7 +498,7 @@ jobs:
build-pkgs-onedir:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -523,7 +513,7 @@ jobs:
linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }}
build-ci-deps:
name: CI Deps
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-deps-ci'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-deps-ci'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -566,7 +556,7 @@ jobs:
nox-session: ci-test-onedir
nox-version: 2022.8.7
python-version: "3.10"
testrun: ${{ needs.prepare-workflow.outputs.testrun }}
testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }}
salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}"
cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.15
skip-code-coverage: true
@ -574,7 +564,6 @@ jobs:
default-timeout: 360
matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['test-matrix']) }}
linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }}
set-pipeline-exit-status:
# This step is just so we can make github require this step, to pass checks
# on a pull request instead of requiring all
@ -607,8 +596,3 @@ jobs:
else
exit 0
fi
- name: Done
if: always()
run:
echo "All worflows finished"

40
.github/workflows/ssh-debug.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: SSH Debug
run-name: "SSH Debug ${{ inputs.runner }}"
on:
workflow_dispatch:
inputs:
runner:
type: string
required: True
description: The runner to start a tunnel on.
offer:
type: string
required: True
description: SDP Offer
public_key:
type: string
required: True
description: Your public key for ssh access.
debug:
required: false
type: boolean
default: false
description: Run sshd with debug enabled.
jobs:
debug:
runs-on: ${{ inputs.runner }}
if: ${{ inputs.runner }}
environment: ci
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- uses: ./.github/actions/ssh-tunnel
with:
public_key: ${{ inputs.public_key }}
offer: ${{ inputs.offer }}
debug: ${{ inputs.debug }}

View file

@ -71,9 +71,7 @@ jobs:
needs:
- check-requirements
outputs:
jobs: ${{ steps.define-jobs.outputs.jobs }}
changed-files: ${{ steps.process-changed-files.outputs.changed-files }}
testrun: ${{ steps.define-testrun.outputs.testrun }}
salt-version: ${{ steps.setup-salt-version.outputs.salt-version }}
cache-seed: ${{ steps.set-cache-seed.outputs.cache-seed }}
latest-release: ${{ steps.get-salt-releases.outputs.latest-release }}
@ -84,6 +82,8 @@ jobs:
config: ${{ steps.workflow-config.outputs.config }}
env:
LINUX_ARM_RUNNER: ${{ vars.LINUX_ARM_RUNNER }}
FULL_TESTRUN_SLUGS: ${{ vars.FULL_TESTRUN_SLUGS }}
PR_TESTRUN_SLUGS: ${{ vars.PR_TESTRUN_SLUGS }}
steps:
- uses: actions/checkout@v4
with:
@ -229,11 +229,6 @@ jobs:
run: |
echo '${{ steps.process-changed-files.outputs.changed-files }}' | jq -C '.'
- name: Define Jobs To Run
id: define-jobs
run: |
tools ci define-jobs${{ inputs.skip-salt-test-suite && ' --skip-tests' || '' }}${{ inputs.skip-salt-pkg-test-suite && ' --skip-pkg-tests' || '' }}${{ inputs.skip-salt-pkg-download-test-suite && ' --skip-pkg-download-tests' || '' }} ${{ github.event_name }} changed-files.json
- name: Get Salt Releases
id: get-salt-releases
env:
@ -248,23 +243,18 @@ jobs:
run: |
tools ci get-testing-releases ${{ join(fromJSON(steps.get-salt-releases.outputs.releases), ' ') }} --salt-version ${{ steps.setup-salt-version.outputs.salt-version }}
- name: Define Testrun
id: define-testrun
run: |
tools ci define-testrun ${{ github.event_name }} changed-files.json
- name: Define workflow config
id: workflow-config
run: |
tools ci workflow-config${{ inputs.skip-salt-test-suite && ' --skip-tests' || '' }}${{ inputs.skip-salt-pkg-test-suite && ' --skip-pkg-tests' || '' }}${{ inputs.skip-salt-pkg-download-test-suite && ' --skip-pkg-download-tests' || '' }} ${{ steps.setup-salt-version.outputs.salt-version }} ${{ github.event_name }} changed-files.json
- name: Check Contents of generated testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
run: |
cat testrun-changed-files.txt || true
- name: Upload testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
uses: actions/upload-artifact@v4
with:
name: testrun-changed-files.txt
@ -501,7 +491,7 @@ jobs:
build-pkgs-onedir:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -521,7 +511,7 @@ jobs:
build-pkgs-src:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -540,7 +530,7 @@ jobs:
secrets: inherit
build-ci-deps:
name: CI Deps
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-deps-ci'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-deps-ci'] }}
needs:
- prepare-workflow
- build-salt-onedir
@ -583,7 +573,7 @@ jobs:
nox-session: ci-test-onedir
nox-version: 2022.8.7
python-version: "3.10"
testrun: ${{ needs.prepare-workflow.outputs.testrun }}
testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }}
salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}"
cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.15
skip-code-coverage: true
@ -701,7 +691,6 @@ jobs:
salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}"
matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['artifact-matrix']) }}
build-matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }}
set-pipeline-exit-status:
# This step is just so we can make github require this step, to pass checks
# on a pull request instead of requiring all
@ -735,8 +724,3 @@ jobs:
else
exit 0
fi
- name: Done
if: always()
run:
echo "All worflows finished"

View file

@ -4,7 +4,7 @@
<%- do test_salt_linux_needs.append("build-ci-deps") %>
name: CI Deps
<%- if workflow_slug != 'release' %>
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-deps-ci'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-deps-ci'] }}
<%- endif %>
needs:
- prepare-workflow

View file

@ -11,7 +11,7 @@
<{ job_name }>:
name: Build Packages
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['build-pkgs'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['build-pkgs'] }}
needs:
- prepare-workflow
- build-salt-onedir

View file

@ -306,7 +306,7 @@
combine-all-code-coverage:
<%- do conclusion_needs.append("combine-all-code-coverage") %>
name: Combine Code Coverage
if: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] == false }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] == false }}
runs-on: ubuntu-22.04
env:
PIP_INDEX_URL: https://pypi.org/simple

View file

@ -5,7 +5,7 @@
<%- set prepare_workflow_skip_pkg_test_suite = prepare_workflow_skip_pkg_test_suite|default("") %>
<%- set prepare_workflow_skip_pkg_download_test_suite = prepare_workflow_skip_pkg_download_test_suite|default("") %>
<%- set prepare_workflow_salt_version_input = prepare_workflow_salt_version_input|default("") %>
<%- set skip_test_coverage_check = skip_test_coverage_check|default("${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }}") %>
<%- set skip_test_coverage_check = skip_test_coverage_check|default("${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] }}") %>
<%- set gpg_key_id = "64CBBC8173D76B3F" %>
<%- set prepare_actual_release = prepare_actual_release | default(False) %>
<%- set gh_actions_workflows_python_version = "3.10" %>
@ -89,9 +89,7 @@ jobs:
<%- endfor %>
<%- endif %>
outputs:
jobs: ${{ steps.define-jobs.outputs.jobs }}
changed-files: ${{ steps.process-changed-files.outputs.changed-files }}
testrun: ${{ steps.define-testrun.outputs.testrun }}
salt-version: ${{ steps.setup-salt-version.outputs.salt-version }}
cache-seed: ${{ steps.set-cache-seed.outputs.cache-seed }}
latest-release: ${{ steps.get-salt-releases.outputs.latest-release }}
@ -102,6 +100,8 @@ jobs:
config: ${{ steps.workflow-config.outputs.config }}
env:
LINUX_ARM_RUNNER: ${{ vars.LINUX_ARM_RUNNER }}
FULL_TESTRUN_SLUGS: ${{ vars.FULL_TESTRUN_SLUGS }}
PR_TESTRUN_SLUGS: ${{ vars.PR_TESTRUN_SLUGS }}
steps:
- uses: actions/checkout@v4
with:
@ -252,13 +252,6 @@ jobs:
run: |
echo '${{ steps.process-changed-files.outputs.changed-files }}' | jq -C '.'
- name: Define Jobs To Run
id: define-jobs
run: |
tools ci define-jobs<{ prepare_workflow_skip_test_suite }><{
prepare_workflow_skip_pkg_test_suite }><{ prepare_workflow_skip_pkg_download_test_suite
}> ${{ github.event_name }} changed-files.json
- name: Get Salt Releases
id: get-salt-releases
env:
@ -273,11 +266,6 @@ jobs:
run: |
tools ci get-testing-releases ${{ join(fromJSON(steps.get-salt-releases.outputs.releases), ' ') }} --salt-version ${{ steps.setup-salt-version.outputs.salt-version }}
- name: Define Testrun
id: define-testrun
run: |
tools ci define-testrun ${{ github.event_name }} changed-files.json
- name: Define workflow config
id: workflow-config
run: |
@ -286,12 +274,12 @@ jobs:
}> ${{ steps.setup-salt-version.outputs.salt-version }} ${{ github.event_name }} changed-files.json
- name: Check Contents of generated testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
run: |
cat testrun-changed-files.txt || true
- name: Upload testrun-changed-files.txt
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['type'] != 'full' }}
if: ${{ fromJSON(steps.workflow-config.outputs.config)['testrun']['type'] != 'full' }}
uses: actions/upload-artifact@v4
with:
name: testrun-changed-files.txt
@ -305,18 +293,18 @@ jobs:
{# We can't yet use tokenless uploads with the codecov CLI
- name: Install Codecov CLI
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['skip_code_coverage'] == false }}
if: ${{ fromJSON(steps.define-testrun.outputs.config)['skip_code_coverage'] == false }}
run: |
python3 -m pip install codecov-cli
- name: Save Commit Metadata In Codecov
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['skip_code_coverage'] == false }}
if: ${{ fromJSON(steps.define-testrun.outputs.config)['skip_code_coverage'] == false }}
run: |
codecovcli --auto-load-params-from GithubActions --verbose --token ${{ secrets.CODECOV_TOKEN }} \
create-commit --git-service github --sha ${{ github.sha }}
- name: Create Codecov Coverage Report
if: ${{ fromJSON(steps.define-testrun.outputs.testrun)['skip_code_coverage'] == false }}
if: ${{ fromJSON(steps.define-testrun.outputs.config)['skip_code_coverage'] == false }}
run: |
codecovcli --auto-load-params-from GithubActions --verbose --token ${{ secrets.CODECOV_TOKEN }} \
create-report --git-service github --sha ${{ github.sha }}
@ -327,7 +315,6 @@ jobs:
<%- endif %>
<%- endblock jobs %>
set-pipeline-exit-status:
# This step is just so we can make github require this step, to pass checks
# on a pull request instead of requiring all
@ -373,8 +360,3 @@ jobs:
else
exit 0
fi
- name: Done
if: always()
run:
echo "All worflows finished"

View file

@ -6,7 +6,7 @@
<%- do conclusion_needs.append(job_name) %>
name: Package Downloads
<%- if gh_environment == "staging" %>
if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test-pkg-download'] }}
if: ${{ fromJSON(needs.prepare-workflow.outputs.config)['jobs']['test-pkg-download'] }}
<%- else %>
if: ${{ inputs.skip-salt-pkg-download-test-suite == false }}
<%- endif %>

View file

@ -14,7 +14,7 @@
nox-session: ci-test-onedir
nox-version: <{ nox_version }>
python-version: "<{ gh_actions_workflows_python_version }>"
testrun: ${{ needs.prepare-workflow.outputs.testrun }}
testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }}
salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}"
cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|<{ python_version }>
skip-code-coverage: <{ skip_test_coverage_check }>

View file

@ -71,7 +71,7 @@ jobs:
test-linux:
name: ${{ matrix.display_name }} ${{ matrix.tests-chunk }} ${{ matrix.transport }}${{ matrix.fips && '(fips)' || '' }}${{ matrix.test-group && ' ' || '' }}${{ matrix.test-group && matrix.test-group || '' }}
runs-on: ${{ matrix.arch == 'x86_64' && 'ubuntu-24.04' || inputs.linux_arm_runner }}
if: ${{ toJSON(fromJSON(inputs.matrix)['linux']) != '[]' }}
if: toJSON(fromJSON(inputs.matrix)['linux-x86_64']) != '[]'
# Full test runs. Each chunk should never take more than 2 hours.
# Partial test runs(no chunk parallelization), 6 Hours
timeout-minutes: ${{ fromJSON(inputs.testrun)['type'] == 'full' && inputs.default-timeout || 360 }}
@ -387,7 +387,7 @@ jobs:
test-linux-arm64:
name: ${{ matrix.display_name }} ${{ matrix.tests-chunk }} ${{ matrix.transport }}${{ matrix.fips && '(fips)' || '' }}${{ matrix.test-group && ' ' || '' }}${{ matrix.test-group && matrix.test-group || '' }}
runs-on: ${{ matrix.arch == 'x86_64' && 'ubuntu-22.04' || inputs.linux_arm_runner }}
if: ${{ toJSON(fromJSON(inputs.matrix)['linux']) != '[]' }}
if: toJSON(fromJSON(inputs.matrix)['linux-arm64']) != '[]'
# Full test runs. Each chunk should never take more than 2 hours.
# Partial test runs(no chunk parallelization), 6 Hours
timeout-minutes: ${{ fromJSON(inputs.testrun)['type'] == 'full' && inputs.default-timeout || 360 }}
@ -705,7 +705,7 @@ jobs:
runs-on: ${{ matrix.runner }}
# Full test runs. Each chunk should never take more than 2 hours.
# Partial test runs(no chunk parallelization), 6 Hours
if: ${{ toJSON(fromJSON(inputs.matrix)['macos']) != '[]' }}
if: toJSON(fromJSON(inputs.matrix)['macos']) != '[]'
timeout-minutes: ${{ fromJSON(inputs.testrun)['type'] == 'full' && inputs.default-timeout || 360 }}
strategy:
fail-fast: false
@ -983,7 +983,7 @@ jobs:
test-windows:
name: ${{ matrix.display_name }} ${{ matrix.tests-chunk }} ${{ matrix.transport }}${{ matrix.test-group && ' ' || '' }}${{ matrix.test-group && matrix.test-group || '' }}
if: ${{ toJSON(fromJSON(inputs.matrix)['windows']) != '[]' }}
if: toJSON(fromJSON(inputs.matrix)['windows']) != '[]'
runs-on: ${{ matrix.slug }}
# Full test runs. Each chunk should never take more than 2 hours.
# Partial test runs(no chunk parallelization), 6 Hours

View file

@ -125,6 +125,10 @@ jobs:
with:
name: nox-linux-${{ matrix.arch }}-${{ inputs.nox-session }}
- name: "Ensure docker is running"
run: |
sudo systemctl start containerd || exit 0
- name: "Pull container ${{ matrix.container }}"
run: |
docker pull ${{ matrix.container }}

30
.github/workflows/workflow-finished.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Workflow Finished
run-name: Workflow Finished ${{ github.event.workflow_run.display_title }} (${{ github.event.workflow_run.conclusion }})
on:
workflow_run:
workflows:
- CI
- Nightly
- Scheduled
- Stage Release
types:
- completed
permissions:
contents: read
pull-requests: read
actions: write
jobs:
restart-failed-jobs:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.run_attempt < 5 }}
steps:
- name: Restart failed jobs
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
gh run rerun ${{ github.event.workflow_run.id }} --failed

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

@ -0,0 +1,2 @@
Fixes an issue with the LGPO module when trying to parse ADMX/ADML files
that have a space in the XMLNS url in the policyDefinitionsResources header.

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

@ -0,0 +1 @@
Update for deprecation of hex in pygit2 1.15.0 and above

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

@ -0,0 +1 @@
Fixed blob path for salt.ufw in the firewall tutorial documentation

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

@ -0,0 +1 @@
Update locations for bootstrap scripts, to new infrastructure, GitHub releases for bootstrap

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

@ -0,0 +1 @@
Recognise newer AMD GPU devices

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

@ -0,0 +1,2 @@
Fixed an issue with making changes to the Windows Firewall when the
AllowInboundRules setting is set to True

View file

@ -4,6 +4,14 @@ relenv_version: "0.18.0"
release_branches:
- "3006.x"
- "3007.x"
mandatory_os_slugs:
- ubuntu-22.04
- ubuntu-22.04-arm64
pr-testrun-slugs:
- ubuntu-24.04-pkg
- ubuntu-24.04
- rockylinux-9
- rockylinux-9-pkg
- windows-2022
- windows-2022-pkg
- macos-15
- macos-15-pkg
full-testrun-slugs:
- all

View file

@ -147,8 +147,7 @@ Install or upgrade Salt
-----------------------
Ensure your Salt masters are running at least Salt version 3004. For instructions
on installing or upgrading Salt, see the
`Salt Install Guide <https://docs.saltproject.io/salt/install-guide/en/latest/>`__.
`Salt Install Guide <https://docs.saltproject.io/salt/install-guide/en/latest/>`_.
.. _delta-proxy-install:

View file

@ -174,8 +174,8 @@ exception is raised, causing the rendering to fail with the following message:
TemplateError: Custom Error
Filters
=======
Custom Filters
==============
Saltstack extends `builtin filters`_ with these custom filters:
@ -405,8 +405,9 @@ This text will be wrapped in quotes.
.. versionadded:: 2017.7.0
Scan through string looking for a location where this regular expression
produces a match. Returns ``None`` in case there were no matches found
Looks for a match for the specified regex anywhere in the string. If the string
does not match the regex, this filter returns ``None``. If the string _does_
match the regex, then the `capture groups`_ for the regex will be returned.
Example:
@ -420,6 +421,29 @@ Returns:
("defabcdef",)
If the regex you use does not contain a capture group then the number of
capture groups will be zero, and a matching regex will return an empty tuple.
This means that the following ``if`` statement would evaluate as ``False``:
.. code-block:: jinja
{%- if 'foobar' | regex_search('foo') %}
If you do not need a capture group and are just looking to test if a string
matches a regex, then you should check to see if the filter returns ``None``:
.. code-block:: jinja
{%- if (some_var | regex_search('foo')) is not none %}
.. note::
In a Jinja statement, a null value (i.e. a Python ``None``) should be
expressed as ``none`` (i.e. lowercase). More info on this can be found in
the **Note** section here in the `jinja docs`_.
.. _`capture groups`: https://docs.python.org/3/library/re.html#re.Match.groups
.. _`jinja docs`: https://jinja.palletsprojects.com/en/stable/templates/#literals
.. jinja_ref:: regex_match
@ -428,8 +452,8 @@ Returns:
.. versionadded:: 2017.7.0
If zero or more characters at the beginning of string match this regular
expression, otherwise returns ``None``.
Works exactly like :jinja_ref:`regex_search`, but only checks for matches at
the _beginning_ of the string passed into this filter.
Example:

View file

@ -176,7 +176,7 @@ to allow traffic on ``tcp/4505`` and ``tcp/4506``:
**Ubuntu**
Salt installs firewall rules in :blob:`/etc/ufw/applications.d/salt.ufw
<pkg/salt.ufw>`. Enable with:
<pkg/common/salt.ufw>`. Enable with:
.. code-block:: bash

View file

@ -176,13 +176,10 @@ $BUILD_DIR = "$SCRIPT_DIR\buildenv"
$RELENV_DIR = "${env:LOCALAPPDATA}\relenv"
$SYS_PY_BIN = (python -c "import sys; print(sys.executable)")
$BLD_PY_BIN = "$BUILD_DIR\Scripts\python.exe"
$SALT_DEP_URL = "https://repo.saltproject.io/windows/dependencies"
if ( $Architecture -eq "x64" ) {
$SALT_DEP_URL = "$SALT_DEP_URL/64"
$ARCH = "amd64"
} else {
$SALT_DEP_URL = "$SALT_DEP_URL/32"
$ARCH = "x86"
}
@ -249,7 +246,7 @@ if ( $env:VIRTUAL_ENV ) {
#-------------------------------------------------------------------------------
# Installing Relenv
#-------------------------------------------------------------------------------
Write-Host "Installing Relenv: " -NoNewLine
Write-Host "Installing Relenv ($RelenvVersion): " -NoNewLine
pip install relenv==$RelenvVersion --disable-pip-version-check | Out-Null
$output = pip list --disable-pip-version-check
if ("relenv" -in $output.split()) {

View file

@ -81,11 +81,6 @@ $ARCH = $(. $PYTHON_BIN -c "import platform; print(platform.architectur
# Script Variables
$PROJECT_DIR = $(git rev-parse --show-toplevel)
$SALT_DEPS = "$PROJECT_DIR\requirements\static\pkg\py$PY_VERSION\windows.txt"
if ( $ARCH -eq "64bit" ) {
$SALT_DEP_URL = "https://repo.saltproject.io/windows/dependencies/64"
} else {
$SALT_DEP_URL = "https://repo.saltproject.io/windows/dependencies/32"
}
if ( ! $SkipInstall ) {
#-------------------------------------------------------------------------------

View file

@ -1,219 +0,0 @@
:: ############################################################################
::
:: FILE: sign.bat
::
:: DESCRIPTION: Signing and Hashing script for Salt builds on Windows.
:: Requires an official Code Signing Certificate and drivers
:: installed to sign the files. Generates hashes in MD5 and
:: SHA256 in a file of the same name with a `.md5` or
:: `.sha256` extension.
::
:: NOTE: This script is used internally by SaltStack to sign and
:: hash Windows Installer builds and uses resources not
:: available to the community, such as SaltStack's Code
:: Signing Certificate. It is placed here for version
:: control.
::
:: COPYRIGHT: (c) 2012-2018 by the SaltStack Team
::
:: LICENSE: Apache 2.0
:: ORGANIZATION: SaltStack, Inc (saltstack.com)
:: CREATED: 2017
::
:: ############################################################################
::
:: USAGE: The script must be located in a directory that has the installer
:: files in a sub-folder named with the major version, ie: `2018.3`.
:: Insert the key fob that contains the code signing certificate. Run
:: the script passing the full version: `.\sign.bat 2018.3.1`.
::
:: The script will sign the installers and generate the corresponding
:: hash files. These can then be uploaded to the salt repo.
::
:: The files must be in the following format:
:: <Series>\Salt-Minion-<Version>-<Python Version>-<System Architecture>-Setup.exe
:: So, for a Salt Minion installer for 2018.3.1 on AMD64 for Python 3
:: file would be placed in a subdirectory named `2018.3` and the file
:: would be named: `Salt-Minion-2018.3.1-Py3-AMD64-Setup.exe`. This
:: is how the file is created by the NSI Script anyway.
::
:: You can test the timestamp server with the following command:
:: curl -i timestamp.digicert.com/timestamp/health
::
:: REQUIREMENTS: This script requires the ``signtool.exe`` binary that is a part
:: of the Windows SDK. To install just the ``signtool.exe``:
::
:: OPTION 1:
:: 1. Download the Windows 10 SDK ISO:
:: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
:: 2. Mount the ISO and browse to the ``Installers`` directory
:: 3. Run the ``Windows SDK Signing Tools-x86_en-us.msi``
::
:: OPTION 2:
:: 1. Download the Visual Studio BUild Tools:
:: https://aka.ms/vs/15/release/vs_buildtools.exe
:: 2. Run the following command:
:: vs_buildtools.exe --quiet --add Microsoft.Component.ClickOnce.MSBuild
::
:: ############################################################################
@ echo off
if [%1]==[] (
echo You must pass a version
goto quit
) else (
set "Version=%~1"
)
set Series=%Version:~0,4%
if not exist .\%Series%\ (
echo - Series %Series% is not valid
exit 1
)
:: Sign Installer Files
echo ===========================================================================
echo Signing...
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
signtool.exe sign /a /t http://timestamp.digicert.com ^
"%Series%\Salt-Minion-%Version%-AMD64-Setup.exe" ^
"%Series%\Salt-Minion-%Version%-x86-Setup.exe" ^
"%Series%\Salt-%Version%-AMD64-Setup.exe" ^
"%Series%\Salt-%Version%-x86-Setup.exe" ^
"%Series%\Salt-%Version%-Py2-AMD64-Setup.exe" ^
"%Series%\Salt-%Version%-Py2-x86-Setup.exe" ^
"%Series%\Salt-%Version%-Py3-AMD64-Setup.exe" ^
"%Series%\Salt-%Version%-Py3-x86-Setup.exe" ^
"%Series%\Salt-Minion-%Version%-Py2-AMD64-Setup.exe" ^
"%Series%\Salt-Minion-%Version%-Py2-x86-Setup.exe" ^
"%Series%\Salt-Minion-%Version%-Py3-AMD64-Setup.exe" ^
"%Series%\Salt-Minion-%Version%-Py3-x86-Setup.exe" ^
"%Series%\Salt-Minion-%Version%-Py3-AMD64.msi" ^
"%Series%\Salt-Minion-%Version%-Py3-x86.msi"
echo %ERRORLEVEL%
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
echo Signing Complete
echo ===========================================================================
:: Create Hash files
echo ===========================================================================
echo Creating Hashes...
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
set "file_name=Salt-Minion-%Version%-AMD64-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-x86-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-%Version%-AMD64-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-%Version%-x86-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-%Version%-Py2-AMD64-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-%Version%-Py2-x86-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-%Version%-Py3-AMD64-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-%Version%-Py3-x86-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-Py2-AMD64-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-Py2-x86-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-Py3-AMD64-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-Py3-x86-Setup.exe"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-Py3-AMD64.msi"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
set "file_name=Salt-Minion-%Version%-Py3-x86.msi"
set "file=.\%Series%\%file_name%"
if exist "%file%" (
echo - %file_name%
powershell -c "$hash = (Get-FileHash -Algorithm MD5 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.md5\" -NoNewLine -Encoding ASCII"
powershell -c "$hash = (Get-FileHash -Algorithm SHA256 \"%file%\").Hash; Out-File -InputObject $hash\" %file_name%\" -FilePath \"%file%.sha256\" -NoNewLine -Encoding ASCII"
)
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
echo Hashing Complete
echo ===========================================================================
:quit

View file

@ -1,27 +0,0 @@
#!/bin/bash
# This legacy script pre-dates the salt-bootstrap project. In most cases, the
# bootstrap-salt.sh script is the recommended script for installing salt onto
# a new minion. However, that may not be appropriate for all situations. This
# script remains to help fill those needs, and to provide an example for users
# needing to write their own deploy scripts.
rpm -Uvh --force http://mirrors.kernel.org/fedora-epel/5/x86_64/epel-release-5-4.noarch.rpm
yum install -y salt-minion git
rm -rf /usr/lib/python2.6/site-packages/salt*
rm -rf /usr/bin/salt-*
mkdir -p /root/git
cd /root/git
git clone git://github.com/saltstack/salt.git
cd salt
python26 setup.py install
cd
mkdir -p /etc/salt/pki
echo '{{ vm['priv_key'] }}' > /etc/salt/pki/minion.pem
echo '{{ vm['pub_key'] }}' > /etc/salt/pki/minion.pub
cat > /etc/salt/minion <<EOF
{{minion}}
EOF
/sbin/chkconfig salt-minion on
service salt-minion start

View file

@ -1,19 +0,0 @@
#!/bin/bash
# This legacy script pre-dates the salt-bootstrap project. In most cases, the
# bootstrap-salt.sh script is the recommended script for installing salt onto
# a new minion. However, that may not be appropriate for all situations. This
# script remains to help fill those needs, and to provide an example for users
# needing to write their own deploy scripts.
rpm -Uvh --force http://mirrors.kernel.org/fedora-epel/5/x86_64/epel-release-5-4.noarch.rpm
yum install -y salt-minion
mkdir -p /etc/salt/pki
echo '{{ vm['priv_key'] }}' > /etc/salt/pki/minion.pem
echo '{{ vm['pub_key'] }}' > /etc/salt/pki/minion.pub
cat > /etc/salt/minion <<EOF
{{minion}}
EOF
/sbin/chkconfig salt-minion on
service salt-minion start

View file

@ -1,27 +0,0 @@
#!/bin/bash
# This legacy script pre-dates the salt-bootstrap project. In most cases, the
# bootstrap-salt.sh script is the recommended script for installing salt onto
# a new minion. However, that may not be appropriate for all situations. This
# script remains to help fill those needs, and to provide an example for users
# needing to write their own deploy scripts.
rpm -Uvh --force http://mirrors.kernel.org/fedora-epel/6/x86_64/epel-release-6-8.noarch.rpm
yum -y install salt-minion git --enablerepo epel-testing
rm -rf /usr/lib/python/site-packages/salt*
rm -rf /usr/bin/salt-*
mkdir -p /root/git
cd /root/git
git clone git://github.com/saltstack/salt.git
cd salt
python setup.py install
cd
mkdir -p /etc/salt/pki
echo '{{ vm['priv_key'] }}' > /etc/salt/pki/minion.pem
echo '{{ vm['pub_key'] }}' > /etc/salt/pki/minion.pub
cat > /etc/salt/minion <<EOF
{{minion}}
EOF
/sbin/chkconfig salt-minion on
service salt-minion start

View file

@ -1,19 +0,0 @@
#!/bin/bash
# This legacy script pre-dates the salt-bootstrap project. In most cases, the
# bootstrap-salt.sh script is the recommended script for installing salt onto
# a new minion. However, that may not be appropriate for all situations. This
# script remains to help fill those needs, and to provide an example for users
# needing to write their own deploy scripts.
rpm -Uvh --force http://mirrors.kernel.org/fedora-epel/6/x86_64/epel-release-6-8.noarch.rpm
yum -y install salt-minion --enablerepo epel-testing
mkdir -p /etc/salt/pki
echo '{{ vm['priv_key'] }}' > /etc/salt/pki/minion.pem
echo '{{ vm['pub_key'] }}' > /etc/salt/pki/minion.pub
cat > /etc/salt/minion <<EOF
{{minion}}
EOF
/sbin/chkconfig salt-minion on
service salt-minion start

File diff suppressed because it is too large Load diff

View file

@ -7,11 +7,11 @@
#
# It has been designed as an example, to be customized for your own needs.
curl -L https://bootstrap.saltstack.com | sudo sh -s -- "$@" git develop
curl -L https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh -s -- "$@" git develop
# By default, Salt Cloud now places the minion's keys and configuration in
# /tmp/.saltcloud/ before executing the deploy script. After it has executed,
# these temporary files are removed. If you don't want salt-bootstrap to handle
# these files, comment out the above command, and uncomment the below command.
#curl -L https://bootstrap.saltstack.com | sudo sh -s git develop
#curl -L https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh -s git develop

View file

@ -7,11 +7,11 @@
#
# It has been designed as an example, to be customized for your own needs.
curl -L https://bootstrap.saltstack.com | sudo sh -s -- "$@"
curl -L https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh -s -- "$@"
# By default, Salt Cloud now places the minion's keys and configuration in
# /tmp/.saltcloud/ before executing the deploy script. After it has executed,
# these temporary files are removed. If you don't want salt-bootstrap to handle
# these files, comment out the above command, and uncomment the below command.
#curl -L https://bootstrap.saltstack.com | sudo sh
#curl -L https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh

View file

@ -7,11 +7,11 @@
#
# It has been designed as an example, to be customized for your own needs.
python -c 'import urllib; print urllib.urlopen("https://bootstrap.saltstack.com").read()' | sudo sh -s -- "$@"
python -c 'import urllib; print urllib.urlopen("https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh").read()' | sudo sh -s -- "$@"
# By default, Salt Cloud now places the minion's keys and configuration in
# /tmp/.saltcloud/ before executing the deploy script. After it has executed,
# these temporary files are removed. If you don't want salt-bootstrap to handle
# these files, comment out the above command, and uncomment the below command.
#python -c 'import urllib; print urllib.urlopen("https://bootstrap.saltstack.com").read()' | sudo sh
#python -c 'import urllib; print urllib.urlopen("https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh").read()' | sudo sh

View file

@ -7,11 +7,11 @@
#
# It has been designed as an example, to be customized for your own needs.
wget --no-check-certificate -O - https://bootstrap.saltstack.com | sudo sh -s -- "$@"
wget --no-check-certificate -O - https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh -s -- "$@"
# By default, Salt Cloud now places the minion's keys and configuration in
# /tmp/.saltcloud/ before executing the deploy script. After it has executed,
# these temporary files are removed. If you don't want salt-bootstrap to handle
# these files, comment out the above command, and uncomment the below command.
#wget --no-check-certificate -O - https://bootstrap.saltstack.com | sudo sh
#wget --no-check-certificate -O - https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh

View file

@ -7,11 +7,11 @@
#
# It has been designed as an example, to be customized for your own needs.
wget -O - https://bootstrap.saltstack.com | sudo sh -s -- "$@"
wget -O - https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh -s -- "$@"
# By default, Salt Cloud now places the minion's keys and configuration in
# /tmp/.saltcloud/ before executing the deploy script. After it has executed,
# these temporary files are removed. If you don't want salt-bootstrap to handle
# these files, comment out the above command, and uncomment the below command.
#wget -O - https://bootstrap.saltstack.com | sudo sh
#wget -O - https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh | sudo sh

View file

@ -287,7 +287,12 @@ def _linux_gpu_data():
"matrox",
"aspeed",
]
gpu_classes = ("vga compatible controller", "3d controller", "display controller")
gpu_classes = (
"3d controller",
"display controller",
"processing accelerators",
"vga compatible controller",
)
devs = []
try:

View file

@ -17,6 +17,7 @@ import threading
import time
import traceback
import types
import uuid
import tornado
import tornado.gen
@ -1071,8 +1072,10 @@ class MinionManager(MinionBase):
@tornado.gen.coroutine
def handle_event(self, package):
for minion in self.minions:
minion.handle_event(package)
try:
yield [_.handle_event(package) for _ in self.minions]
except Exception as exc: # pylint: disable=broad-except
log.error("Error dispatching event. %s", exc)
def _create_minion_object(
self,
@ -1396,13 +1399,8 @@ class Minion(MinionBase):
self.req_channel = salt.channel.client.AsyncReqChannel.factory(
self.opts, io_loop=self.io_loop
)
if hasattr(
self.req_channel, "connect"
): # TODO: consider generalizing this for all channels
log.debug("Connecting minion's long-running req channel")
yield self.req_channel.connect()
log.debug("Connecting minion's long-running req channel")
yield self.req_channel.connect()
yield self._post_master_init(master)
@tornado.gen.coroutine
@ -1625,6 +1623,7 @@ class Minion(MinionBase):
return functions, returners, errors, executors
def _send_req_sync(self, load, timeout):
# XXX: Signing should happen in RequestChannel to be fixed in 3008
if self.opts["minion_sign_messages"]:
log.trace("Signing event to be published onto the bus.")
minion_privkey_path = os.path.join(self.opts["pki_dir"], "minion.pem")
@ -1632,18 +1631,25 @@ class Minion(MinionBase):
minion_privkey_path, salt.serializers.msgpack.serialize(load)
)
load["sig"] = sig
with salt.utils.event.get_event(
"minion", opts=self.opts, listen=False
) as event:
return event.fire_event(
with salt.utils.event.get_event("minion", opts=self.opts, listen=True) as event:
request_id = str(uuid.uuid4())
log.trace("Send request to main id=%s", request_id)
event.fire_event(
load,
f"__master_req_channel_payload/{self.opts['master']}",
f"__master_req_channel_payload/{request_id}/{self.opts['master']}",
timeout=timeout,
)
ret = event.get_event(
tag=f"__master_req_channel_return/{request_id}",
wait=timeout,
)
log.trace("Reply from main %s", request_id)
return ret["ret"]
@tornado.gen.coroutine
def _send_req_async(self, load, timeout):
# XXX: Signing should happen in RequestChannel to be fixed in 3008
# XXX: This is only used by syndic
if self.opts["minion_sign_messages"]:
log.trace("Signing event to be published onto the bus.")
minion_privkey_path = os.path.join(self.opts["pki_dir"], "minion.pem")
@ -1651,31 +1657,49 @@ class Minion(MinionBase):
minion_privkey_path, salt.serializers.msgpack.serialize(load)
)
load["sig"] = sig
with salt.utils.event.get_event(
"minion", opts=self.opts, listen=False
) as event:
ret = yield event.fire_event_async(
with salt.utils.event.get_event("minion", opts=self.opts, listen=True) as event:
request_id = str(uuid.uuid4())
log.trace("Send request to main id=%s", request_id)
yield event.fire_event_async(
load,
f"__master_req_channel_payload/{self.opts['master']}",
f"__master_req_channel_payload/{request_id}/{self.opts['master']}",
timeout=timeout,
)
raise tornado.gen.Return(ret)
start = time.time()
while time.time() - start < timeout:
ret = event.get_event(
tag=f"__master_req_channel_return/{request_id}", no_block=True
)
if ret:
break
yield tornado.gen.sleep(0.3)
else:
raise TimeoutError("Did not recieve return event")
log.trace("Reply from main %s", request_id)
raise tornado.gen.Return(ret["ret"])
def _fire_master(
self,
data=None,
tag=None,
events=None,
pretag=None,
timeout=60,
sync=True,
timeout_handler=None,
include_startup_grains=False,
@tornado.gen.coroutine
def _send_req_async_main(self, load, timeout):
"""
Send a request to the master's request server. To be called from the
top level process in the main thread only. Worker threads and
processess should call _send_req_sync or _send_req_async as nessecery.
"""
if self.opts["minion_sign_messages"]:
log.trace("Signing event to be published onto the bus.")
minion_privkey_path = os.path.join(self.opts["pki_dir"], "minion.pem")
sig = salt.crypt.sign_message(
minion_privkey_path, salt.serializers.msgpack.serialize(load)
)
load["sig"] = sig
ret = yield self.req_channel.send(
load, timeout=timeout, tries=self.opts["return_retry_tries"]
)
raise tornado.gen.Return(ret)
def _fire_master_prepare(
self, data, tag, events, pretag, include_startup_grains=False
):
"""
Fire an event on the master, or drop message if unable to send.
"""
load = {
"id": self.opts["id"],
"cmd": "_minion_event",
@ -1700,34 +1724,62 @@ class Minion(MinionBase):
if k in self.opts["start_event_grains"]
}
load["grains"] = grains_to_add
return load
if sync:
try:
self._send_req_sync(load, timeout)
except salt.exceptions.SaltReqTimeoutError:
@tornado.gen.coroutine
def _fire_master_main(
self,
data=None,
tag=None,
events=None,
pretag=None,
timeout=60,
timeout_handler=None,
include_startup_grains=False,
):
load = self._fire_master_prepare(
data, tag, events, pretag, include_startup_grains
)
if timeout_handler is None:
def handle_timeout(*_):
log.info(
"fire_master failed: master could not be contacted. Request timed"
" out."
"fire_master failed: master could not be contacted. Request"
" timed out."
)
return False
except Exception: # pylint: disable=broad-except
log.info("fire_master failed: %s", traceback.format_exc())
return False
else:
if timeout_handler is None:
return True
def handle_timeout(*_):
log.info(
"fire_master failed: master could not be contacted. Request"
" timed out."
)
return True
timeout_handler = handle_timeout
timeout_handler = handle_timeout
yield self._send_req_async_main(load, timeout)
# pylint: disable=unexpected-keyword-arg
self._send_req_async(load, timeout)
# pylint: enable=unexpected-keyword-arg
def _fire_master(
self,
data=None,
tag=None,
events=None,
pretag=None,
timeout=60,
timeout_handler=None,
include_startup_grains=False,
):
"""
Fire an event on the master, or drop message if unable to send.
"""
load = self._fire_master_prepare(
data, tag, events, pretag, include_startup_grains
)
try:
self._send_req_sync(load, timeout)
except salt.exceptions.SaltReqTimeoutError:
log.info(
"fire_master failed: master could not be contacted. Request timed"
" out."
)
return False
except Exception: # pylint: disable=broad-except
log.info("fire_master failed: %s", traceback.format_exc())
return False
return True
async def _handle_decoded_payload(self, data):
@ -2228,10 +2280,7 @@ class Minion(MinionBase):
except Exception as exc: # pylint: disable=broad-except
log.error("The return failed for job %s: %s", data["jid"], exc)
def _return_pub(self, ret, ret_cmd="_return", timeout=60, sync=True):
"""
Return the data from the executed command to the master server
"""
def _prepare_return_pub(self, ret, ret_cmd="_return"):
jid = ret.get("jid", ret.get("__jid__"))
fun = ret.get("fun", ret.get("__fun__"))
if self.opts["multiprocessing"]:
@ -2285,7 +2334,12 @@ class Minion(MinionBase):
if ret["jid"] == "req":
ret["jid"] = salt.utils.jid.gen_jid(self.opts)
salt.utils.minion.cache_jobs(self.opts, ret["jid"], ret)
return load
@tornado.gen.coroutine
def _return_pub_main(self, ret, ret_cmd="_return", timeout=60):
jid = ret.get("jid", ret.get("__jid__"))
load = self._prepare_return_pub(ret, ret_cmd)
if not self.opts["pub_ret"]:
return ""
@ -2299,20 +2353,38 @@ class Minion(MinionBase):
)
return True
if sync:
try:
ret_val = self._send_req_sync(load, timeout=timeout)
except SaltReqTimeoutError:
timeout_handler()
return ""
else:
# pylint: disable=unexpected-keyword-arg
ret_val = self._send_req_async(
load,
timeout=timeout,
)
# pylint: enable=unexpected-keyword-arg
try:
ret_val = yield self._send_req_async_main(load, timeout=timeout)
except SaltReqTimeoutError:
timeout_handler()
ret_val = ""
log.trace("ret_val = %s", ret_val) # pylint: disable=no-member
raise tornado.gen.Return(ret_val)
def _return_pub(self, ret, ret_cmd="_return", timeout=60):
"""
Return the data from the executed command to the master server
"""
jid = ret.get("jid", ret.get("__jid__"))
load = self._prepare_return_pub(ret, ret_cmd)
if not self.opts["pub_ret"]:
return ""
def timeout_handler(*_):
log.warning(
"The minion failed to return the job information for job %s. "
"This is often due to the master being shut down or "
"overloaded. If the master is running, consider increasing "
"the worker_threads value.",
jid,
)
return True
try:
ret_val = self._send_req_sync(load, timeout=timeout)
except SaltReqTimeoutError:
timeout_handler()
return ""
log.trace("ret_val = %s", ret_val) # pylint: disable=no-member
return ret_val
@ -2320,6 +2392,9 @@ class Minion(MinionBase):
"""
Return the data from the executed command to the master server
"""
# XXX: This is only used by syndic and should be moved to the Syndic class.
# XXX: The sync flag is only called with sync=False. Which also means
# deprecating sync means we can remove Minion._send_req_async.
if not isinstance(rets, list):
rets = [rets]
jids = {}
@ -2460,13 +2535,13 @@ class Minion(MinionBase):
# Send an event to the master that the minion is live
if self.opts["enable_legacy_startup_events"]:
# Old style event. Defaults to False in 3001 release.
self._fire_master(
self._fire_master_main(
"Minion {} started at {}".format(self.opts["id"], time.asctime()),
"minion_start",
include_startup_grains=include_grains,
)
# send name spaced event
self._fire_master(
self._fire_master_main(
"Minion {} started at {}".format(self.opts["id"], time.asctime()),
tagify([self.opts["id"], "start"], "minion"),
include_startup_grains=include_grains,
@ -2750,21 +2825,35 @@ class Minion(MinionBase):
notify=data.get("notify", False),
)
elif tag.startswith("__master_req_channel_payload"):
job_master = tag.rsplit("/", 1)[1]
request_id, job_master = tag.rsplit("/", 2)[1:]
if job_master == self.opts["master"]:
ret = None
try:
yield _minion.req_channel.send(
ret = yield _minion.req_channel.send(
data,
timeout=_minion._return_retry_timer(),
tries=_minion.opts["return_retry_tries"],
)
except salt.exceptions.SaltReqTimeoutError:
log.error("Timeout encountered while sending %r request", data)
log.error(
"Timeout encountered while sending %r request. id=%s",
data,
request_id,
)
raise tornado.gen.Return()
with salt.utils.event.get_event(
"minion", opts=self.opts, listen=False
) as event:
yield event.fire_event_async(
{"ret": ret},
f"__master_req_channel_return/{request_id}",
)
else:
log.debug(
"Skipping req for other master: cmd=%s master=%s",
"Skipping req for other master: cmd=%s master=%s id=%s",
data["cmd"],
job_master,
request_id,
)
elif tag.startswith("pillar_refresh"):
yield _minion.pillar_refresh(
@ -2792,13 +2881,22 @@ class Minion(MinionBase):
self._mine_send(tag, data)
elif tag.startswith("fire_master"):
if self.connected:
log.debug("Forwarding master event tag=%s", data["tag"])
self._fire_master(
log.debug(
"Forwarding event %s to master %s",
data["tag"],
self.opts["master"],
)
yield self._fire_master_main(
data["data"],
data["tag"],
data["events"],
data["pretag"],
sync=False,
)
else:
log.debug(
"Master %s is not connected, dropping event %s",
self.opts["master"],
data["tag"],
)
elif tag.startswith(master_event(type="disconnected")) or tag.startswith(
master_event(type="failback")
@ -2866,6 +2964,7 @@ class Minion(MinionBase):
self.req_channel = salt.channel.client.AsyncReqChannel.factory(
self.opts, io_loop=self.io_loop
)
yield self.req_channel.connect()
# put the current schedule into the new loaders
self.opts["schedule"] = self.schedule.option("schedule")
@ -2955,11 +3054,11 @@ class Minion(MinionBase):
1
],
)
self._return_pub(data, ret_cmd="_return", sync=False)
yield self._return_pub_main(data, ret_cmd="_return")
elif tag.startswith("_salt_error"):
if self.connected:
log.debug("Forwarding salt error event tag=%s", tag)
self._fire_master(data, tag, sync=False)
yield self._fire_master_main(data, tag)
elif tag.startswith("salt/auth/creds"):
key = tuple(data["key"])
log.debug(
@ -2972,7 +3071,7 @@ class Minion(MinionBase):
elif tag.startswith("__beacons_return"):
if self.connected:
log.debug("Firing beacons to master")
self._fire_master(events=data["beacons"])
yield self._fire_master_main(events=data["beacons"])
def cleanup_subprocesses(self):
"""
@ -3170,10 +3269,9 @@ class Minion(MinionBase):
"minion is running under an init system."
)
self._fire_master(
self._fire_master_main(
"ping",
"minion_ping",
sync=False,
timeout_handler=ping_timeout_handler,
)
except Exception: # pylint: disable=broad-except
@ -3374,12 +3472,10 @@ class Syndic(Minion):
self._fire_master(
"Syndic {} started at {}".format(self.opts["id"], time.asctime()),
"syndic_start",
sync=False,
)
self._fire_master(
"Syndic {} started at {}".format(self.opts["id"], time.asctime()),
tagify([self.opts["id"], "start"], "syndic"),
sync=False,
)
# TODO: clean up docs
@ -3774,7 +3870,7 @@ class SyndicManager(MinionBase):
"events": events,
"pretag": tagify(self.opts["id"], base="syndic"),
"timeout": self._return_retry_timer(),
"sync": False,
"sync": True, # Sync needs to be true unless being called from a coroutine
},
)
if self.delayed:

View file

@ -5061,6 +5061,18 @@ def _remove_invalid_xmlns(xml_file):
return xml_tree
def _encode_xmlns_url(match):
"""
Escape spaces in xmlns urls
"""
before_xmlns = match.group(1)
xmlns = match.group(2)
url = match.group(3)
after_url = match.group(4)
encoded_url = re.sub(r"\s+", "%20", url)
return f'{before_xmlns}{xmlns}="{encoded_url}"{after_url}'
def _parse_xml(adm_file):
"""
Parse the admx/adml file. There are 3 scenarios (so far) that we'll likely
@ -5107,6 +5119,12 @@ def _parse_xml(adm_file):
encoding = "utf-16"
raw = raw.decode(encoding)
for line in raw.split("\r\n"):
if 'xmlns="' in line:
line = re.sub(
r'(.*)(\bxmlns(?::\w+)?)\s*=\s*"([^"]+)"(.*)',
_encode_xmlns_url,
line,
)
if 'key="' in line:
start = line.index('key="')
q1 = line[start:].index('"') + start
@ -5744,8 +5762,9 @@ def _set_netsh_value(profile, section, option, value):
salt.utils.win_lgpo_netsh.set_logging_settings(
profile=profile, setting=option, value=value, store="lgpo"
)
log.trace("LGPO: Clearing netsh data for %s profile", profile)
__context__["lgpo.netsh_data"].pop(profile)
if profile in __context__["lgpo.netsh_data"]:
log.trace("LGPO: Clearing netsh data for %s profile", profile)
__context__["lgpo.netsh_data"].pop(profile, {})
return True

View file

@ -623,7 +623,7 @@ def versions():
def bootstrap(
version="develop",
script="https://bootstrap.saltproject.io",
script="https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh",
hosts="",
script_args="",
roster="flat",
@ -639,7 +639,7 @@ def bootstrap(
version : develop
Git tag of version to install
script : https://bootstrap.saltproject.io/
script : https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh
URL containing the script to execute
hosts
@ -699,8 +699,8 @@ def bootstrap(
.. code-block:: bash
salt-run manage.bootstrap hosts='host1,host2'
salt-run manage.bootstrap hosts='host1,host2' version='v3004.2'
salt-run manage.bootstrap hosts='host1,host2' version='v3004.2' script='https://bootstrap.saltproject.io/develop'
salt-run manage.bootstrap hosts='host1,host2' version='v3006.2'
salt-run manage.bootstrap hosts='host1,host2' version='v3006.2' script='https://github.com/saltstack/salt-bootstrap/develop'
"""
client_opts = __opts__.copy()

View file

@ -317,6 +317,8 @@ class PublishClient(salt.transport.base.PublishClient):
self.backoff,
self._trace,
)
if not timeout:
raise
if timeout and time.monotonic() - start > timeout:
break
await asyncio.sleep(self.backoff)

View file

@ -73,7 +73,8 @@ class SyncWrapper:
self.cls = cls
if loop_kwarg:
kwargs[self.loop_kwarg] = self.io_loop
self.obj = cls(*args, **kwargs)
with current_ioloop(self.io_loop):
self.obj = cls(*args, **kwargs)
self._async_methods = list(
set(async_methods + getattr(self.obj, "async_methods", []))
)

View file

@ -2950,7 +2950,10 @@ def update_bootstrap(config, url=None):
- The absolute path to the bootstrap
- The content of the bootstrap script
"""
default_url = config.get("bootstrap_script_url", "https://bootstrap.saltstack.com")
default_url = config.get(
"bootstrap_script_url",
"https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh",
)
if not url:
url = default_url
if not url:

View file

@ -76,6 +76,7 @@ import salt.utils.platform
import salt.utils.process
import salt.utils.stringutils
import salt.utils.zeromq
from salt.exceptions import SaltInvocationError
from salt.utils.versions import warn_until
log = logging.getLogger(__name__)
@ -550,6 +551,9 @@ class SaltEvent:
try:
if not self.cpub and not self.connect_pub(timeout=wait):
break
if not self._run_io_loop_sync:
log.error("Trying to get event with async subscriber")
raise SaltInvocationError("get_event needs synchronous subscriber")
raw = self.subscriber.recv(timeout=wait)
if raw is None:
break

View file

@ -487,11 +487,15 @@ class GitProvider:
).replace(
"/", "_"
) # replace "/" with "_" to not cause trouble with file system
self._cache_hash = salt.utils.path.join(cache_root, self._cache_basehash)
self._cache_basename = "_"
if self.id.startswith("__env__"):
try:
self._cache_basename = self.get_checkout_target()
self._cache_basename = self.get_checkout_target().replace(
"/", "-"
) # replace '/' with '-' to not cause trouble with file-system
except AttributeError:
log.critical(
"__env__ cant generate basename: %s %s", self.role, self.id
@ -529,7 +533,6 @@ class GitProvider:
if HAS_PSUTIL:
cur_pid = os.getpid()
process = psutil.Process(cur_pid)
dgm_process_dir = dir(process)
cache_dir = self.opts.get("cachedir", None)
gitfs_active = self.opts.get("gitfs_remotes", None)
if cache_dir and gitfs_active:
@ -1567,12 +1570,14 @@ class GitPython(GitProvider):
local copy was already up-to-date, return False.
"""
origin = self.repo.remotes[0]
try:
fetch_results = origin.fetch()
except AssertionError:
fetch_results = origin.fetch()
new_objs = False
for fetchinfo in fetch_results:
if fetchinfo.old_commit is not None:
log.debug(
@ -1781,7 +1786,7 @@ class Pygit2(GitProvider):
return None
try:
head_sha = self.peel(local_head).hex
head_sha = str(self.peel(local_head).id)
except AttributeError:
# Shouldn't happen, but just in case a future pygit2 API change
# breaks things, avoid a traceback and log an error.
@ -1840,7 +1845,10 @@ class Pygit2(GitProvider):
self.repo.create_reference(local_ref, pygit2_id)
try:
target_sha = self.peel(self.repo.lookup_reference(remote_ref)).hex
target_sha = str(
self.peel(self.repo.lookup_reference(remote_ref)).id
)
except KeyError:
log.error(
"pygit2 was unable to get SHA for %s in %s remote '%s'",
@ -1853,6 +1861,7 @@ class Pygit2(GitProvider):
# Only perform a checkout if HEAD and target are not pointing
# at the same SHA1.
if head_sha != target_sha:
# Check existence of the ref in refs/heads/ which
# corresponds to the local HEAD. Checking out local_ref
@ -1921,10 +1930,11 @@ class Pygit2(GitProvider):
else:
try:
# If no AttributeError raised, this is an annotated tag
tag_sha = tag_obj.target.hex
tag_sha = str(tag_obj.target.id)
except AttributeError:
try:
tag_sha = tag_obj.hex
tag_sha = str(tag_obj.id)
except AttributeError:
# Shouldn't happen, but could if a future pygit2
# API change breaks things.
@ -2106,6 +2116,7 @@ class Pygit2(GitProvider):
origin = self.repo.remotes[0]
refs_pre = self.repo.listall_references()
fetch_kwargs = {}
# pygit2 radically changed fetchiing in 0.23.2
if self.remotecallbacks is not None:
fetch_kwargs["callbacks"] = self.remotecallbacks
@ -2119,6 +2130,7 @@ class Pygit2(GitProvider):
pass
try:
fetch_results = origin.fetch(**fetch_kwargs)
except GitError as exc: # pylint: disable=broad-except
exc_str = get_error_message(exc).lower()
if "unsupported url protocol" in exc_str and isinstance(
@ -2157,6 +2169,7 @@ class Pygit2(GitProvider):
# pygit2.Remote.fetch() returns a class instance in
# pygit2 >= 0.21.0
received_objects = fetch_results.received_objects
if received_objects != 0:
log.debug(
"%s received %s objects for remote '%s'",
@ -2168,6 +2181,7 @@ class Pygit2(GitProvider):
log.debug("%s remote '%s' is up-to-date", self.role, self.id)
refs_post = self.repo.listall_references()
cleaned = self.clean_stale_refs(local_refs=refs_post)
return True if (received_objects or refs_pre != refs_post or cleaned) else None
def file_list(self, tgt_env):
@ -2278,7 +2292,7 @@ class Pygit2(GitProvider):
blob = None
break
if isinstance(blob, pygit2.Blob):
return blob, blob.hex, mode
return blob, str(blob.id), mode
return None, None, None
def get_tree_from_branch(self, ref):
@ -3481,6 +3495,7 @@ class GitPillar(GitBase):
"""
self.pillar_dirs = OrderedDict()
self.pillar_linked_dirs = []
for repo in self.remotes:
cachedir = self.do_checkout(repo, fetch_on_fail=fetch_on_fail)
if cachedir is not None:

View file

@ -110,20 +110,38 @@ def _get_inbound_text(rule, action):
The "Inbound connections" setting is a combination of 2 parameters:
- AllowInboundRules
0 = False
1 = True
2 = NotConfigured
I don't see a way to set "AllowInboundRules" outside of PowerShell
- DefaultInboundAction
0 = Not Configured
2 = Allow Inbound
4 = Block Inbound
The settings are as follows:
Rules Action
0 4 BlockInboundAlways
1 0 NotConfigured
1 2 AllowInbound
1 4 BlockInbound
2 0 NotConfigured
2 2 AllowInbound
2 4 BlockInbound
0 4 BlockInboundAlways
2 0 NotConfigured
"""
settings = {
0: {
0: "NotConfigured",
2: "AllowInbound",
4: "BlockInboundAlways",
},
1: {
0: "NotConfigured",
2: "AllowInbound",
4: "BlockInbound",
},
2: {
0: "NotConfigured",
2: "AllowInbound",
@ -143,6 +161,30 @@ def _get_inbound_settings(text):
return settings[text.lower()]
def _get_all_settings(profile, store="local"):
# Get current settings using PowerShell
# if "lgpo.firewall_profile_settings" not in __context__:
cmd = ["Get-NetFirewallProfile"]
if profile:
cmd.append(profile)
if store.lower() == "lgpo":
cmd.extend(["-PolicyStore", "localhost"])
# Run the command and get dict
settings = salt.utils.win_pwsh.run_dict(cmd)
# A successful run should return a dictionary
if not settings:
raise CommandExecutionError("LGPO NETSH: An unknown error occurred")
# Remove the junk
for setting in list(settings.keys()):
if setting.startswith("Cim"):
settings.pop(setting)
return settings
def get_settings(profile, section, store="local"):
"""
Get the firewall property from the specified profile in the specified store
@ -190,24 +232,7 @@ def get_settings(profile, section, store="local"):
if store.lower() not in ("local", "lgpo"):
raise ValueError(f"Incorrect store: {store}")
# Build the powershell command
cmd = ["Get-NetFirewallProfile"]
if profile:
cmd.append(profile)
if store and store.lower() == "lgpo":
cmd.extend(["-PolicyStore", "localhost"])
# Run the command
settings = salt.utils.win_pwsh.run_dict(cmd)
# A successful run should return a dictionary
if not settings:
raise CommandExecutionError("LGPO NETSH: An unknown error occurred")
# Remove the junk
for setting in list(settings.keys()):
if setting.startswith("Cim"):
settings.pop(setting)
settings = _get_all_settings(profile=profile, store=store)
# Make it look like netsh output
ret_settings = {
@ -299,24 +324,7 @@ def get_all_settings(profile, store="local"):
if store.lower() not in ("local", "lgpo"):
raise ValueError(f"Incorrect store: {store}")
# Build the powershell command
cmd = ["Get-NetFirewallProfile"]
if profile:
cmd.append(profile)
if store and store.lower() == "lgpo":
cmd.extend(["-PolicyStore", "localhost"])
# Run the command
settings = salt.utils.win_pwsh.run_dict(cmd)
# A successful run should return a dictionary
if not settings:
raise CommandExecutionError("LGPO NETSH: An unknown error occurred")
# Remove the junk
for setting in list(settings.keys()):
if setting.startswith("Cim"):
settings.pop(setting)
settings = _get_all_settings(profile=profile, store=store)
# Make it look like netsh output
ret_settings = {
@ -409,6 +417,9 @@ def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
raise ValueError(f"Incorrect outbound value: {outbound}")
if not inbound and not outbound:
raise ValueError("Must set inbound or outbound")
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-allowinboundrules
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-defaultoutboundaction
if store == "local":
if inbound and inbound.lower() == "notconfigured":
msg = "Cannot set local inbound policies as NotConfigured"
@ -417,16 +428,26 @@ def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
msg = "Cannot set local outbound policies as NotConfigured"
raise CommandExecutionError(msg)
# Get current settings
settings = _get_all_settings(profile=profile, store=store)
# Build the powershell command
cmd = ["Set-NetFirewallProfile"]
if profile:
cmd.append(profile)
if store and store.lower() == "lgpo":
if store.lower() == "lgpo":
cmd.extend(["-PolicyStore", "localhost"])
# Get inbound settings
if inbound:
in_rule, in_action = _get_inbound_settings(inbound.lower())
# If current AllowInboundRules is set (1 or 2) and new AllowInboundRules is 2
# We want to just keep the current setting.
# We don't have a way in LGPO to set the AllowInboundRules. I can't find it in
# gpedit.msc either. Not sure how to set it outside of PowerShell
current_in_rule = settings["AllowInboundRules"]
if current_in_rule > 0 and in_rule == 2:
in_rule = current_in_rule
cmd.extend(["-AllowInboundRules", in_rule, "-DefaultInboundAction", in_action])
if outbound:
@ -509,10 +530,6 @@ def set_logging_settings(profile, setting, value, store="local"):
# Input validation
if profile.lower() not in ("domain", "public", "private"):
raise ValueError(f"Incorrect profile: {profile}")
if store == "local":
if str(value).lower() == "notconfigured":
msg = "Cannot set local policies as NotConfigured"
raise CommandExecutionError(msg)
if setting.lower() not in (
"allowedconnections",
"droppedconnections",
@ -520,6 +537,18 @@ def set_logging_settings(profile, setting, value, store="local"):
"maxfilesize",
):
raise ValueError(f"Incorrect setting: {setting}")
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-logallowed
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-logblocked
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-logmaxsizekilobytes
if str(value).lower() == "notconfigured" and store.lower() == "local":
if setting in ["allowedconnections", "droppedconnections", "maxfilesize"]:
raise CommandExecutionError(
"NotConfigured only valid when setting Group Policy"
)
if setting == "maxfilesize" and str(value).lower() == "notconfigured":
raise CommandExecutionError(f"NotConfigured not a valid option for {setting}")
settings = {"filename": ["-LogFileName", value]}
if setting.lower() in ("allowedconnections", "droppedconnections"):
if value.lower() not in ("enable", "disable", "notconfigured"):
@ -588,7 +617,7 @@ def set_settings(profile, setting, value, store="local"):
- enable
- disable
- notconfigured
- notconfigured <== lgpo only
store (str):
The store to use. This is either the local firewall policy or the
@ -618,20 +647,19 @@ def set_settings(profile, setting, value, store="local"):
raise ValueError(f"Incorrect setting: {setting}")
if value.lower() not in ("enable", "disable", "notconfigured"):
raise ValueError(f"Incorrect value: {value}")
if setting.lower() in ["localfirewallrules", "localconsecrules"]:
if store.lower() != "lgpo":
msg = f"{setting} can only be set using Group Policy"
raise CommandExecutionError(msg)
if setting.lower() == "inboundusernotification" and store.lower() != "lgpo":
if value.lower() == "notconfigured":
msg = "NotConfigured is only valid when setting group policy"
raise CommandExecutionError(msg)
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-allowlocalfirewallrules
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-allowlocalipsecrules
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-allowunicastresponsetomulticast
# https://learn.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2025-ps#-notifyonlisten
if value.lower() == "notconfigured" and store.lower() == "local":
msg = "NotConfigured is only valid when setting group policy"
raise CommandExecutionError(msg)
# Build the powershell command
cmd = ["Set-NetFirewallProfile"]
if profile:
cmd.append(profile)
if store and store.lower() == "lgpo":
if store.lower() == "lgpo":
cmd.extend(["-PolicyStore", "localhost"])
settings = {
@ -706,7 +734,7 @@ def set_state(profile, state, store="local"):
cmd = ["Set-NetFirewallProfile"]
if profile:
cmd.append(profile)
if store and store.lower() == "lgpo":
if store.lower() == "lgpo":
cmd.extend(["-PolicyStore", "localhost"])
cmd.extend(["-Enabled", ON_OFF[state.lower()]])

View file

@ -2,6 +2,7 @@ import pytest
from salt.pillar.git_pillar import ext_pillar
from salt.utils.immutabletypes import ImmutableDict, ImmutableList
from salt.utils.odict import OrderedDict
from tests.support.mock import patch
pytestmark = [
@ -260,3 +261,38 @@ def test_gitpython_multiple_2(gitpython_pillar_opts, grains):
@skipif_no_pygit2
def test_pygit2_multiple_2(pygit2_pillar_opts, grains):
_test_multiple_2(pygit2_pillar_opts, grains)
def _test_multiple_slash_in_branch_name(pillar_opts, grains):
pillar_opts["pillarenv"] = "doggy/moggy"
data = _get_ext_pillar(
"minion",
pillar_opts,
grains,
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
"other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
)
assert data == {
"key": "data",
"foo": OrderedDict(
[
("animals", OrderedDict([("breed", "seadog")])),
(
"feature/baz",
OrderedDict(
[("test1", "dog"), ("test2", "kat"), ("test3", "gerbil")]
),
),
]
),
}
@skipif_no_gitpython
def test_gitpython_multiple_slash_in_branch_name(gitpython_pillar_opts, grains):
_test_multiple_slash_in_branch_name(gitpython_pillar_opts, grains)
@skipif_no_pygit2
def test_pygit2_multiple_slash_in_branch_name(pygit2_pillar_opts, grains):
_test_multiple_slash_in_branch_name(pygit2_pillar_opts, grains)

View file

@ -32,6 +32,8 @@ except ImportError:
skipif_no_gitpython = pytest.mark.skipif(not HAS_GITPYTHON, reason="Missing gitpython")
skipif_no_pygit2 = pytest.mark.skipif(not HAS_PYGIT2, reason="Missing pygit2")
testgitfs = "https://github.com/saltstack/salt-test-pillar-gitfs.git"
@pytest.fixture
def pillar_opts(salt_factories, tmp_path):
@ -72,9 +74,7 @@ def _get_pillar(opts, *remotes):
@skipif_no_gitpython
def test_gitpython_pillar_provider(gitpython_pillar_opts):
p = _get_pillar(
gitpython_pillar_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
)
p = _get_pillar(gitpython_pillar_opts, testgitfs)
assert len(p.remotes) == 1
assert p.provider == "gitpython"
assert isinstance(p.remotes[0], GitPython)
@ -82,18 +82,14 @@ def test_gitpython_pillar_provider(gitpython_pillar_opts):
@skipif_no_pygit2
def test_pygit2_pillar_provider(pygit2_pillar_opts):
p = _get_pillar(
pygit2_pillar_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
)
p = _get_pillar(pygit2_pillar_opts, testgitfs)
assert len(p.remotes) == 1
assert p.provider == "pygit2"
assert isinstance(p.remotes[0], Pygit2)
def _test_env(opts):
p = _get_pillar(
opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
)
p = _get_pillar(opts, f"__env__ {testgitfs}")
assert len(p.remotes) == 1
p.checkout()
repo = p.remotes[0]
@ -102,9 +98,7 @@ def _test_env(opts):
for f in (".gitignore", "README.md", "file.sls", "top.sls"):
assert f in files
opts["pillarenv"] = "main"
p2 = _get_pillar(
opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
)
p2 = _get_pillar(opts, f"__env__ {testgitfs}")
assert len(p.remotes) == 1
p2.checkout()
repo2 = p2.remotes[0]
@ -165,9 +159,9 @@ def test_pygit2_checkout_fetch_on_fail(pygit2_pillar_opts):
def _test_multiple_repos(opts):
p = _get_pillar(
opts,
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
"main https://github.com/saltstack/salt-test-pillar-gitfs.git",
"branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
f"__env__ {testgitfs}",
f"main {testgitfs}",
f"branch {testgitfs}",
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
"other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
)
@ -179,9 +173,9 @@ def _test_multiple_repos(opts):
p2 = _get_pillar(
opts,
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
"main https://github.com/saltstack/salt-test-pillar-gitfs.git",
"branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
f"__env__ {testgitfs}",
f"main {testgitfs}",
f"branch {testgitfs}",
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
"other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
)
@ -194,9 +188,9 @@ def _test_multiple_repos(opts):
opts["pillarenv"] = "main"
p3 = _get_pillar(
opts,
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
"main https://github.com/saltstack/salt-test-pillar-gitfs.git",
"branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
f"__env__ {testgitfs}",
f"main {testgitfs}",
f"branch {testgitfs}",
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
"other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
)
@ -227,15 +221,13 @@ def test_pygit2_multiple_repos(pygit2_pillar_opts):
def _test_fetch_request(opts):
p = _get_pillar(
opts,
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
f"__env__ {testgitfs}",
"other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
)
frequest = os.path.join(p.remotes[0].get_salt_working_dir(), "fetch_request")
frequest_other = os.path.join(p.remotes[1].get_salt_working_dir(), "fetch_request")
opts["pillarenv"] = "main"
p2 = _get_pillar(
opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
)
p2 = _get_pillar(opts, f"__env__ {testgitfs}")
frequest2 = os.path.join(p2.remotes[0].get_salt_working_dir(), "fetch_request")
assert frequest != frequest2
assert os.path.isfile(frequest) is False
@ -277,15 +269,13 @@ def test_pygit2_fetch_request(pygit2_pillar_opts):
def _test_clear_old_remotes(opts):
p = _get_pillar(
opts,
"__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
f"__env__ {testgitfs}",
"other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
)
repo = p.remotes[0]
repo2 = p.remotes[1]
opts["pillarenv"] = "main"
p2 = _get_pillar(
opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
)
p2 = _get_pillar(opts, f"__env__ {testgitfs}")
repo3 = p2.remotes[0]
assert os.path.isdir(repo.get_cachedir()) is True
assert os.path.isdir(repo2.get_cachedir()) is True
@ -313,7 +303,7 @@ def test_pygit2_clear_old_remotes(pygit2_pillar_opts):
def _test_remote_map(opts):
p = _get_pillar(
opts,
"https://github.com/saltstack/salt-test-pillar-gitfs.git",
testgitfs,
)
p.fetch_remotes()
assert len(p.remotes) == 1
@ -335,7 +325,7 @@ def test_pygit2_remote_map(pygit2_pillar_opts):
def _test_lock(opts):
p = _get_pillar(
opts,
"https://github.com/saltstack/salt-test-pillar-gitfs.git",
testgitfs,
)
p.fetch_remotes()
assert len(p.remotes) == 1
@ -345,8 +335,7 @@ def _test_lock(opts):
assert repo.lock() == (
[
(
f"Set update lock for git_pillar remote "
f"'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'"
f"Set update lock for git_pillar remote '{testgitfs}' on machine_id '{mach_id}'"
)
],
[],
@ -355,8 +344,7 @@ def _test_lock(opts):
assert repo.clear_lock() == (
[
(
f"Removed update lock for git_pillar remote "
f"'https://github.com/saltstack/salt-test-pillar-gitfs.git' on machine_id '{mach_id}'"
f"Removed update lock for git_pillar remote '{testgitfs}' on machine_id '{mach_id}'"
)
],
[],

View file

@ -0,0 +1,106 @@
import sys
import pytest
import salt.utils.event
import salt.utils.platform
import tests.support.helpers
from tests.conftest import FIPS_TESTRUN
@pytest.fixture
def salt_master_1(request, salt_factories):
config_defaults = {
"open_mode": True,
"transport": request.config.getoption("--transport"),
}
config_overrides = {
"interface": "127.0.0.1",
"fips_mode": FIPS_TESTRUN,
"publish_signing_algorithm": (
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
),
}
factory = salt_factories.salt_master_daemon(
"master-1",
defaults=config_defaults,
overrides=config_overrides,
extra_cli_arguments_after_first_start_failure=["--log-level=info"],
)
with factory.started(start_timeout=120):
yield factory
@pytest.fixture
def salt_minion_1(salt_master_1):
config_defaults = {
"transport": salt_master_1.config["transport"],
}
master_1_port = salt_master_1.config["ret_port"]
master_1_addr = salt_master_1.config["interface"]
config_overrides = {
"master": [
f"{master_1_addr}:{master_1_port}",
],
"test.foo": "baz",
"fips_mode": FIPS_TESTRUN,
"encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1",
"signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1",
}
factory = salt_master_1.salt_minion_daemon(
"minion-1",
defaults=config_defaults,
overrides=config_overrides,
extra_cli_arguments_after_first_start_failure=["--log-level=info"],
)
with factory.started(start_timeout=120):
yield factory
@pytest.fixture
def script(salt_minion_1, tmp_path):
path = tmp_path / "script.py"
content = f"""
import salt.config
import salt.utils.event
opts = salt.config.minion_config('{salt_minion_1.config_file}')
payload = b'0' * 1048576000
big_event = dict()
for i in range(10000):
big_event[i] = payload = b'0' * 100
with salt.utils.event.get_event("minion", opts=opts) as event:
event.fire_master(big_event, 'bigevent')
"""
path.write_text(tests.support.helpers.dedent(content))
return path
# @pytest.mark.timeout_unless_on_windows(360)
def test_schedule_large_event(salt_master_1, salt_minion_1, script):
cli = salt_master_1.salt_cli(timeout=120)
ret = cli.run(
"schedule.add",
name="myjob",
function="cmd.run",
seconds=5,
job_args=f'["{sys.executable} {script}"]',
minion_tgt=salt_minion_1.id,
)
assert "result" in ret.data
assert ret.data["result"]
with salt.utils.event.get_event(
"master",
salt_master_1.config["sock_dir"],
salt_master_1.config["transport"],
salt_master_1.config,
) as event:
event = event.get_event(tag="bigevent", wait=15)
assert event
assert "data" in event
assert len(event["data"]) == 10000

View file

@ -25,6 +25,12 @@ def salt_mm_master_1(request, salt_factories):
"publish_signing_algorithm": (
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
),
"log_granular_levels": {
"salt": "info",
"salt.transport": "debug",
"salt.channel": "debug",
"salt.utils.event": "debug",
},
}
factory = salt_factories.salt_master_daemon(
"mm-master-1",
@ -56,6 +62,12 @@ def salt_mm_master_2(salt_factories, salt_mm_master_1):
"publish_signing_algorithm": (
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
),
"log_granular_levels": {
"salt": "info",
"salt.transport": "debug",
"salt.channel": "debug",
"salt.utils.event": "debug",
},
}
# Use the same ports for both masters, they are binding to different interfaces
@ -106,6 +118,13 @@ def salt_mm_minion_1(salt_mm_master_1, salt_mm_master_2):
"fips_mode": FIPS_TESTRUN,
"encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1",
"signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1",
"log_granular_levels": {
"salt": "info",
"salt.minion": "debug",
"salt.transport": "debug",
"salt.channel": "debug",
"salt.utils.event": "debug",
},
}
factory = salt_mm_master_1.salt_minion_daemon(
"mm-minion-1",
@ -136,6 +155,13 @@ def salt_mm_minion_2(salt_mm_master_1, salt_mm_master_2):
"fips_mode": FIPS_TESTRUN,
"encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1",
"signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1",
"log_granular_levels": {
"salt": "info",
"salt.minion": "debug",
"salt.transport": "debug",
"salt.channel": "debug",
"salt.utils.event": "debug",
},
}
factory = salt_mm_master_2.salt_minion_daemon(
"mm-minion-2",

View file

@ -3379,6 +3379,12 @@ def test_linux_gpus(caplog):
"Vega [Radeon RX Vega]]",
"amd",
], # AMD
[
"Processing accelerators",
"Advanced Micro Devices, Inc. [AMD/ATI]",
"Device X",
"amd",
], # AMD
[
"Audio device",
"Advanced Micro Devices, Inc. [AMD/ATI]",

View file

@ -349,6 +349,9 @@ def _test_set_user_policy(lgpo_bin, shell, name, setting, exp_regexes):
],
),
(
# This will need to be fixed for Windows Server 2025
# The bottom two options have been removed in 2025
# Though not set here, we're verifying there were set
"Specify settings for optional component installation and component repair",
"Disabled",
[
@ -358,6 +361,8 @@ def _test_set_user_policy(lgpo_bin, shell, name, setting, exp_regexes):
],
),
(
# This will need to be fixed for Windows Server 2025
# The bottom two options have been removed in 2025
"Specify settings for optional component installation and component repair",
{
"Alternate source file path": "",
@ -371,6 +376,8 @@ def _test_set_user_policy(lgpo_bin, shell, name, setting, exp_regexes):
],
),
(
# This will need to be fixed for Windows Server 2025
# The bottom two options have been removed in 2025
"Specify settings for optional component installation and component repair",
{
"Alternate source file path": r"\\some\fake\server",
@ -757,3 +764,16 @@ def test_set_computer_policy_multiple_policies(clean_comp, lgpo_bin, shell):
r"\\AU[\s]*AllowMUUpdateService[\s]*DELETE",
],
)
def test__encode_xmlns_url():
"""
Tests the _encode_xmlns_url function.
Spaces in the xmlns url should be converted to %20
"""
line = '<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.0" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/Policysecurity intelligence">'
result = re.sub(
r'(.*)(\bxmlns(?::\w+)?)\s*=\s*"([^"]+)"(.*)', win_lgpo._encode_xmlns_url, line
)
expected = '<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.0" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/Policysecurity%20intelligence">'
assert result == expected

View file

@ -2,6 +2,7 @@ import asyncio
import copy
import logging
import os
import uuid
import pytest
import tornado
@ -102,12 +103,15 @@ def test_minion_load_grains_default(minion_opts):
],
)
def test_send_req_fires_completion_event(event, minion_opts):
req_id = uuid.uuid4()
event_enter = MagicMock()
event_enter.send.side_effect = event[1]
event = MagicMock()
event.__enter__.return_value = event_enter
with patch("salt.utils.event.get_event", return_value=event):
with patch("salt.utils.event.get_event", return_value=event), patch(
"uuid.uuid4", return_value=req_id
):
minion_opts["random_startup_delay"] = 0
minion_opts["return_retry_tries"] = 30
minion_opts["grains"] = {}
@ -132,7 +136,7 @@ def test_send_req_fires_completion_event(event, minion_opts):
condition_event_tag = (
len(call.args) > 1
and call.args[1]
== f"__master_req_channel_payload/{minion_opts['master']}"
== f"__master_req_channel_payload/{req_id}/{minion_opts['master']}"
)
condition_event_tag_error = (
"{} != {}; Call(number={}): {}".format(
@ -167,18 +171,18 @@ async def test_send_req_async_regression_62453(minion_opts):
event.__enter__.return_value = event_enter
minion_opts["random_startup_delay"] = 0
minion_opts["return_retry_tries"] = 30
minion_opts["return_retry_tries"] = 5
minion_opts["grains"] = {}
minion_opts["ipc_mode"] = "tcp"
with patch("salt.loader.grains"):
minion = salt.minion.Minion(minion_opts)
load = {"load": "value"}
timeout = 60
timeout = 1
# We are just validating no exception is raised
rtn = await minion._send_req_async(load, timeout)
assert rtn is False
with pytest.raises(TimeoutError):
rtn = await minion._send_req_async(load, timeout)
def test_mine_send_tries(minion_opts):

View file

@ -23,6 +23,14 @@ except AttributeError:
if HAS_PYGIT2:
import pygit2
try:
from pygit2.enums import ObjectType
HAS_PYGIT2_ENUMS = True
except ModuleNotFoundError:
HAS_PYGIT2_ENUMS = False
@pytest.fixture
def minion_opts(tmp_path):
@ -147,9 +155,14 @@ def _prepare_remote_repository_pygit2(tmp_path):
tree,
[repository.head.target],
)
repository.create_tag(
"annotated_tag", commit, pygit2.GIT_OBJ_COMMIT, signature, "some message"
)
if HAS_PYGIT2_ENUMS:
repository.create_tag(
"annotated_tag", commit, ObjectType.COMMIT, signature, "some message"
)
else:
repository.create_tag(
"annotated_tag", commit, pygit2.GIT_OBJ_COMMIT, signature, "some message"
)
return remote

View file

@ -9,72 +9,42 @@ pytestmark = [
]
def test_get_settings_firewallpolicy_local():
@pytest.mark.parametrize("store", ["local", "lgpo"])
def test_get_settings_firewallpolicy(store):
ret = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="local"
profile="domain", section="firewallpolicy", store=store
)
assert "Inbound" in ret
assert "Outbound" in ret
def test_get_settings_firewallpolicy_lgpo():
ret = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="lgpo"
)
assert "Inbound" in ret
assert "Outbound" in ret
def test_get_settings_logging_local():
ret = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)
@pytest.mark.parametrize("store", ["local", "lgpo"])
def test_get_settings_logging(store):
ret = win_lgpo_netsh.get_settings(profile="domain", section="logging", store=store)
assert "FileName" in ret
assert "LogAllowedConnections" in ret
assert "LogDroppedConnections" in ret
assert "MaxFileSize" in ret
def test_get_settings_logging_lgpo():
ret = win_lgpo_netsh.get_settings(profile="domain", section="logging", store="lgpo")
assert "FileName" in ret
assert "LogAllowedConnections" in ret
assert "LogDroppedConnections" in ret
assert "MaxFileSize" in ret
def test_get_settings_settings_local():
ret = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="local"
)
@pytest.mark.parametrize("store", ["local", "lgpo"])
def test_get_settings_settings(store):
ret = win_lgpo_netsh.get_settings(profile="domain", section="settings", store=store)
assert "InboundUserNotification" in ret
assert "LocalConSecRules" in ret
assert "LocalFirewallRules" in ret
assert "UnicastResponseToMulticast" in ret
def test_get_settings_settings_lgpo():
ret = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="lgpo"
)
assert "InboundUserNotification" in ret
assert "LocalConSecRules" in ret
assert "LocalFirewallRules" in ret
assert "UnicastResponseToMulticast" in ret
def test_get_settings_state_local():
ret = win_lgpo_netsh.get_settings(profile="domain", section="state", store="local")
@pytest.mark.parametrize("store", ["local", "lgpo"])
def test_get_settings_state(store):
ret = win_lgpo_netsh.get_settings(profile="domain", section="state", store=store)
assert "State" in ret
def test_get_settings_state_lgpo():
ret = win_lgpo_netsh.get_settings(profile="domain", section="state", store="lgpo")
assert "State" in ret
def test_get_all_settings_local():
ret = win_lgpo_netsh.get_all_settings(profile="domain", store="local")
@pytest.mark.parametrize("store", ["local", "lgpo"])
def test_get_all_settings(store):
ret = win_lgpo_netsh.get_all_settings(profile="domain", store=store)
assert "Inbound" in ret
assert "Outbound" in ret
assert "FileName" in ret
@ -88,470 +58,287 @@ def test_get_all_settings_local():
assert "State" in ret
def test_get_all_settings_lgpo():
ret = win_lgpo_netsh.get_all_settings(profile="domain", store="local")
assert "Inbound" in ret
assert "Outbound" in ret
assert "FileName" in ret
assert "LogAllowedConnections" in ret
assert "LogDroppedConnections" in ret
assert "MaxFileSize" in ret
assert "InboundUserNotification" in ret
assert "LocalConSecRules" in ret
assert "LocalFirewallRules" in ret
assert "UnicastResponseToMulticast" in ret
assert "State" in ret
def test_get_all_profiles_local():
ret = win_lgpo_netsh.get_all_profiles(store="local")
assert "Domain Profile" in ret
assert "Private Profile" in ret
assert "Public Profile" in ret
def test_get_all_profiles_lgpo():
ret = win_lgpo_netsh.get_all_profiles(store="lgpo")
@pytest.mark.parametrize("store", ["local", "lgpo"])
def test_get_all_profiles(store):
ret = win_lgpo_netsh.get_all_profiles(store=store)
assert "Domain Profile" in ret
assert "Private Profile" in ret
assert "Public Profile" in ret
@pytest.mark.destructive_test
def test_set_firewall_settings_inbound_local():
current = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="local"
)["Inbound"]
try:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound="allowinbound", store="local"
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="local"
)["Inbound"]
assert new == "AllowInbound"
finally:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound=current, store="local"
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_settings_inbound_local_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="local"
)["Inbound"]
try:
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize(
"inbound", ["allowinbound", "blockinbound", "blockinboundalways", "notconfigured"]
)
def test_set_firewall_settings_inbound(store, inbound):
if inbound == "notconfigured" and store == "local":
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_firewall_settings,
profile="domain",
inbound="notconfigured",
store="local",
inbound=inbound,
store=store,
)
finally:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound=current, store="local"
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_settings_inbound_lgpo_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="lgpo"
)["Inbound"]
try:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound="notconfigured", store="lgpo"
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="lgpo"
else:
current = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store=store
)["Inbound"]
assert new == "NotConfigured"
finally:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound=current, store="lgpo"
)
assert ret is True
try:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound=inbound, store=store
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store=store
)["Inbound"]
assert new.lower() == inbound
finally:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", inbound=current, store=store
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_settings_outbound_local():
current = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="local"
)["Outbound"]
try:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", outbound="allowoutbound", store="local"
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize(
"outbound", ["allowoutbound", "blockoutbound", "notconfigured"]
)
def test_set_firewall_settings_outbound(store, outbound):
if outbound == "notconfigured" and store == "local":
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_firewall_settings,
profile="domain",
inbound=outbound,
store=store,
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store="local"
else:
current = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store=store
)["Outbound"]
assert new == "AllowOutbound"
finally:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", outbound=current, store="local"
)
assert ret is True
try:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", outbound=outbound, store=store
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="firewallpolicy", store=store
)["Outbound"]
assert new.lower() == outbound
finally:
ret = win_lgpo_netsh.set_firewall_settings(
profile="domain", outbound=current, store=store
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_logging_allowed_local_enable():
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["LogAllowedConnections"]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="allowedconnections",
value="enable",
store="local",
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["LogAllowedConnections"]
assert new == "Enable"
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="allowedconnections",
value=current,
store="local",
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_logging_allowed_local_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["LogAllowedConnections"]
try:
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize("setting", ["allowedconnections", "droppedconnections"])
@pytest.mark.parametrize("value", ["enable", "disable", "notconfigured"])
def test_set_firewall_logging_connections(store, setting, value):
if value == "notconfigured" and store == "local":
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_logging_settings,
profile="domain",
setting="allowedconnections",
value="notconfigured",
store="local",
setting=setting,
value=value,
store=store,
)
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="allowedconnections",
value=current,
store="local",
)
assert ret is True
else:
setting_map = {
"allowedconnections": "LogAllowedConnections",
"droppedconnections": "LogDroppedConnections",
}
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store=store
)[setting_map[setting]]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting=setting,
value=value,
store=store,
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store=store
)[setting_map[setting]]
assert new.lower() == value
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting=setting,
value=current,
store=store,
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_logging_allowed_lgpo_notconfigured():
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize("value", ["C:\\Temp\\test.log", "notconfigured"])
def test_set_firewall_logging_filename(store, value):
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="lgpo"
)["LogAllowedConnections"]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="allowedconnections",
value="notconfigured",
store="lgpo",
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="lgpo"
)["LogAllowedConnections"]
assert new == "NotConfigured"
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="allowedconnections",
value=current,
store="lgpo",
)
assert ret is True
def test_set_firewall_logging_dropped_local_enable():
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["LogDroppedConnections"]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="droppedconnections",
value="enable",
store="local",
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["LogDroppedConnections"]
assert new == "Enable"
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="droppedconnections",
value=current,
store="local",
)
assert ret is True
def test_set_firewall_logging_filename_local():
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
profile="domain", section="logging", store=store
)["FileName"]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain",
setting="filename",
value="C:\\Temp\\test.log",
store="local",
value=value,
store=store,
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
profile="domain", section="logging", store=store
)["FileName"]
assert new == "C:\\Temp\\test.log"
assert new.lower() == value.lower()
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain", setting="filename", value=current, store="local"
profile="domain", setting="filename", value=current, store=store
)
assert ret is True
def test_set_firewall_logging_maxfilesize_local():
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["MaxFileSize"]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain", setting="maxfilesize", value="16384", store="local"
@pytest.mark.destructive_test
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize("value", ["16384", "notconfigured"])
def test_set_firewall_logging_maxfilesize(store, value):
if value == "notconfigured":
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_logging_settings,
profile="domain",
setting="maxfilesize",
value=value,
store=store,
)
else:
current = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store=store
)["MaxFileSize"]
try:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain", setting="maxfilesize", value=value, store=store
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store=store
)["MaxFileSize"]
assert new == int(value)
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain", setting="maxfilesize", value=current, store=store
)
assert ret is True
@pytest.mark.destructive_test
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize(
"setting",
["localconsecrules", "inboundusernotification", "unicastresponsetomulticast"],
)
@pytest.mark.parametrize("value", ["enable", "disable", "notconfigured"])
def test_set_firewall_settings(store, setting, value):
setting_map = {
"localconsecrules": "LocalConSecRules",
"inboundusernotification": "InboundUserNotification",
"unicastresponsetomulticast": "UnicastResponseToMulticast",
}
if value == "notconfigured" and store == "local":
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_settings,
profile="domain",
setting=setting,
value=value,
store=store,
)
else:
current = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store=store
)[setting_map[setting]]
try:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting=setting,
value=value,
store=store,
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store=store
)[setting_map[setting]]
assert new.lower() == value
finally:
if current != "notconfigured":
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting=setting,
value=current,
store=store,
)
assert ret is True
@pytest.mark.destructive_test
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize("state", ["on", "off", "notconfigured"])
def test_set_firewall_state(store, state):
current_state = win_lgpo_netsh.get_settings(
profile="domain", section="state", store=store
)["State"]
try:
ret = win_lgpo_netsh.set_state(profile="domain", state=state, store=store)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="logging", store="local"
)["MaxFileSize"]
assert new == 16384
profile="domain", section="state", store=store
)["State"]
assert new.lower() == state.lower()
finally:
ret = win_lgpo_netsh.set_logging_settings(
profile="domain", setting="maxfilesize", value=current, store="local"
)
assert ret is True
win_lgpo_netsh.set_state(profile="domain", state=current_state, store=store)
@pytest.mark.destructive_test
def test_set_firewall_settings_fwrules_local_enable():
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_settings,
profile="domain",
setting="localfirewallrules",
value="enable",
store="local",
)
@pytest.mark.destructive_test
def test_set_firewall_settings_fwrules_lgpo_notconfigured():
current = win_lgpo_netsh.get_settings(
@pytest.mark.parametrize("store", ["local", "lgpo"])
@pytest.mark.parametrize("allow_inbound", ["enable", "disable"])
@pytest.mark.parametrize("state", ["on", "off", "notconfigured"])
def test_set_firewall_state_allow_inbound(store, allow_inbound, state):
current_state = win_lgpo_netsh.get_settings(
profile="domain", section="state", store=store
)["State"]
current_local_fw_rules = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="lgpo"
)["LocalFirewallRules"]
try:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="localfirewallrules",
value="notconfigured",
store="lgpo",
value=allow_inbound,
store=store,
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="lgpo"
profile="domain", section="settings", store=store
)["LocalFirewallRules"]
assert new == "NotConfigured"
finally:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="localfirewallrules",
value=current,
store="lgpo",
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_settings_consecrules_local_enable():
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_settings,
profile="domain",
setting="localconsecrules",
value="enable",
store="local",
)
def test_set_firewall_settings_notification_local_enable():
current = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="local"
)["InboundUserNotification"]
try:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="inboundusernotification",
value="enable",
store="local",
)
assert new.lower() == allow_inbound.lower()
ret = win_lgpo_netsh.set_state(profile="domain", state=state, store=store)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="local"
)["InboundUserNotification"]
assert new == "Enable"
finally:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="inboundusernotification",
value=current,
store="local",
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_settings_notification_local_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="local"
)["InboundUserNotification"]
try:
pytest.raises(
CommandExecutionError,
win_lgpo_netsh.set_settings,
profile="domain",
setting="inboundusernotification",
value="notconfigured",
store="local",
)
finally:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="inboundusernotification",
value=current,
store="local",
)
assert ret is True
def test_set_firewall_settings_notification_lgpo_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="lgpo"
)["InboundUserNotification"]
try:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="inboundusernotification",
value="notconfigured",
store="lgpo",
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="lgpo"
)["InboundUserNotification"]
assert new == "NotConfigured"
finally:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="inboundusernotification",
value=current,
store="lgpo",
)
assert ret is True
def test_set_firewall_settings_unicast_local_disable():
current = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="local"
)["UnicastResponseToMulticast"]
try:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="unicastresponsetomulticast",
value="disable",
store="local",
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="settings", store="local"
)["UnicastResponseToMulticast"]
assert new == "Disable"
finally:
ret = win_lgpo_netsh.set_settings(
profile="domain",
setting="unicastresponsetomulticast",
value=current,
store="local",
)
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_state_local_on():
current = win_lgpo_netsh.get_settings(
profile="domain", section="state", store="local"
)["State"]
try:
ret = win_lgpo_netsh.set_state(profile="domain", state="off", store="local")
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="state", store="local"
profile="domain", section="state", store=store
)["State"]
assert new == "OFF"
assert new.lower() == state.lower()
finally:
ret = win_lgpo_netsh.set_state(profile="domain", state=current, store="local")
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_state_local_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="state", store="local"
)["State"]
try:
ret = win_lgpo_netsh.set_state(
profile="domain",
state="notconfigured",
store="local",
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="state", store="local"
)["State"]
assert new == "NotConfigured"
finally:
ret = win_lgpo_netsh.set_state(profile="domain", state=current, store="local")
assert ret is True
@pytest.mark.destructive_test
def test_set_firewall_state_lgpo_notconfigured():
current = win_lgpo_netsh.get_settings(
profile="domain", section="state", store="local"
)["State"]
try:
ret = win_lgpo_netsh.set_state(
profile="domain", state="notconfigured", store="lgpo"
)
assert ret is True
new = win_lgpo_netsh.get_settings(
profile="domain", section="state", store="lgpo"
)["State"]
assert new == "NotConfigured"
finally:
ret = win_lgpo_netsh.set_state(profile="domain", state=current, store="lgpo")
assert ret is True
if current_local_fw_rules.lower() != "notconfigured":
win_lgpo_netsh.set_settings(
profile="domain",
setting="localfirewallrules",
value=current_local_fw_rules,
store=store,
)
win_lgpo_netsh.set_state(profile="domain", state=current_state, store=store)

View file

@ -349,198 +349,6 @@ class TestRun(TypedDict):
selected_tests: NotRequired[dict[str, bool]]
@ci.command(
name="define-testrun",
arguments={
"event_name": {
"help": "The name of the GitHub event being processed.",
},
"changed_files": {
"help": (
"Path to '.json' file containing the payload of changed files "
"from the 'dorny/paths-filter' GitHub action."
),
},
},
)
def define_testrun(ctx: Context, event_name: str, changed_files: pathlib.Path):
"""
Set GH Actions outputs for what and how Salt should be tested.
"""
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output is None:
ctx.warn("The 'GITHUB_OUTPUT' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert github_output is not None
github_step_summary = os.environ.get("GITHUB_STEP_SUMMARY")
if github_step_summary is None:
ctx.warn("The 'GITHUB_STEP_SUMMARY' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert github_step_summary is not None
labels: list[str] = []
gh_event_path = os.environ.get("GITHUB_EVENT_PATH") or None
if gh_event_path is not None:
try:
gh_event = json.loads(open(gh_event_path, encoding="utf-8").read())
except Exception as exc:
ctx.error(
f"Could not load the GH Event payload from {gh_event_path!r}:\n", exc # type: ignore[arg-type]
)
ctx.exit(1)
labels.extend(
label[0] for label in _get_pr_test_labels_from_event_payload(gh_event)
)
if "test:coverage" in labels:
ctx.info("Writing 'testrun' to the github outputs file")
# skip running code coverage for now, was False
testrun = TestRun(type="full", skip_code_coverage=True)
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"testrun={json.dumps(testrun)}\n")
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write(
"Full test run chosen because the label `test:coverage` is set.\n"
)
return
elif event_name != "pull_request":
# In this case, a full test run is in order
ctx.info("Writing 'testrun' to the github outputs file")
# skip running code coverage for now, was False
testrun = TestRun(type="full", skip_code_coverage=True)
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"testrun={json.dumps(testrun)}\n")
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write(f"Full test run chosen due to event type of `{event_name}`.\n")
return
# So, it's a pull request...
if not changed_files.exists():
ctx.error(f"The '{changed_files}' file does not exist.")
ctx.error(
"FYI, the command 'tools process-changed-files <changed-files-path>' "
"needs to run prior to this one."
)
ctx.exit(1)
try:
changed_files_contents = json.loads(changed_files.read_text())
except Exception as exc:
ctx.error(f"Could not load the changed files from '{changed_files}': {exc}")
ctx.exit(1)
# Based on which files changed, or other things like PR labels we can
# decide what to run, or even if the full test run should be running on the
# pull request, etc...
changed_pkg_requirements_files = json.loads(
changed_files_contents["pkg_requirements_files"]
)
changed_test_requirements_files = json.loads(
changed_files_contents["test_requirements_files"]
)
if changed_files_contents["golden_images"] == "true":
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write(
"Full test run chosen because there was a change made "
"to `cicd/golden-images.json`.\n"
)
testrun = TestRun(type="full", skip_code_coverage=True)
elif changed_pkg_requirements_files or changed_test_requirements_files:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write(
"Full test run chosen because there was a change made "
"to the requirements files.\n"
)
wfh.write(
"<details>\n<summary>Changed Requirements Files (click me)</summary>\n<pre>\n"
)
for path in sorted(
changed_pkg_requirements_files + changed_test_requirements_files
):
wfh.write(f"{path}\n")
wfh.write("</pre>\n</details>\n")
testrun = TestRun(type="full", skip_code_coverage=True)
elif "test:full" in labels:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("Full test run chosen because the label `test:full` is set.\n")
testrun = TestRun(type="full", skip_code_coverage=True)
else:
testrun_changed_files_path = tools.utils.REPO_ROOT / "testrun-changed-files.txt"
testrun = TestRun(
type="changed",
skip_code_coverage=True,
from_filenames=str(
testrun_changed_files_path.relative_to(tools.utils.REPO_ROOT)
),
)
ctx.info(f"Writing {testrun_changed_files_path.name} ...")
selected_changed_files = []
for fpath in json.loads(changed_files_contents["testrun_files"]):
if fpath.startswith(("tools/", "tasks/")):
continue
if fpath in ("noxfile.py",):
continue
if fpath == "tests/conftest.py":
# In this particular case, just run the full test suite
testrun["type"] = "full"
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write(
f"Full test run chosen because there was a change to `{fpath}`.\n"
)
selected_changed_files.append(fpath)
testrun_changed_files_path.write_text("\n".join(sorted(selected_changed_files)))
if testrun["type"] == "changed":
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("Partial test run chosen.\n")
testrun["selected_tests"] = {
"core": False,
"slow": False,
"fast": True,
"flaky": False,
}
if "test:slow" in labels:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("Slow tests chosen by `test:slow` label.\n")
testrun["selected_tests"]["slow"] = True
if "test:core" in labels:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("Core tests chosen by `test:core` label.\n")
testrun["selected_tests"]["core"] = True
if "test:no-fast" in labels:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("Fast tests deselected by `test:no-fast` label.\n")
testrun["selected_tests"]["fast"] = False
if "test:flaky-jail" in labels:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("Flaky jailed tests chosen by `test:flaky-jail` label.\n")
testrun["selected_tests"]["flaky"] = True
if selected_changed_files:
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write(
"<details>\n<summary>Selected Changed Files (click me)</summary>\n<pre>\n"
)
for path in sorted(selected_changed_files):
wfh.write(f"{path}\n")
wfh.write("</pre>\n</details>\n")
with open(github_step_summary, "a", encoding="utf-8") as wfh:
wfh.write("<details>\n<summary>All Changed Files (click me)</summary>\n<pre>\n")
for path in sorted(json.loads(changed_files_contents["repo_files"])):
wfh.write(f"{path}\n")
wfh.write("</pre>\n</details>\n")
ctx.info("Writing 'testrun' to the github outputs file:\n", testrun)
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"testrun={json.dumps(testrun)}\n")
def _build_matrix(os_kind, linux_arm_runner):
"""
Generate matrix for build ci/cd steps.
@ -558,466 +366,6 @@ def _build_matrix(os_kind, linux_arm_runner):
return _matrix
@ci.command(
arguments={
"distro_slug": {
"help": "The distribution slug to generate the matrix for",
},
"full": {
"help": "Full test run",
},
"workflow": {
"help": "Which workflow is running",
},
},
)
def matrix(
ctx: Context,
distro_slug: str,
full: bool = False,
workflow: str = "ci",
):
"""
Generate the test matrix.
"""
gh_event_path = os.environ.get("GITHUB_EVENT_PATH") or None
if gh_event_path is None:
ctx.warn("The 'GITHUB_EVENT_PATH' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event_path is not None
gh_event = None
try:
gh_event = json.loads(open(gh_event_path, encoding="utf-8").read())
except Exception as exc:
ctx.error(f"Could not load the GH Event payload from {gh_event_path!r}:\n", exc)
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event is not None
_matrix = []
_splits = {
"functional": 4,
"integration": 7,
"scenarios": 1,
"unit": 4,
}
for transport in ("zeromq", "tcp"):
if transport == "tcp":
if distro_slug not in (
"rockylinux-9",
"rockylinux-9-arm64",
"photonos-5",
"photonos-5-arm64",
"ubuntu-22.04",
"ubuntu-22.04-arm64",
):
# Only run TCP transport tests on these distributions
continue
for chunk in ("unit", "functional", "integration", "scenarios"):
if transport == "tcp" and chunk in ("unit", "functional"):
# Only integration and scenarios shall be tested under TCP,
# the rest would be repeating tests
continue
if "macos" in distro_slug and chunk == "scenarios":
continue
splits = _splits.get(chunk) or 1
if full and splits > 1:
for split in range(1, splits + 1):
_matrix.append(
{
"transport": transport,
"tests-chunk": chunk,
"test-group": split,
"test-group-count": splits,
}
)
else:
_matrix.append({"transport": transport, "tests-chunk": chunk})
ctx.info("Generated matrix:")
if not _matrix:
ctx.print(" * `None`")
else:
for entry in _matrix:
ctx.print(" * ", entry, soft_wrap=True)
if (
gh_event["repository"]["fork"] is True
and "macos" in distro_slug
and "arm64" in distro_slug
):
ctx.warn("Forks don't have access to MacOS 13 Arm64. Clearning the matrix.")
_matrix.clear()
if not _matrix:
build_reports = False
ctx.info("Not building reports because the matrix is empty")
else:
build_reports = True
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output is not None:
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"matrix={json.dumps(_matrix)}\n")
wfh.write(f"build-reports={json.dumps(build_reports)}\n")
ctx.exit(0)
@ci.command(
name="pkg-matrix",
arguments={
"distro_slug": {
"help": "The distribution slug to generate the matrix for",
},
"pkg_type": {
"help": "The type of package we are testing against",
},
"testing_releases": {
"help": "The salt releases to test upgrades against",
"nargs": "+",
"required": True,
},
},
)
def pkg_matrix(
ctx: Context,
distro_slug: str,
pkg_type: str,
testing_releases: list[tools.utils.Version] = None,
):
"""
Generate the test matrix.
"""
gh_event = None
gh_event_path = os.environ.get("GITHUB_EVENT_PATH") or None
if gh_event_path is None:
ctx.warn("The 'GITHUB_EVENT_PATH' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event_path is not None
try:
gh_event = json.loads(open(gh_event_path, encoding="utf-8").read())
except Exception as exc:
ctx.error(f"Could not load the GH Event payload from {gh_event_path!r}:\n", exc)
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event is not None
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output is None:
ctx.warn("The 'GITHUB_OUTPUT' variable is not set.")
if TYPE_CHECKING:
assert testing_releases
adjusted_versions = []
for ver in testing_releases:
adjusted_versions.append((ver, "relenv"))
ctx.info(f"Will look for the following versions: {adjusted_versions}")
# Filter out the prefixes to look under
if "macos-" in distro_slug:
# We don't have golden images for macos, handle these separately
prefixes = {
"classic": "osx/",
"tiamat": "salt/py3/macos/minor/",
"relenv": "salt/py3/macos/minor/",
}
name = "macos"
else:
parts = distro_slug.split("-")
name = parts[0]
version = parts[1]
if len(parts) > 2:
arch = parts[2]
elif name in ("debian", "ubuntu"):
arch = "amd64"
else:
arch = "x86_64"
ctx.info(f"Parsed linux slug parts {name} {version} {arch}")
if name == "amazonlinux":
name = "amazon"
elif name == "rockylinux":
name = "redhat"
elif "photon" in name:
name = "photon"
if name == "windows":
prefixes = {
"classic": "windows/",
"tiamat": "salt/py3/windows/minor",
"relenv": "salt/py3/windows/minor",
}
else:
prefixes = {
"classic": f"py3/{name}/{version}/{arch}/",
"tiamat": f"salt/py3/{name}/{version}/{arch}/minor/",
"relenv": f"salt/py3/{name}/{version}/{arch}/minor/",
}
_matrix = []
# XXX: fetch versions
# s3 = boto3.client("s3")
# paginator = s3.get_paginator("list_objects_v2")
_matrix = [
{
"tests-chunk": "install",
"version": None,
}
]
for version, backend in adjusted_versions:
prefix = prefixes[backend]
# TODO: Remove this after 3009.0
if backend == "relenv" and version >= tools.utils.Version("3006.5"):
prefix.replace("/arm64/", "/aarch64/")
# Using a paginator allows us to list recursively and avoid the item limit
# page_iterator = paginator.paginate(
# Bucket=f"salt-project-{tools.utils.SPB_ENVIRONMENT}-salt-artifacts-release",
# Prefix=prefix,
# )
# Uses a jmespath expression to test if the wanted version is in any of the filenames
# key_filter = f"Contents[?contains(Key, '{version}')][]"
# if pkg_type == "MSI":
# # TODO: Add this back when we add MSI upgrade and downgrade tests
# # key_filter = f"Contents[?contains(Key, '{version}')] | [?ends_with(Key, '.msi')]"
# continue
# elif pkg_type == "NSIS":
# key_filter = (
# f"Contents[?contains(Key, '{version}')] | [?ends_with(Key, '.exe')]"
# )
# continue
# objects = list(page_iterator.search(key_filter))
# Testing using `any` because sometimes the paginator returns `[None]`
# if any(objects):
# ctx.info(
# f"Found {version} ({backend}) for {distro_slug}: {objects[0]['Key']}"
# )
# for session in ("upgrade", "downgrade"):
# if backend == "classic":
# session += "-classic"
# _matrix.append(
# {
# "tests-chunk": session,
# "version": str(version),
# }
# )
# else:
# ctx.info(f"No {version} ({backend}) for {distro_slug} at {prefix}")
if name == "windows":
sessions = [
"upgrade",
]
else:
sessions = ["upgrade", "downgrade"]
for session in sessions:
_matrix.append(
{
"tests-chunk": session,
"version": str(version),
}
)
ctx.info("Generated matrix:")
if not _matrix:
ctx.print(" * `None`")
else:
for entry in _matrix:
ctx.print(" * ", entry, soft_wrap=True)
# if (
# gh_event["repository"]["fork"] is True
# and "macos" in distro_slug
# and "arm64" in distro_slug
# ):
# # XXX: This should work now
# ctx.warn("Forks don't have access to MacOS 13 Arm64. Clearning the matrix.")
# _matrix.clear()
if (
arch == "arm64"
and name not in ["windows", "macos"]
and os.environ.get("LINUX_ARM_RUNNER", "0") not in ("0", "")
):
ctx.warn("This fork does not have a linux arm64 runner configured.")
_matrix.clear()
if not _matrix:
build_reports = False
ctx.info("Not building reports because the matrix is empty")
else:
build_reports = True
if github_output is not None:
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"matrix={json.dumps(_matrix)}\n")
wfh.write(f"build-reports={json.dumps(build_reports)}\n")
ctx.exit(0)
@ci.command(name="deps-matrix")
def get_ci_deps_matrix(ctx: Context):
gh_event_path = os.environ.get("GITHUB_EVENT_PATH") or None
if gh_event_path is None:
ctx.warn("The 'GITHUB_EVENT_PATH' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event_path is not None
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output is None:
ctx.warn("The 'GITHUB_OUTPUT' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert github_output is not None
gh_event = None
try:
gh_event = json.loads(open(gh_event_path, encoding="utf-8").read())
except Exception as exc:
ctx.error(f"Could not load the GH Event payload from {gh_event_path!r}:\n", exc)
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event is not None
_matrix = {
"linux": [
{"arch": "x86_64"},
],
"macos": [
{"distro-slug": "macos-13", "arch": "x86_64"},
{"distro-slug": "macos-14", "arch": "arm64"},
],
"windows": [
{"distro-slug": "windows-2022", "arch": "amd64"},
],
}
if os.environ.get("LINUX_ARM_RUNNER", "0") not in ("0", ""):
_matrix["linux"].append({"arch": "arm64"})
ctx.info("Generated matrix:")
ctx.print(_matrix, soft_wrap=True)
if github_output is not None:
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"matrix={json.dumps(_matrix)}\n")
ctx.exit(0)
@ci.command(name="pkg-downloads-matrix")
def get_pkg_downloads_matrix(ctx: Context):
gh_event_path = os.environ.get("GITHUB_EVENT_PATH") or None
if gh_event_path is None:
ctx.warn("The 'GITHUB_EVENT_PATH' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event_path is not None
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output is None:
ctx.warn("The 'GITHUB_OUTPUT' variable is not set.")
ctx.exit(1)
if TYPE_CHECKING:
assert github_output is not None
gh_event = None
try:
gh_event = json.loads(open(gh_event_path, encoding="utf-8").read())
except Exception as exc:
ctx.error(f"Could not load the GH Event payload from {gh_event_path!r}:\n", exc)
ctx.exit(1)
if TYPE_CHECKING:
assert gh_event is not None
_matrix: dict[str, list[dict[str, str]]] = {
"linux": [],
"macos": [],
"windows": [],
}
rpm_slugs = (
"rockylinux",
"amazonlinux",
"fedora",
"photon",
)
linux_skip_pkg_download_tests = (
"opensuse-15",
"windows",
)
for slug in sorted(tools.utils.get_golden_images()):
if slug.startswith(linux_skip_pkg_download_tests):
continue
if "arm64" in slug:
arch = "arm64"
else:
arch = "x86_64"
if slug.startswith(rpm_slugs) and arch == "arm64":
# While we maintain backwards compatible urls
_matrix["linux"].append(
{"distro-slug": slug, "arch": "aarch64", "pkg-type": "package"}
)
_matrix["linux"].append(
{"distro-slug": slug, "arch": arch, "pkg-type": "package"}
)
if slug.startswith("ubuntu-22"):
_matrix["linux"].append(
{"distro-slug": slug, "arch": arch, "pkg-type": "onedir"}
)
for mac in TEST_SALT_LISTING["macos"]:
if gh_event["repository"]["fork"] is True and mac.arch == "arm64":
continue
_matrix["macos"].append(
{"distro-slug": mac.slug, "arch": mac.arch, "pkg-type": "package"}
)
if gh_event["repository"]["fork"] is True:
macos_idx = 0 # macos-12
else:
macos_idx = 1 # macos-13
_matrix["macos"].append(
{
"distro-slug": TEST_SALT_LISTING["macos"][macos_idx].slug,
"arch": TEST_SALT_LISTING["macos"][macos_idx].arch,
"pkg-type": "onedir",
}
)
for win in TEST_SALT_LISTING["windows"][-1:]:
for pkg_type in ("nsis", "msi", "onedir"):
_matrix["windows"].append(
{
"distro-slug": win.slug,
"arch": win.arch,
"pkg-type": pkg_type,
}
)
ctx.info("Generated matrix:")
ctx.print(_matrix, soft_wrap=True)
if github_output is not None:
with open(github_output, "a", encoding="utf-8") as wfh:
wfh.write(f"matrix={json.dumps(_matrix)}\n")
ctx.exit(0)
@ci.command(
name="get-releases",
arguments={
@ -1530,10 +878,12 @@ def upload_coverage(ctx: Context, reports_path: pathlib.Path, commit_sha: str =
ctx.exit(0)
def _os_test_filter(osdef, transport, chunk, arm_runner):
def _os_test_filter(osdef, transport, chunk, arm_runner, requested_slugs):
"""
Filter out some test runs based on os, tranport and chunk to be run.
"""
if osdef.slug not in requested_slugs:
return False
if transport == "tcp" and chunk in ("unit", "functional"):
return False
if "macos" in osdef.slug and chunk == "scenarios":
@ -1552,6 +902,160 @@ def _os_test_filter(osdef, transport, chunk, arm_runner):
return True
def _define_testrun(ctx, changed_files, labels, full):
if not changed_files.exists():
ctx.error(f"The '{changed_files}' file does not exist.")
ctx.error(
"FYI, the command 'tools process-changed-files <changed-files-path>' "
"needs to run prior to this one."
)
ctx.exit(1)
try:
changed_files_contents = json.loads(changed_files.read_text())
except Exception as exc:
ctx.error(f"Could not load the changed files from '{changed_files}': {exc}")
ctx.exit(1)
# Based on which files changed, or other things like PR labels we can
# decide what to run, or even if the full test run should be running on the
# pull request, etc...
changed_pkg_requirements_files: list[str] = []
changed_test_requirements_files: list[str] = []
if "pkg_requirements_files" in changed_files_contents:
changed_pkg_requirements_files = json.loads(
changed_files_contents["pkg_requirements_files"]
)
if "test_requirements_files" in changed_files_contents:
changed_test_requirements_files = json.loads(
changed_files_contents["test_requirements_files"]
)
if full:
ctx.info("Full test run chosen")
testrun = TestRun(type="full", skip_code_coverage=True)
elif changed_pkg_requirements_files or changed_test_requirements_files:
ctx.info(
"Full test run chosen because there was a change made "
"to the requirements files."
)
testrun = TestRun(type="full", skip_code_coverage=True)
elif "test:full" in labels:
ctx.info("Full test run chosen because the label `test:full` is set.\n")
testrun = TestRun(type="full", skip_code_coverage=True)
else:
testrun_changed_files_path = tools.utils.REPO_ROOT / "testrun-changed-files.txt"
testrun = TestRun(
type="changed",
skip_code_coverage=True,
from_filenames=str(
testrun_changed_files_path.relative_to(tools.utils.REPO_ROOT)
),
)
ctx.info(f"Writing {testrun_changed_files_path.name} ...")
selected_changed_files = []
for fpath in json.loads(changed_files_contents["testrun_files"]):
if fpath.startswith(("tools/", "tasks/")):
continue
if fpath in ("noxfile.py",):
continue
if fpath == "tests/conftest.py":
# In this particular case, just run the full test suite
testrun["type"] = "full"
ctx.info(
f"Full test run chosen because there was a change to `{fpath}`."
)
selected_changed_files.append(fpath)
testrun_changed_files_path.write_text("\n".join(sorted(selected_changed_files)))
if testrun["type"] == "changed":
testrun["selected_tests"] = {
"core": False,
"slow": False,
"fast": True,
"flaky": False,
}
if "test:slow" in labels:
ctx.info("Slow tests chosen by `test:slow` label.")
testrun["selected_tests"]["slow"] = True
if "test:core" in labels:
ctx.info("Core tests chosen by `test:core` label.")
testrun["selected_tests"]["core"] = True
if "test:no-fast" in labels:
ctx.info("Fast tests deselected by `test:no-fast` label.")
testrun["selected_tests"]["fast"] = False
if "test:flaky-jail" in labels:
ctx.info("Flaky jailed tests chosen by `test:flaky-jail` label.")
testrun["selected_tests"]["flaky"] = True
return testrun
def _environment_slugs(ctx, slugdef, labels):
"""
Based a slugs defenition from our environment and labels for a pr, return
the requeted slugs for a testrun.
Environment slug defenitions can be a comma separated list. An "all" item
in the list will include all os and package slugs.
"""
if isinstance(slugdef, list):
requests = slugdef
else:
requests = [_.strip().lower() for _ in slugdef.split(",") if _.strip()]
label_requests = [
_[0].rsplit(":", 1)[1] for _ in labels if _[0].startswith("test:os:")
]
all_slugs = []
slugs = set()
for platform in TEST_SALT_LISTING:
for osdef in TEST_SALT_LISTING[platform]:
all_slugs.append(osdef.slug)
for platform in TEST_SALT_LISTING:
for osdef in TEST_SALT_LISTING[platform]:
all_slugs.append(osdef.slug)
if "all" in requests:
slugs = all_slugs[:]
requests.remove("all")
if "all" in label_requests:
slugs = all_slugs[:]
label_requests.remove("all")
for request in requests[:]:
if request.startswith("+"):
request = request.strip("+")
if request not in all_slugs:
ctx.warn(f"invalid slug name from environment {request}")
continue
if request in slugs:
ctx.info("slug already requested from environment {request}")
continue
slugs.add(request)
elif request.startswith("-"):
request = request.strip("-")
if request not in all_slugs:
ctx.warn(f"invalid slug name from environment {request}")
continue
if request in slugs:
slugs.remove(request)
else:
ctx.info("slug from environment was never requested {request}")
else:
if request not in all_slugs:
ctx.warn(f"invalid slug name from environment {request}")
continue
if request in slugs:
ctx.info("slug from environment already requested {request}")
continue
slugs.add(request)
for label in label_requests:
if label not in all_slugs:
ctx.warn(f"invalid slug name from label {label}")
continue
if label in slugs:
ctx.info(f"slug from labels already requested {label}")
continue
slugs.add(label)
return list(slugs)
@ci.command(
name="workflow-config",
arguments={
@ -1589,19 +1093,17 @@ def workflow_config(
):
full = False
gh_event_path = os.environ.get("GITHUB_EVENT_PATH") or None
gh_event = None
gh_event: dict[str, Any] = {}
config: dict[str, Any] = {}
labels: list[tuple[str, str]] = []
slugs: list[str] = []
ctx.info(f"{'==== environment ====':^80s}")
ctx.info(f"{pprint.pformat(dict(os.environ))}")
ctx.info(f"{'==== end environment ====':^80s}")
ctx.info(f"Github event path is {gh_event_path}")
if event_name != "pull_request":
full = True
if gh_event_path is None:
labels = []
config["linux_arm_runner"] = ""
else:
try:
@ -1616,7 +1118,6 @@ def workflow_config(
pr = gh_event["pull_request"]["number"]
labels = _get_pr_test_labels_from_event_payload(gh_event)
else:
labels = []
ctx.warn("The 'pull_request' key was not found on the event payload.")
if gh_event["repository"]["private"]:
@ -1630,14 +1131,44 @@ def workflow_config(
# Public repositories can use github's arm64 runners.
config["linux_arm_runner"] = "ubuntu-24.04-arm"
if event_name != "pull_request" or "test:full" in [_[0] for _ in labels]:
full = True
requested_slugs = _environment_slugs(
ctx,
tools.utils.get_cicd_shared_context()["full-testrun-slugs"],
labels,
)
else:
requested_slugs = _environment_slugs(
ctx,
tools.utils.get_cicd_shared_context()["pr-testrun-slugs"],
labels,
)
ctx.info(f"{'==== requested slugs ====':^80s}")
ctx.info(f"{pprint.pformat(requested_slugs)}")
ctx.info(f"{'==== end requested slugs ====':^80s}")
ctx.info(f"{'==== labels ====':^80s}")
ctx.info(f"{pprint.pformat(labels)}")
ctx.info(f"{'==== end labels ====':^80s}")
config["skip_code_coverage"] = True
if "test:coverage" in labels:
config["skip_code_coverage"] = False
else:
ctx.info("Skipping code coverage.")
ctx.info(f"{'==== github event ====':^80s}")
ctx.info(f"{pprint.pformat(gh_event)}")
ctx.info(f"{'==== end github event ====':^80s}")
config["testrun"] = _define_testrun(ctx, changed_files, labels, full)
ctx.info(f"{'==== testrun ====':^80s}")
ctx.info(f"{pprint.pformat(config['testrun'])}")
ctx.info(f"{'==== testrun ====':^80s}")
jobs = {
"lint": True,
"test": True,
@ -1649,7 +1180,7 @@ def workflow_config(
"build-deps-onedir": True,
"build-salt-onedir": True,
"build-pkgs": True,
"build-deps-ci": True,
"build-deps-ci": True if requested_slugs else False,
}
platforms: list[Literal["linux", "macos", "windows"]] = [
@ -1730,6 +1261,7 @@ def workflow_config(
**_.as_dict(),
)
for _ in TEST_SALT_PKG_LISTING[platform]
if _.slug in requested_slugs
]
for version in str_releases:
for platform in platforms:
@ -1742,6 +1274,7 @@ def workflow_config(
**_.as_dict(),
)
for _ in TEST_SALT_PKG_LISTING[platform]
if _.slug in requested_slugs
]
# Skipping downgrade tests on windows. These tests have never
# been run and currently fail. This should be fixed.
@ -1756,6 +1289,7 @@ def workflow_config(
**_.as_dict(),
)
for _ in TEST_SALT_PKG_LISTING[platform]
if _.slug in requested_slugs
]
ctx.info(f"{'==== pkg test matrix ====':^80s}")
ctx.info(f"{pprint.pformat(pkg_test_matrix)}")
@ -1793,7 +1327,11 @@ def workflow_config(
)
for _ in TEST_SALT_LISTING[platform]
if _os_test_filter(
_, transport, chunk, config["linux_arm_runner"]
_,
transport,
chunk,
config["linux_arm_runner"],
requested_slugs,
)
]
else:
@ -1816,6 +1354,7 @@ def workflow_config(
transport,
chunk,
config["linux_arm_runner"],
requested_slugs,
)
and _.arch == arch
]
@ -1830,7 +1369,11 @@ def workflow_config(
)
for _ in TEST_SALT_LISTING[platform]
if _os_test_filter(
_, transport, chunk, config["linux_arm_runner"]
_,
transport,
chunk,
config["linux_arm_runner"],
requested_slugs,
)
]
else:
@ -1844,7 +1387,11 @@ def workflow_config(
)
for _ in TEST_SALT_LISTING[platform]
if _os_test_filter(
_, transport, chunk, config["linux_arm_runner"]
_,
transport,
chunk,
config["linux_arm_runner"],
requested_slugs,
)
and _.arch == arch
]