mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 11:12:12 -05:00
Compare commits
186 Commits
executor_v
...
v0.21.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dd7d4507e | ||
|
|
54da3fc268 | ||
|
|
ce94c9ad29 | ||
|
|
f80bdc2909 | ||
|
|
f0fcdf4851 | ||
|
|
2aeb11e2da | ||
|
|
368f4f22db | ||
|
|
3f7c385aba | ||
|
|
59dc598ee9 | ||
|
|
49a8d13b85 | ||
|
|
67e4154069 | ||
|
|
4bb2de219d | ||
|
|
27c8762628 | ||
|
|
3d29077a99 | ||
|
|
33ac1ac535 | ||
|
|
eec8fb9fb4 | ||
|
|
df5d13467a | ||
|
|
65999abb3f | ||
|
|
58d82b79f8 | ||
|
|
5131c347cd | ||
|
|
b7259cc796 | ||
|
|
ea26e4dda7 | ||
|
|
5b1d216c40 | ||
|
|
49977f505f | ||
|
|
1dc8401a0e | ||
|
|
c004ba1a6b | ||
|
|
cfe4e2a3c2 | ||
|
|
d4b7650633 | ||
|
|
312221ef73 | ||
|
|
9774d46d8b | ||
|
|
b3699eda5f | ||
|
|
527c13dec6 | ||
|
|
e89b102ea3 | ||
|
|
0933439503 | ||
|
|
09d5b2f584 | ||
|
|
4abbc4cbc7 | ||
|
|
d84d66005a | ||
|
|
e4c7c0f103 | ||
|
|
278289a0c7 | ||
|
|
07329555c0 | ||
|
|
49cfb8f425 | ||
|
|
9aeda7160b | ||
|
|
51e5d35e67 | ||
|
|
522cbbc9f9 | ||
|
|
82ef34c085 | ||
|
|
e312ece25f | ||
|
|
fffac0f905 | ||
|
|
01e4f7f8cd | ||
|
|
6f24dac816 | ||
|
|
f3c3c65374 | ||
|
|
035d9a56d7 | ||
|
|
6966779f3c | ||
|
|
bed0470b73 | ||
|
|
43b55d8e56 | ||
|
|
bdf7a1681c | ||
|
|
592a99e669 | ||
|
|
3269ee2266 | ||
|
|
b0fa3ddd56 | ||
|
|
4c2317ddd4 | ||
|
|
f866af5ee8 | ||
|
|
f3f691431b | ||
|
|
38ba665ef1 | ||
|
|
6929ca1963 | ||
|
|
23bde6b716 | ||
|
|
0b2df61c2e | ||
|
|
d30b331b3c | ||
|
|
1fa026f0ee | ||
|
|
3a39c65829 | ||
|
|
b174a81562 | ||
|
|
077421d59c | ||
|
|
fcf999ff61 | ||
|
|
3e2f798ccf | ||
|
|
69faecf339 | ||
|
|
aa3a6854ae | ||
|
|
bb6edfff98 | ||
|
|
f7b495d22f | ||
|
|
eaf63f307c | ||
|
|
905f778204 | ||
|
|
0b15711b23 | ||
|
|
a51b193f4b | ||
|
|
42a7938d38 | ||
|
|
5783a95db3 | ||
|
|
785afe7884 | ||
|
|
28fea2e5dc | ||
|
|
dcc59fde35 | ||
|
|
4e9ac8b3a2 | ||
|
|
5d5b74613b | ||
|
|
44c149e8d5 | ||
|
|
c262525341 | ||
|
|
7da24df76f | ||
|
|
2664307517 | ||
|
|
8c0f0f86b6 | ||
|
|
b651f53e8a | ||
|
|
10fad29923 | ||
|
|
d9962a89a7 | ||
|
|
60b189d101 | ||
|
|
6b065815b7 | ||
|
|
8c943b43f0 | ||
|
|
8b813115a9 | ||
|
|
4a6bb0ba87 | ||
|
|
a2daf0f493 | ||
|
|
0e3218c7be | ||
|
|
d98c5e19fc | ||
|
|
e086099d6c | ||
|
|
df3bec4d6c | ||
|
|
4b946175bf | ||
|
|
0e891f64a2 | ||
|
|
47cc38d89e | ||
|
|
d2f9060b5c | ||
|
|
c36cc504eb | ||
|
|
8d3b3a8493 | ||
|
|
e7955ca7bf | ||
|
|
016cd09849 | ||
|
|
23846d6100 | ||
|
|
0b247b709e | ||
|
|
bfee53a9b1 | ||
|
|
70a3c98aca | ||
|
|
a923124108 | ||
|
|
92484c0333 | ||
|
|
eb21452a83 | ||
|
|
433fe963e2 | ||
|
|
7a2390ddf7 | ||
|
|
1c6a14d17a | ||
|
|
0ba64f7979 | ||
|
|
38720e96a9 | ||
|
|
0f7d9b2adc | ||
|
|
210fc246ac | ||
|
|
df0d037f66 | ||
|
|
07ea309a47 | ||
|
|
1f09f53a88 | ||
|
|
f356921daa | ||
|
|
3d50ef03f7 | ||
|
|
7b309eb2d2 | ||
|
|
b22b0642ed | ||
|
|
1cbc9195c4 | ||
|
|
b853dd0b6e | ||
|
|
f7df60419c | ||
|
|
9f76cae55e | ||
|
|
aca5a9ff4c | ||
|
|
a6ce86d702 | ||
|
|
4392c89ec7 | ||
|
|
d74a31ba7f | ||
|
|
cb3195900f | ||
|
|
cf4b91f44d | ||
|
|
33ecf8d5f5 | ||
|
|
39a2293a45 | ||
|
|
88c93995df | ||
|
|
6afe5ff41f | ||
|
|
a3a8863f46 | ||
|
|
fcfee5116b | ||
|
|
3f2d91014b | ||
|
|
41149a83b3 | ||
|
|
1ed882e8f3 | ||
|
|
0f6e0de29c | ||
|
|
238bc532c3 | ||
|
|
6919848ab3 | ||
|
|
86aec88de4 | ||
|
|
f609d57a0c | ||
|
|
f3852a3c24 | ||
|
|
804ff6a81c | ||
|
|
7869f90edd | ||
|
|
2b72306b3d | ||
|
|
f0d5d4b93f | ||
|
|
4e4ab80b2f | ||
|
|
c33d08afda | ||
|
|
a246ac38f5 | ||
|
|
7bdaa81dee | ||
|
|
6a1d831849 | ||
|
|
95d2d1dfa3 | ||
|
|
d12dd179c2 | ||
|
|
ceda5eb8ee | ||
|
|
1301aaac76 | ||
|
|
5f7468a9a4 | ||
|
|
aa24c888a3 | ||
|
|
c792d9b6ea | ||
|
|
a921b95404 | ||
|
|
e46df069a9 | ||
|
|
c08f4f24ca | ||
|
|
67b3937824 | ||
|
|
17e1623342 | ||
|
|
d12fbf05b0 | ||
|
|
efa2d44e76 | ||
|
|
acdb46cea0 | ||
|
|
c1807516f5 | ||
|
|
ab796dff93 | ||
|
|
2d98f909de |
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@@ -9,6 +9,8 @@ jobs:
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
GOOGLE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }}
|
||||
# to save corepack from itself
|
||||
COREPACK_INTEGRITY_KEYS: 0
|
||||
name: Check & Publish
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
27
.github/workflows/docker.yml
vendored
27
.github/workflows/docker.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Create Docker images on tag
|
||||
name: Create Docker images on Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -11,6 +11,10 @@ on:
|
||||
options:
|
||||
- "true"
|
||||
- "false"
|
||||
release-tag:
|
||||
description: 'Kestra Release Tag'
|
||||
required: false
|
||||
type: string
|
||||
plugin-version:
|
||||
description: 'Plugin version'
|
||||
required: false
|
||||
@@ -38,7 +42,6 @@ jobs:
|
||||
name: Publish Docker
|
||||
needs: [ plugins ]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
strategy:
|
||||
matrix:
|
||||
image:
|
||||
@@ -57,10 +60,19 @@ jobs:
|
||||
- name: Set image name
|
||||
id: vars
|
||||
run: |
|
||||
TAG=${GITHUB_REF#refs/*/}
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "plugins=${{ matrix.image.plugins }}" >> $GITHUB_OUTPUT
|
||||
if [[ "${{ inputs.release-tag }}" == "" ]]; then
|
||||
TAG=${GITHUB_REF#refs/*/}
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
TAG="${{ inputs.release-tag }}"
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ "${{ env.PLUGIN_VERSION }}" == *"-SNAPSHOT" ]]; then
|
||||
echo "plugins=--repositories=https://s01.oss.sonatype.org/content/repositories/snapshots ${{ matrix.image.plugins }}" >> $GITHUB_OUTPUT;
|
||||
else
|
||||
echo "plugins=${{ matrix.image.plugins }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
# Download release
|
||||
- name: Download release
|
||||
uses: robinraju/release-downloader@v1.11
|
||||
@@ -77,6 +89,11 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Docker - Fix Qemu
|
||||
shell: bash
|
||||
run: |
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
releaseVersion:
|
||||
description: 'The release version (e.g., 0.21.0)'
|
||||
description: 'The release version (e.g., 0.21.0-rc1)'
|
||||
required: true
|
||||
type: string
|
||||
nextVersion:
|
||||
@@ -18,13 +18,29 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
name: Release plugins
|
||||
runs-on: kestra-private-standard
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
id: build
|
||||
with:
|
||||
java-enabled: true
|
||||
node-enabled: true
|
||||
python-enabled: true
|
||||
caches-enabled: true
|
||||
|
||||
# Get Plugins List
|
||||
- name: Get Plugins List
|
||||
uses: ./.github/actions/plugins-list
|
||||
@@ -33,14 +49,20 @@ jobs:
|
||||
with:
|
||||
plugin-version: 'LATEST'
|
||||
|
||||
- name: 'Configure Git'
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
# Execute
|
||||
- name: Run Gradle Release
|
||||
if: ${{ github.event.inputs.dryRun == 'false' }}
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
chmod +x ./release-plugins.sh;
|
||||
./release-plugins.sh \
|
||||
chmod +x ./dev-tools/release-plugins.sh;
|
||||
|
||||
./dev-tools/release-plugins.sh \
|
||||
--release-version=${{github.event.inputs.releaseVersion}} \
|
||||
--next-version=${{github.event.inputs.nextVersion}} \
|
||||
--yes \
|
||||
@@ -51,8 +73,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
chmod +x ./release-plugins.sh;
|
||||
./release-plugins.sh \
|
||||
chmod +x ./dev-tools/release-plugins.sh;
|
||||
|
||||
./dev-tools/release-plugins.sh \
|
||||
--release-version=${{github.event.inputs.releaseVersion}} \
|
||||
--next-version=${{github.event.inputs.nextVersion}} \
|
||||
--dry-run \
|
||||
89
.github/workflows/gradle-release.yml
vendored
Normal file
89
.github/workflows/gradle-release.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: Run Gradle Release
|
||||
run-name: "Releasing Kestra ${{ github.event.inputs.releaseVersion }} 🚀"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
releaseVersion:
|
||||
description: 'The release version (e.g., 0.21.0-rc1)'
|
||||
required: true
|
||||
type: string
|
||||
nextVersion:
|
||||
description: 'The next version (e.g., 0.22.0-SNAPSHOT)'
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
RELEASE_VERSION: "${{ github.event.inputs.releaseVersion }}"
|
||||
NEXT_VERSION: "${{ github.event.inputs.nextVersion }}"
|
||||
jobs:
|
||||
release:
|
||||
name: Release Kestra
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
steps:
|
||||
# Checks
|
||||
- name: Check Inputs
|
||||
run: |
|
||||
if ! [[ "$RELEASE_VERSION" =~ ^[0-9]+(\.[0-9]+)\.0-rc[01](-SNAPSHOT)?$ ]]; then
|
||||
echo "Invalid release version. Must match regex: ^[0-9]+(\.[0-9]+)\.0-rc[01](-SNAPSHOT)?$"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$NEXT_VERSION" =~ ^[0-9]+(\.[0-9]+)\.0-SNAPSHOT$ ]]; then
|
||||
echo "Invalid next version. Must match regex: ^[0-9]+(\.[0-9]+)\.0-SNAPSHOT$"
|
||||
exit 1;
|
||||
fi
|
||||
# Checkout
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
id: build
|
||||
with:
|
||||
java-enabled: true
|
||||
node-enabled: true
|
||||
python-enabled: true
|
||||
caches-enabled: true
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
# Execute
|
||||
- name: Run Gradle Release
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
# Extract the major and minor versions
|
||||
BASE_VERSION=$(echo "$RELEASE_VERSION" | sed -E 's/^([0-9]+\.[0-9]+)\..*/\1/')
|
||||
PUSH_RELEASE_BRANCH="releases/v${BASE_VERSION}.x"
|
||||
|
||||
# Create and push release branch
|
||||
git checkout -b "$PUSH_RELEASE_BRANCH";
|
||||
git push -u origin "$PUSH_RELEASE_BRANCH";
|
||||
|
||||
# Run gradle release
|
||||
git checkout develop;
|
||||
|
||||
if [[ "$RELEASE_VERSION" == *"-SNAPSHOT" ]]; then
|
||||
# -SNAPSHOT qualifier maybe used to test release-candidates
|
||||
./gradlew release -Prelease.useAutomaticVersion=true \
|
||||
-Prelease.releaseVersion="${RELEASE_VERSION}" \
|
||||
-Prelease.newVersion="${NEXT_VERSION}" \
|
||||
-Prelease.pushReleaseVersionBranch="${PUSH_RELEASE_BRANCH}" \
|
||||
-Prelease.failOnSnapshotDependencies=false
|
||||
else
|
||||
./gradlew release -Prelease.useAutomaticVersion=true \
|
||||
-Prelease.releaseVersion="${RELEASE_VERSION}" \
|
||||
-Prelease.newVersion="${NEXT_VERSION}" \
|
||||
-Prelease.pushReleaseVersionBranch="${PUSH_RELEASE_BRANCH}"
|
||||
fi
|
||||
21
.github/workflows/main.yml
vendored
21
.github/workflows/main.yml
vendored
@@ -35,6 +35,8 @@ env:
|
||||
DOCKER_APT_PACKAGES: python3 python3-venv python-is-python3 python3-pip nodejs npm curl zip unzip
|
||||
DOCKER_PYTHON_LIBRARIES: kestra
|
||||
PLUGIN_VERSION: ${{ github.event.inputs.plugin-version != null && github.event.inputs.plugin-version || 'LATEST' }}
|
||||
# to save corepack from itself
|
||||
COREPACK_INTEGRITY_KEYS: 0
|
||||
jobs:
|
||||
build-artifacts:
|
||||
name: Build Artifacts
|
||||
@@ -45,13 +47,14 @@ jobs:
|
||||
docker-artifact-name: ${{ steps.vars.outputs.artifact }}
|
||||
plugins: ${{ steps.plugins-list.outputs.plugins }}
|
||||
steps:
|
||||
# Checkout
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout current ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout GitHub Actions
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
@@ -125,6 +128,11 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Docker - Fix Qemu
|
||||
shell: bash
|
||||
run: |
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -403,6 +411,11 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Docker - Fix Qemu
|
||||
shell: bash
|
||||
run: |
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Update and Tag Kestra Plugins
|
||||
name: Set Version and Tag Plugins
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
tag:
|
||||
name: Release plugins
|
||||
runs-on: kestra-private-standard
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout
|
||||
- uses: actions/checkout@v4
|
||||
@@ -29,25 +29,32 @@ jobs:
|
||||
with:
|
||||
plugin-version: 'LATEST'
|
||||
|
||||
- name: 'Configure Git'
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
# Execute
|
||||
- name: Tag Plugins
|
||||
- name: Set Version and Tag Plugins
|
||||
if: ${{ github.event.inputs.dryRun == 'false' }}
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
chmod +x ./tag-release-plugins.sh;
|
||||
./tag-release-plugins.sh \
|
||||
chmod +x ./dev-tools/setversion-tag-plugins.sh;
|
||||
|
||||
./dev-tools/setversion-tag-plugins.sh \
|
||||
--release-version=${{github.event.inputs.releaseVersion}} \
|
||||
--yes \
|
||||
${{ steps.plugins-list.outputs.repositories }}
|
||||
|
||||
- name: Run Gradle Release (DRY_RUN)
|
||||
- name: Set Version and Tag Plugins (DRY_RUN)
|
||||
if: ${{ github.event.inputs.dryRun == 'true' }}
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
chmod +x ./tag-release-plugins.sh;
|
||||
./tag-release-plugins.sh \
|
||||
chmod +x ./dev-tools/setversion-tag-plugins.sh;
|
||||
|
||||
./dev-tools/setversion-tag-plugins.sh \
|
||||
--release-version=${{github.event.inputs.releaseVersion}} \
|
||||
--dry-run \
|
||||
--yes \
|
||||
58
.github/workflows/setversion-tag.yml
vendored
Normal file
58
.github/workflows/setversion-tag.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Set Version and Tag
|
||||
run-name: "Set version and Tag Kestra to ${{ github.event.inputs.releaseVersion }} 🚀"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
releaseVersion:
|
||||
description: 'The release version (e.g., 0.21.1)'
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
RELEASE_VERSION: "${{ github.event.inputs.releaseVersion }}"
|
||||
jobs:
|
||||
release:
|
||||
name: Release Kestra
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/heads/releases/v')
|
||||
steps:
|
||||
# Checks
|
||||
- name: Check Inputs
|
||||
run: |
|
||||
if ! [[ "$RELEASE_VERSION" =~ ^[0-9]+(\.[0-9]+)(\.[0-9]+)(-rc[0-9])?(-SNAPSHOT)?$ ]]; then
|
||||
echo "Invalid release version. Must match regex: ^[0-9]+(\.[0-9]+)(\.[0-9]+)-(rc[0-9])?(-SNAPSHOT)?$"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_BRANCH="{{ github.ref }}"
|
||||
|
||||
# Extract the major and minor versions
|
||||
BASE_VERSION=$(echo "$RELEASE_VERSION" | sed -E 's/^([0-9]+\.[0-9]+)\..*/\1/')
|
||||
RELEASE_BRANCH="refs/heads/releases/v${BASE_VERSION}.x"
|
||||
|
||||
if ! [[ "$CURRENT_BRANCH" == "$RELEASE_BRANCH" ]]; then
|
||||
echo "Invalid release branch. Expected $RELEASE_BRANCH, was $CURRENT_BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Checkout
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
# Execute
|
||||
- name: Run Gradle Release
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
# Update version
|
||||
sed -i "s/^version=.*/version=$RELEASE_VERSION/" ./gradle.properties
|
||||
git add ./gradle.properties
|
||||
git commit -m"chore(version): update to version '$RELEASE_VERSION'"
|
||||
git push
|
||||
git tag -a "v$RELEASE_VERSION" -m"v$RELEASE_VERSION"
|
||||
git push origin "v$RELEASE_VERSION"
|
||||
1
.plugins
1
.plugins
@@ -40,6 +40,7 @@
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-db2:LATEST
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-duckdb:LATEST
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-druid:LATEST
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-mariadb:LATEST
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-mysql:LATEST
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-oracle:LATEST
|
||||
#plugin-jdbc:io.kestra.plugin:plugin-jdbc-pinot:LATEST
|
||||
|
||||
982
CHANGELOG.md
Normal file
982
CHANGELOG.md
Normal file
@@ -0,0 +1,982 @@
|
||||
# 0.21.6
|
||||
|
||||
**Full Changelog**: [v0.21.5...v0.21.6](https://github.com/kestra-io/kestra/compare/v0.21.5...v0.21.6)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **core:** Allow reading file from any namespaces ([4bb2de219](https://github.com/kestra-io/kestra/commit/4bb2de219))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **core:** Avoid duplicates in plugins subgroups + properly retrieve subgroup title ([33ac1ac53](https://github.com/kestra-io/kestra/commit/33ac1ac53))
|
||||
- align to EE ([YannC](https://github.com/kestra-io/kestra/commit/3d29077a99c209a1f30c369618c29ad52c46a5a6))
|
||||
- **runner-memory:** Delete MemorySchedulerTriggerState back due to cherry-pick ([27c876262](https://github.com/kestra-io/kestra/commit/27c876262))
|
||||
- **core:** Possible NPE when an execution has no labels ([67e415406](https://github.com/kestra-io/kestra/commit/67e415406))
|
||||
- **jdbc:** Resubmit worker job to the good worker group ([49a8d13b8](https://github.com/kestra-io/kestra/commit/49a8d13b8))
|
||||
- **core:** #7740 http configuration bearer token may change to basic because of allowFailed ([#7788](https://github.com/kestra-io/kestra/pull/7788), [#7740](https://github.com/kestra-io/kestra/issues/7740))
|
||||
- **core:** Use AbstractFileFunction in file functions ([3f7c385ab](https://github.com/kestra-io/kestra/commit/3f7c385ab))
|
||||
- **cli:** Fix binding for plugin repository config ([368f4f22d](https://github.com/kestra-io/kestra/commit/368f4f22d))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update the CHANGELOG.MD with details for 0.21.5 ([eec8fb9fb](https://github.com/kestra-io/kestra/commit/eec8fb9fb))
|
||||
- **version:** Update to version 'v0.21.6' ([2aeb11e2d](https://github.com/kestra-io/kestra/commit/2aeb11e2d))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Brian.mulier ([@brian-mulier-p](https://github.com/brian-mulier-p))
|
||||
- Florian Hussonnois ([@fhussonnois](https://github.com/fhussonnois))
|
||||
- Nicolas K. <nk_mikmak@hotmail.com>
|
||||
- Loïc Mathieu ([@loicmathieu](https://github.com/loicmathieu))
|
||||
- YannC <ycoornaert@kestra.io>
|
||||
- MilosPaunovic ([@MilosPaunovic](https://github.com/MilosPaunovic))
|
||||
|
||||
|
||||
|
||||
# 0.21.5
|
||||
|
||||
**Full Changelog**: [v0.21.4...v0.21.5](https://github.com/kestra-io/kestra/compare/v0.21.4...v0.21.5)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **core:** Add default options for HttpClient ([#7650](https://github.com/kestra-io/kestra/pull/7650))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **ui:** Plugins TOC is now handling every type of plugins ([278289a0c](https://github.com/kestra-io/kestra/commit/278289a0c))
|
||||
- **ui:** Styling enhancements for plugin doc ([e4c7c0f10](https://github.com/kestra-io/kestra/commit/e4c7c0f10))
|
||||
- **core:** Render list ([d84d66005](https://github.com/kestra-io/kestra/commit/d84d66005))
|
||||
- **core:** Properly render list properties ([4abbc4cbc](https://github.com/kestra-io/kestra/commit/4abbc4cbc))
|
||||
- **ui:** Replace alert blocks upon markdown rendering to display them properly ([9774d46d8](https://github.com/kestra-io/kestra/commit/9774d46d8))
|
||||
- **core:** Validation error when timeWindow.type is null ([c004ba1a6](https://github.com/kestra-io/kestra/commit/c004ba1a6))
|
||||
- **core:** MultipleCondition documentation ([1dc8401a0](https://github.com/kestra-io/kestra/commit/1dc8401a0))
|
||||
- make subflow not found a warning instead of an error (#7649) ([YannC](https://github.com/kestra-io/kestra/commit/d4b76506330553a43832026f70dec6391b0236c3))
|
||||
- recoverMissedSchedules behavior on long running executions (#7617) ([YannC](https://github.com/kestra-io/kestra/commit/cfe4e2a3c21f7d953458a32d5f9a663e977e1b40))
|
||||
- avoid crash on injectDefault ([YannC](https://github.com/kestra-io/kestra/commit/49977f505f7ba45fd7e6e11019721de3ea9151b1))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **ui:** Remove obsolete `chartjs-chart-treemap` library ([#7529](https://github.com/kestra-io/kestra/pull/7529))
|
||||
|
||||
### 📦 Build
|
||||
|
||||
- **deps:** Bump io.micronaut.platform:micronaut-platform ([527c13dec](https://github.com/kestra-io/kestra/commit/527c13dec))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update the CHANGELOG.MD with details for 0.21.4 ([07329555c](https://github.com/kestra-io/kestra/commit/07329555c))
|
||||
- **ui:** Amend item labels in left menu ([#7590](https://github.com/kestra-io/kestra/pull/7590))
|
||||
- **ui:** Make sure chart stacks are following the same order every time ([#7664](https://github.com/kestra-io/kestra/pull/7664))
|
||||
- **deps**: upgrade Micronaut core to 4.7.15 ([Loïc Mathieu](https://github.com/kestra-io/kestra/commit/b3699eda5f9293887f58f0304bb42dcb951dcef4))
|
||||
- **deps**: bump ui-libs to 0.0.151 ([brian.mulier](https://github.com/kestra-io/kestra/commit/312221ef73eebdc7f7cd05112f56b2eecd526fac))
|
||||
- **deps**: upgrade ui-libs version to v0.0.153 ([YannC](https://github.com/kestra-io/kestra/commit/5b1d216c409f6af7cc4d676a53cda3b3cd751144))
|
||||
- **deps**: upgrade ui-libs version to v0.0.154 ([YannC](https://github.com/kestra-io/kestra/commit/ea26e4dda7e6f4a8fdb0c1ab9780525784774a52))
|
||||
- **deps**: upgrade ui-libs version to v0.0.154 ([MilosPaunovic](https://github.com/kestra-io/kestra/commit/65999abb3fef73bf50f9736d219524e47c659a08))
|
||||
- 09d5b2f: Feat/npe on runcontext cleanup (#7585) (Nicolas K.)
|
||||
- b7259cc: feat(#7636): add default options for HttpClient (#7650) (Nicolas K.)
|
||||
- **webserver**: merging ([nKwiatkowski](https://github.com/kestra-io/kestra/commit/5131c347cd1ed1cf429e46ed346e70182de57c95))
|
||||
- **version:** Update to version 'v0.21.5' ([df5d13467](https://github.com/kestra-io/kestra/commit/df5d13467))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Mathieu Gabelle <mgabelle@kestra.io>
|
||||
- NKwiatkowski <nkwiatkowski@kestra.io>
|
||||
- YannC ([@Skraye](http://github.com/Skraye))
|
||||
- Loïc Mathieu ([@loicmathieu](https://github.com/loicmathieu))
|
||||
- Brian.mulier ([@brian-mulier-p](https://github.com/brian-mulier-p))
|
||||
- MilosPaunovic ([@MilosPaunovic](https://github.com/MilosPaunovic))
|
||||
|
||||
|
||||
|
||||
# 0.21.4
|
||||
|
||||
**Full Changelog**: [v0.21.3...v0.21.4](https://github.com/kestra-io/kestra/compare/v0.21.3...v0.21.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **core:** Http proxy was not passed to configuration ([6929ca196](https://github.com/kestra-io/kestra/commit/6929ca196))
|
||||
- **ci:** QEMU issue ([38ba665ef](https://github.com/kestra-io/kestra/commit/38ba665ef))
|
||||
- **tasks:** Remove useless format metrics on return ([#7486](https://github.com/kestra-io/kestra/pull/7486))
|
||||
- **core:** Add package-info.java to dashboard package ([bed0470b7](https://github.com/kestra-io/kestra/commit/bed0470b7))
|
||||
- **core:** Handle http request with no content type ([6966779f3](https://github.com/kestra-io/kestra/commit/6966779f3))
|
||||
- **core:** Add package-info.java to script + handle subgroups properly ([035d9a56d](https://github.com/kestra-io/kestra/commit/035d9a56d))
|
||||
- **core:** Try to log message for unhandled realtime trigger exception ([f3c3c6537](https://github.com/kestra-io/kestra/commit/f3c3c6537))
|
||||
- **core:** Move package-info.java to proper dashboard packages ([6f24dac81](https://github.com/kestra-io/kestra/commit/6f24dac81))
|
||||
- **core:** Rename dashboards subgroups ([01e4f7f8c](https://github.com/kestra-io/kestra/commit/01e4f7f8c))
|
||||
- **core:** Camel to snake-case for app-blocks in RegisteredPlugin ([fffac0f90](https://github.com/kestra-io/kestra/commit/fffac0f90))
|
||||
- **core:** Missing content type on http client ([e312ece25](https://github.com/kestra-io/kestra/commit/e312ece25))
|
||||
- **core:** Content type encoding should not be mandatory ([82ef34c08](https://github.com/kestra-io/kestra/commit/82ef34c08))
|
||||
- **core:** #172 add reactor into classloader blacklist ([#172](https://github.com/kestra-io/kestra/issues/172))
|
||||
- **core:** Handle space in HTTP request URI ([51e5d35e6](https://github.com/kestra-io/kestra/commit/51e5d35e6))
|
||||
- **core:** Subflow using the old task name never ends ([9aeda7160](https://github.com/kestra-io/kestra/commit/9aeda7160))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **ui:** Move changelog file to root ([f866af5ee](https://github.com/kestra-io/kestra/commit/f866af5ee))
|
||||
- Migrate CommandsWrapper commands to dynamic properties ([bdf7a1681](https://github.com/kestra-io/kestra/commit/bdf7a1681))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **ui:** Improve links inside changelog file ([4c2317ddd](https://github.com/kestra-io/kestra/commit/4c2317ddd))
|
||||
- **ui:** Improve the labels behavior ([b0fa3ddd5](https://github.com/kestra-io/kestra/commit/b0fa3ddd5))
|
||||
- **ui:** Improve the doughnut chart legend ([#7321](https://github.com/kestra-io/kestra/pull/7321))
|
||||
- Bump version ([49cfb8f42](https://github.com/kestra-io/kestra/commit/49cfb8f42))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- NKwiatkowski <nkwiatkowski@kestra.io>
|
||||
- Loïc Mathieu ([@loicmathieu](http://github.com/loicmathieu))
|
||||
- Ludovic DEHON ([@tchiotludo](http://github.com/tchiotludo))
|
||||
- Brian.mulier ([@brian-mulier-p](http://github.com/brian-mulier-p))
|
||||
- Mathieu Gabelle ([@mgabelle](http://github.com/mgabelle))
|
||||
- Shamar ([@Shamar12334](http://github.com/Shamar12334))
|
||||
- MilosPaunovic ([@MilosPaunovic](http://github.com/MilosPaunovic))
|
||||
|
||||
|
||||
|
||||
# 0.21.3
|
||||
|
||||
**Full Changelog**: [v0.21.2...v0.21.3](https://github.com/kestra-io/kestra/compare/v0.21.2...v0.21.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **scheduler:** Delete trigger when flow is not found ([#7366](https://github.com/kestra-io/kestra/pull/7366))
|
||||
- Enable rendering of commands properties inside CommandsWrapper " ([#7381](https://github.com/kestra-io/kestra/pull/7381))
|
||||
- **ui:** Improve modifying inputs from no code editor ([#7440](https://github.com/kestra-io/kestra/pull/7440))
|
||||
- **core:** Provide tenantId when looking for subflow ([#7442](https://github.com/kestra-io/kestra/pull/7442))
|
||||
- **core:** Remove props with default from `required` in json schema to avoid validation errors ([3a39c6582](https://github.com/kestra-io/kestra/commit/3a39c6582))
|
||||
- **core:** Render `delete` property at the beginning in Docker task runner ([1fa026f0e](https://github.com/kestra-io/kestra/commit/1fa026f0e))
|
||||
- **tests:** Increase timeout on JdbcServiceLivenessCoordinatorTest.taskResubmitSkipExecution ([d30b331b3](https://github.com/kestra-io/kestra/commit/d30b331b3))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Introduce render in commands wrapper for property string ([#7430](https://github.com/kestra-io/kestra/pull/7430))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **ui:** Add link to filtered triggers page from backfill dialog ([#7380](https://github.com/kestra-io/kestra/pull/7380))
|
||||
- **ui:** Include autocompletion shortcut in the preview list ([#7384](https://github.com/kestra-io/kestra/pull/7384))
|
||||
- **ui:** Improve breadcrumbs on namespace view ([#7386](https://github.com/kestra-io/kestra/pull/7386))
|
||||
- **ui:** Improve the labels behavior ([#7397](https://github.com/kestra-io/kestra/pull/7397))
|
||||
- Update version to v0.21.3 ([23bde6b71](https://github.com/kestra-io/kestra/commit/23bde6b71))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- **publish-docker:** Usage of qemu-user-static ([0b2df61c2](https://github.com/kestra-io/kestra/commit/0b2df61c2))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Brian.mulier ([@brian-mulier-p](http://github.com/brian-mulier-p))
|
||||
- YannC ([@Skraye](http://github.com/Skraye))
|
||||
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
|
||||
- Mathieu Gabelle ([@mgabelle](http://github.com/mgabelle))
|
||||
|
||||
|
||||
|
||||
# 0.21.2
|
||||
|
||||
**Full Changelog**: [v0.21.1...v0.21.2](https://github.com/kestra-io/kestra/compare/v0.21.1...v0.21.2)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Theme switch to "theme switch" the charts ([#7151](https://github.com/kestra-io/kestra/pull/7151))
|
||||
- **ci:** Add workflows for release process ([10fad2992](https://github.com/kestra-io/kestra/commit/10fad2992))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **ui:** Prevent doubling the executions chart on flow overview ([e086099d6](https://github.com/kestra-io/kestra/commit/e086099d6))
|
||||
- **ui:** Amend bar chart colors on the main dashboard ([0e3218c7b](https://github.com/kestra-io/kestra/commit/0e3218c7b))
|
||||
- **core:** Possible NPE on LabelService.containsAll ([8c943b43f](https://github.com/kestra-io/kestra/commit/8c943b43f))
|
||||
- **ui:** Amend the language switching issue ([#7235](https://github.com/kestra-io/kestra/pull/7235))
|
||||
- Add comment on i18n code ([60b189d10](https://github.com/kestra-io/kestra/commit/60b189d10))
|
||||
- Labels should not be purple if inactive ([d9962a89a](https://github.com/kestra-io/kestra/commit/d9962a89a))
|
||||
- **ci:** Fix and remove unecessary setps in set version workflows ([b651f53e8](https://github.com/kestra-io/kestra/commit/b651f53e8))
|
||||
- Use the udpated labelsFromQuery in labels ([c26252534](https://github.com/kestra-io/kestra/commit/c26252534))
|
||||
- **core:** Make flow/namespace variables available for input expr ([44c149e8d](https://github.com/kestra-io/kestra/commit/44c149e8d))
|
||||
- **core:** Http client was not using deprecated setter ([5d5b74613](https://github.com/kestra-io/kestra/commit/5d5b74613))
|
||||
- **core:** Do not validate subflow if namespace or id is pebble ([#7294](https://github.com/kestra-io/kestra/pull/7294))
|
||||
- **h2:** Remove indenting in sql file ([#7306](https://github.com/kestra-io/kestra/pull/7306))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **ui:** Re-order the list of optional columns ([#7213](https://github.com/kestra-io/kestra/pull/7213))
|
||||
- Remove console.log ([df3bec4d6](https://github.com/kestra-io/kestra/commit/df3bec4d6))
|
||||
- **ui:** Amended global pagination coloring ([#7201](https://github.com/kestra-io/kestra/pull/7201))
|
||||
- **ui:** Generate random flow ID using combination of animal names and numbers ([#7223](https://github.com/kestra-io/kestra/pull/7223))
|
||||
- **ui:** Only show warning on bulk execution deletion if nonTerminated is true ([#7211](https://github.com/kestra-io/kestra/pull/7211))
|
||||
- **ui:** Align chart duration label with switch toggle ([#7259](https://github.com/kestra-io/kestra/pull/7259))
|
||||
- **ui:** Disable saving flow actions if there are errors ([#7278](https://github.com/kestra-io/kestra/pull/7278))
|
||||
- **ui:** Make action columns always visible on executions and flows ([#7291](https://github.com/kestra-io/kestra/pull/7291))
|
||||
- **ui:** Remove the option to change editor theme separately ([#7192](https://github.com/kestra-io/kestra/pull/7192))
|
||||
- Update version to v0.21.2 ([da6d0f57b](https://github.com/kestra-io/kestra/commit/da6d0f57b))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- YannC ([@Skraye](http://github.com/Skraye))
|
||||
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
|
||||
- Ludovic DEHON ([@tchiotludo](http://github.com/tchiotludo))
|
||||
- Florian Hussonnois ([@fhussonnois](http://github.com/fhussonnois))
|
||||
- Bart Ledoux <bledoux@kestra.io>
|
||||
- Aabhas Sao ([@aabhas-sao](http://github.com/aabhas-sao))
|
||||
- 咬轮猫 <10928033@qq.com>
|
||||
- Loïc Mathieu ([@loicmathieu](http://github.com/loicmathieu))
|
||||
- Piyush Bhaskar ([@Piyush-r-bhaskar](http://github.com/Piyush-r-bhaskar))
|
||||
- Pravesh-Sudha ([@Pravesh-Sudha](http://github.com/Pravesh-Sudha))
|
||||
|
||||
|
||||
|
||||
# 0.21.1
|
||||
|
||||
**Full Changelog**: [v0.21.0...v0.21.1](https://github.com/kestra-io/kestra/compare/v0.21.0...v0.21.1)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Show a lock on EE only pages ([#7093](https://github.com/kestra-io/kestra/pull/7093))
|
||||
- **ui:** Add keyboard shortcuts dialog to editor ([#6628](https://github.com/kestra-io/kestra/pull/6628))
|
||||
- **ui:** Add option to choose visible columns in flow and execution listings ([#6932](https://github.com/kestra-io/kestra/pull/6932))
|
||||
- **ui:** Docs markdown alert styled based on alert level in product. ([#6818](https://github.com/kestra-io/kestra/pull/6818))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **ui:** Global plugin doc with new redesign + auto-expand properties initially ([9f76cae55](https://github.com/kestra-io/kestra/commit/9f76cae55))
|
||||
- Avoid clearing selected value on every error ([f7df60419](https://github.com/kestra-io/kestra/commit/f7df60419))
|
||||
- Use the proper variable for select header in table ([#7107](https://github.com/kestra-io/kestra/pull/7107))
|
||||
- Make table links primary instead of purple ([#7106](https://github.com/kestra-io/kestra/pull/7106))
|
||||
- **ui:** Amend pagination on namespace flows listing ([#7163](https://github.com/kestra-io/kestra/pull/7163))
|
||||
- **ui:** Amend translation string for no results ([#7172](https://github.com/kestra-io/kestra/pull/7172))
|
||||
- **ui:** Align dashboard button label to icon ([#7175](https://github.com/kestra-io/kestra/pull/7175))
|
||||
- **core:** #7181 log level rendered as string ([#7198](https://github.com/kestra-io/kestra/pull/7198), [#7181](https://github.com/kestra-io/kestra/issues/7181))
|
||||
- Remove editor theme from english ([8d3b3a849](https://github.com/kestra-io/kestra/commit/8d3b3a849))
|
||||
- **core:** Request option doesn't initialize properly ([47cc38d89](https://github.com/kestra-io/kestra/commit/47cc38d89))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **ui:** Show each plugin deprecation warning in new line ([#6839](https://github.com/kestra-io/kestra/pull/6839))
|
||||
- **translations:** Auto generate values for languages other than english ([1f09f53a8](https://github.com/kestra-io/kestra/commit/1f09f53a8))
|
||||
- **ui:** Amend color of the input length counter ([#6990](https://github.com/kestra-io/kestra/pull/6990))
|
||||
- **ui:** Enable command palette for monaco editor ([#6944](https://github.com/kestra-io/kestra/pull/6944))
|
||||
- **translations:** Auto generate values for languages other than english ([210fc246a](https://github.com/kestra-io/kestra/commit/210fc246a))
|
||||
- **translations:** Auto generate values for languages other than english ([38720e96a](https://github.com/kestra-io/kestra/commit/38720e96a))
|
||||
- **ui:** Improve the example for not condition ([#6820](https://github.com/kestra-io/kestra/pull/6820))
|
||||
- **ui:** Update the visual of no data component ([#7179](https://github.com/kestra-io/kestra/pull/7179))
|
||||
- **ui:** Improve the states options list inside filter values ([#7176](https://github.com/kestra-io/kestra/pull/7176))
|
||||
- **translations:** Auto generate values for languages other than english ([a92312410](https://github.com/kestra-io/kestra/commit/a92312410))
|
||||
- **ui:** Rename advanced properties to other in no code ([#7189](https://github.com/kestra-io/kestra/pull/7189))
|
||||
- **translations:** Auto generate values for languages other than english ([bfee53a9b](https://github.com/kestra-io/kestra/commit/bfee53a9b))
|
||||
- **ui:** Rename advanced properties to other in no code ([#7190](https://github.com/kestra-io/kestra/pull/7190))
|
||||
- **translations:** Auto generate values for languages other than english ([23846d610](https://github.com/kestra-io/kestra/commit/23846d610))
|
||||
- **translations:** Remove extra keys from translation files ([#7193](https://github.com/kestra-io/kestra/pull/7193))
|
||||
- **ui:** Add the missing chart component ([c36cc504e](https://github.com/kestra-io/kestra/commit/c36cc504e))
|
||||
- **ui:** Replace the visual for no tabs opened on namespace editor ([#7204](https://github.com/kestra-io/kestra/pull/7204))
|
||||
- **version:** Update to version 'v0.21.1' ([0e891f64a](https://github.com/kestra-io/kestra/commit/0e891f64a))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Florian Hussonnois ([@fhussonnois](http://github.com/fhussonnois))
|
||||
- NKwiatkowski <nkwiatkowski@kestra.io>
|
||||
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
|
||||
- Bart Ledoux <bledoux@kestra.io>
|
||||
- Nicolas K. <nk_mikmak@hotmail.com>
|
||||
- GitHub Action ([@Github-Action-Bot](http://github.com/Github-Action-Bot))
|
||||
- Piyush Bhaskar ([@Piyush-r-bhaskar](http://github.com/Piyush-r-bhaskar))
|
||||
- Shruti Mantri <shruti1810@gmail.com>
|
||||
- Aabhas Sao ([@aabhas-sao](http://github.com/aabhas-sao))
|
||||
- Rajatsingh23 ([@rajatsingh23](http://github.com/rajatsingh23))
|
||||
- Brian.mulier ([@brian-mulier-p](http://github.com/brian-mulier-p))
|
||||
|
||||
|
||||
|
||||
# 0.21.0
|
||||
|
||||
**Full Changelog**: [v0.20.0...v0.21.0](https://github.com/kestra-io/kestra/compare/v0.20.0...v0.21.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **ui:** Add right click menu on file tree view in editor ([#5936](https://github.com/kestra-io/kestra/pull/5936))
|
||||
- **core:** Add displayName to flow level outputs(backend) " ([#5605](https://github.com/kestra-io/kestra/pull/5605))
|
||||
- **ui:** Add triggers sorting by next execution date ([#6318](https://github.com/kestra-io/kestra/pull/6318))
|
||||
- **core,jdbc:** Small trigger / scheduler improvements ([a27bb1a85](https://github.com/kestra-io/kestra/commit/a27bb1a85))
|
||||
- **ui:** Add flow validation to FlowCreate component ([#6370](https://github.com/kestra-io/kestra/pull/6370))
|
||||
- **plugins:** Add typesense plugin ([7979809ae](https://github.com/kestra-io/kestra/commit/7979809ae))
|
||||
- **core:** Log at ERROR level for script logs to stderr ([0e0928d51](https://github.com/kestra-io/kestra/commit/0e0928d51))
|
||||
- **ui:** Make filters expand fully if we omit some of the non required buttons ([#6364](https://github.com/kestra-io/kestra/pull/6364))
|
||||
- **ui:** Improvement in filter for adding clear all filters. ([#6359](https://github.com/kestra-io/kestra/pull/6359))
|
||||
- **ui:** Improve Gantt page. ([#6358](https://github.com/kestra-io/kestra/pull/6358))
|
||||
- **ui:** Triggers: The expanded button displays an empty area. ([#6337](https://github.com/kestra-io/kestra/pull/6337))
|
||||
- **ui:** Add missing filter options for metrics ([#6409](https://github.com/kestra-io/kestra/pull/6409))
|
||||
- **UI:** Added new filters to Flows -> Metrics tab ([#6305](https://github.com/kestra-io/kestra/pull/6305))
|
||||
- **ui:** Add new filters to Administration -> Triggers page ([#6328](https://github.com/kestra-io/kestra/pull/6328))
|
||||
- **ui:** Introduce new filters bar to audit logs ([#6419](https://github.com/kestra-io/kestra/pull/6419))
|
||||
- **main-gha-workflow:** Add dispatch event with new version ([#6443](https://github.com/kestra-io/kestra/pull/6443))
|
||||
- **tests:** Move the extension into the right folder ([008c03ce2](https://github.com/kestra-io/kestra/commit/008c03ce2))
|
||||
- **webserver:** Small improvements to our OpenAPI spec ([6a34c04e3](https://github.com/kestra-io/kestra/commit/6a34c04e3))
|
||||
- **core:** Use the Sleep official task instead of a custom test one ([aa8ad1520](https://github.com/kestra-io/kestra/commit/aa8ad1520))
|
||||
- **script:** Include task null outputs ([b133210fa](https://github.com/kestra-io/kestra/commit/b133210fa))
|
||||
- **core:** Throw an error if the secret is not found ([da1bbb51a](https://github.com/kestra-io/kestra/commit/da1bbb51a))
|
||||
- **core:** Allow SELECT input to be radio UI type ([18a2b553c](https://github.com/kestra-io/kestra/commit/18a2b553c))
|
||||
- Add Huggingface Plugin ([2290b9933](https://github.com/kestra-io/kestra/commit/2290b9933))
|
||||
- **ci:** Parallelize frontend testing workflow with Storybook, vitest and linter ([#6529](https://github.com/kestra-io/kestra/pull/6529))
|
||||
- **ui:** Implement initial stories for filter components ([#6542](https://github.com/kestra-io/kestra/pull/6542))
|
||||
- **jdbc:** Small improvement to the worker trigger queue ([3559b640e](https://github.com/kestra-io/kestra/commit/3559b640e))
|
||||
- **core:** Add audit log when killing an execution ([53af6a38b](https://github.com/kestra-io/kestra/commit/53af6a38b))
|
||||
- **core:** Add a Write task ([a27ef2ace](https://github.com/kestra-io/kestra/commit/a27ef2ace))
|
||||
- **core:** Added a Pebble function [uniq] to deduplice array #6417 ([#6417](https://github.com/kestra-io/kestra/issues/6417))
|
||||
- **core:** Add an Exit task ([ff83e25c0](https://github.com/kestra-io/kestra/commit/ff83e25c0))
|
||||
- **ui:** Introduce log font size property in settings ([#6600](https://github.com/kestra-io/kestra/pull/6600))
|
||||
- **webserver:** OpenAPI spec improvement ([6db49aebf](https://github.com/kestra-io/kestra/commit/6db49aebf))
|
||||
- **tasks:** Move http task to apache http client ([93dc50888](https://github.com/kestra-io/kestra/commit/93dc50888))
|
||||
- **core:** Add a http client abstraction on top of apache http client ([1772ed45f](https://github.com/kestra-io/kestra/commit/1772ed45f))
|
||||
- **core:** Use http abstraction on http tasks ([6dfe29adf](https://github.com/kestra-io/kestra/commit/6dfe29adf))
|
||||
- **core:** Add an audit log for executions created by a trigger ([e5b0ff459](https://github.com/kestra-io/kestra/commit/e5b0ff459))
|
||||
- **script:** Add privileged flags to docker ([e8ee97e26](https://github.com/kestra-io/kestra/commit/e8ee97e26))
|
||||
- **core:** Add taskrun.iteration inside Pebble variables ([aa9af94c1](https://github.com/kestra-io/kestra/commit/aa9af94c1))
|
||||
- **webserver:** Add a configuration to change app html title ([622f158bd](https://github.com/kestra-io/kestra/commit/622f158bd))
|
||||
- ***:** Maintenance mode ([9901470b7](https://github.com/kestra-io/kestra/commit/9901470b7))
|
||||
- **core-ee:** Add log shipper first implementation ([#6596](https://github.com/kestra-io/kestra/pull/6596))
|
||||
- **core:** Add run context to log shipper ([#6651](https://github.com/kestra-io/kestra/pull/6651))
|
||||
- **core:** Restarting Subflow ([1e36d1eb2](https://github.com/kestra-io/kestra/commit/1e36d1eb2))
|
||||
- **ui:** Add an alert block for restarted execution ([576e023d9](https://github.com/kestra-io/kestra/commit/576e023d9))
|
||||
- **core:** Add `taskRunner` output on ScriptOutput to get detailled information on underlying taskrunner ([c265b49a8](https://github.com/kestra-io/kestra/commit/c265b49a8))
|
||||
- ***:** Fixes and improvements for custom dashboard ([#6684](https://github.com/kestra-io/kestra/pull/6684))
|
||||
- **core:** Add partial fix to micronaut hibernate validator and ValueExtractor ([81f08f00c](https://github.com/kestra-io/kestra/commit/81f08f00c))
|
||||
- **core:** Validate tasks and triggers with dynamic properties ([c5e23d43d](https://github.com/kestra-io/kestra/commit/c5e23d43d))
|
||||
- **core:** Add log record serialization ([#6683](https://github.com/kestra-io/kestra/pull/6683))
|
||||
- **core, ui, webserver:** Add replay system labels ([b3ed3d8df](https://github.com/kestra-io/kestra/commit/b3ed3d8df))
|
||||
- Add support for deleting empty namespace parent folders ([#5699](https://github.com/kestra-io/kestra/pull/5699))
|
||||
- **ui:** Add status remapping to status component to match the real name of the CSS vars ([a8be084e7](https://github.com/kestra-io/kestra/commit/a8be084e7))
|
||||
- **core:** Add outputs and ID to log shippers ([a994120d3](https://github.com/kestra-io/kestra/commit/a994120d3))
|
||||
- **core-ee:** Remove pagination from fetchAsync and use logRecord attributes ([#6698](https://github.com/kestra-io/kestra/pull/6698))
|
||||
- **core-ee:** Change log shipper properties names and use KV instead of state store ([#6709](https://github.com/kestra-io/kestra/pull/6709))
|
||||
- **core:** Introduce an `finally` block on flow & flowable ([#6686](https://github.com/kestra-io/kestra/pull/6686))
|
||||
- **webserver, ui:** Avoid cancelled SSE connection from following exec ([717d5560a](https://github.com/kestra-io/kestra/commit/717d5560a))
|
||||
- **ui:** Introduce the revamped no code editor ([#6787](https://github.com/kestra-io/kestra/pull/6787))
|
||||
- Add a story for executions list ([#6784](https://github.com/kestra-io/kestra/pull/6784))
|
||||
- **ui:** Allow exporting the flow to yaml file ([#6610](https://github.com/kestra-io/kestra/pull/6610))
|
||||
- **ui:** Improvements of no code editor ([#6804](https://github.com/kestra-io/kestra/pull/6804))
|
||||
- Various design fixes asked by nico ([#6798](https://github.com/kestra-io/kestra/pull/6798))
|
||||
- **core:** Validate in editor if subflow with namespace present ([#6717](https://github.com/kestra-io/kestra/pull/6717))
|
||||
- **core:** Add a randomPort pebble function ([4d074cb05](https://github.com/kestra-io/kestra/commit/4d074cb05))
|
||||
- **tasks:** Introduce an Assert tasks ([4d4963abd](https://github.com/kestra-io/kestra/commit/4d4963abd))
|
||||
- **core:** Schema title annotation for Custom Dashboard Filter ([c337d5527](https://github.com/kestra-io/kestra/commit/c337d5527))
|
||||
- Export oss chunks of vite config ([b542dda8e](https://github.com/kestra-io/kestra/commit/b542dda8e))
|
||||
- Simplify logs chunks by packaging markdown ([306d4ecd7](https://github.com/kestra-io/kestra/commit/306d4ecd7))
|
||||
- **ui:** Improvements of no code editor ([#6876](https://github.com/kestra-io/kestra/pull/6876))
|
||||
- ***:** OpenTelemetry traces ([e87b97a2e](https://github.com/kestra-io/kestra/commit/e87b97a2e))
|
||||
- **ui:** Re-work the task array field for the no code editor ([#6885](https://github.com/kestra-io/kestra/pull/6885))
|
||||
- **ui:** New plugin doc redesign ([1b0ce4d6a](https://github.com/kestra-io/kestra/commit/1b0ce4d6a))
|
||||
- Add support for multiple blueprint kinds ([ad719a97f](https://github.com/kestra-io/kestra/commit/ad719a97f))
|
||||
- **ui:** Make the one of component work properly ([#6900](https://github.com/kestra-io/kestra/pull/6900))
|
||||
- **ui:** Add the ability to remove tasks and other items ([#6902](https://github.com/kestra-io/kestra/pull/6902))
|
||||
- **ui:** Improve the one of task section ([#6903](https://github.com/kestra-io/kestra/pull/6903))
|
||||
- **ui:** New gantt layout to display taskrun state & duration with also attempts one ([f08bd94d5](https://github.com/kestra-io/kestra/commit/f08bd94d5))
|
||||
- **ui:** Improvements of no code editor ([#6916](https://github.com/kestra-io/kestra/pull/6916))
|
||||
- **ui:** Multiple improvements of no code editor ([#6923](https://github.com/kestra-io/kestra/pull/6923))
|
||||
- **ui:** Improve custom dashboard access ([#6940](https://github.com/kestra-io/kestra/pull/6940))
|
||||
- **ui:** New 404 page layout ([36675d90f](https://github.com/kestra-io/kestra/commit/36675d90f))
|
||||
- **core:** Add system.restarted: true label when changing the status of a task ([4f2d35fc4](https://github.com/kestra-io/kestra/commit/4f2d35fc4))
|
||||
- **ui, webserver:** Rename "Change status" to "Change state" and enhance the infos ([4e3ed33a4](https://github.com/kestra-io/kestra/commit/4e3ed33a4))
|
||||
- **core, jdbc:** Change the state of a subflow restart parent execution ([7cf495581](https://github.com/kestra-io/kestra/commit/7cf495581))
|
||||
- **ui:** Pretty layout for status on filters ([0a93b10a7](https://github.com/kestra-io/kestra/commit/0a93b10a7))
|
||||
- **ui:** Multiple improvements of no code editor ([#6951](https://github.com/kestra-io/kestra/pull/6951))
|
||||
- **ui:** Make DashboardEdit.vue overrided components ([#6954](https://github.com/kestra-io/kestra/pull/6954))
|
||||
- **ui:** Multiple improvements of no code editor ([#6981](https://github.com/kestra-io/kestra/pull/6981))
|
||||
- **core:** Rename WaitFor task to LoopUntil ([#6978](https://github.com/kestra-io/kestra/pull/6978))
|
||||
- **ui:** Don't load all revisions, optimize unnecessary calls and add back query params upon changing revisions ([69b5093d6](https://github.com/kestra-io/kestra/commit/69b5093d6))
|
||||
- **ui:** Design change on dashboard creation ([#6984](https://github.com/kestra-io/kestra/pull/6984))
|
||||
- **ui:** Better empty chart view + default view to documentation ([5f8a45305](https://github.com/kestra-io/kestra/commit/5f8a45305))
|
||||
- **deps:** Add support for OpenTelemetry metrics ([32112d2ff](https://github.com/kestra-io/kestra/commit/32112d2ff))
|
||||
- **ui:** Multiple improvements of no code editor ([#6991](https://github.com/kestra-io/kestra/pull/6991))
|
||||
- Add demo page for EE only features ([#7003](https://github.com/kestra-io/kestra/pull/7003))
|
||||
- **ui:** Add quick theme switcher ([3194a8926](https://github.com/kestra-io/kestra/commit/3194a8926))
|
||||
- Add enterprise edition empty to flow edition audit logs ([4375dc387](https://github.com/kestra-io/kestra/commit/4375dc387))
|
||||
- Add gradient animation on enterprise edition ([966b8fb3a](https://github.com/kestra-io/kestra/commit/966b8fb3a))
|
||||
- **ui:** Multiple improvements of no code editor ([#7028](https://github.com/kestra-io/kestra/pull/7028))
|
||||
- **core:** Remove deprecated properties and reduce duplication ([afabdf883](https://github.com/kestra-io/kestra/commit/afabdf883))
|
||||
- **ui:** Added Dashboards icons ([0661899e4](https://github.com/kestra-io/kestra/commit/0661899e4))
|
||||
- **ui:** Don't show deprecated tasks in the plugins list ([ab796dff9](https://github.com/kestra-io/kestra/commit/ab796dff9))
|
||||
- **webserver:** If no date provided for dashboard, then use default timewindow ([efa2d44e7](https://github.com/kestra-io/kestra/commit/efa2d44e7))
|
||||
- **ui:** Multiple improvements of no code editor ([#7076](https://github.com/kestra-io/kestra/pull/7076))
|
||||
- **ui:** Improve the task array component ([#7095](https://github.com/kestra-io/kestra/pull/7095))
|
||||
- **ui:** Allow task re-ordering from no code editor ([#7120](https://github.com/kestra-io/kestra/pull/7120))
|
||||
- **ui:** Add finally block to no code editor ([#7123](https://github.com/kestra-io/kestra/pull/7123))
|
||||
- **ui:** Multiple improvements of no code editor ([#7146](https://github.com/kestra-io/kestra/pull/7146))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **core:** Possible NPE when the Executor didn't have the flow ([0f2c5bb57](https://github.com/kestra-io/kestra/commit/0f2c5bb57))
|
||||
- **core:** Possible NPE when the Executor didn't have the flow ([2e9a0d132](https://github.com/kestra-io/kestra/commit/2e9a0d132))
|
||||
- **docs:** Keep use in docs sidebar when clicking in TOC page ([#6262](https://github.com/kestra-io/kestra/pull/6262))
|
||||
- **test:** Add metadata assertion to storage listing ([6dbdfad9c](https://github.com/kestra-io/kestra/commit/6dbdfad9c))
|
||||
- **ui:** Improve debug outputs expression on initial load ([#6094](https://github.com/kestra-io/kestra/pull/6094))
|
||||
- **ui:** Generalize filter width across browsers ([#5980](https://github.com/kestra-io/kestra/pull/5980))
|
||||
- **jdbc:** Possible race when initializing the JdbcMapper ([91dd6170f](https://github.com/kestra-io/kestra/commit/91dd6170f))
|
||||
- **core:** Correctly parse Content-Disposition in the Download task ([973ba0342](https://github.com/kestra-io/kestra/commit/973ba0342))
|
||||
- **core, webserver:** Properly close the queue on Flux.onFinally ([348547268](https://github.com/kestra-io/kestra/commit/348547268))
|
||||
- **ui:** Properly filter flows in namespace tab ([#6046](https://github.com/kestra-io/kestra/pull/6046))
|
||||
- **ui:** Handle logs selector overflow in a good manner ([#6224](https://github.com/kestra-io/kestra/pull/6224))
|
||||
- Rollback router update ([f02cf1f51](https://github.com/kestra-io/kestra/commit/f02cf1f51))
|
||||
- **core:** Fix Pause property annotation, exclude Input subtypes from definitions ([#6265](https://github.com/kestra-io/kestra/pull/6265))
|
||||
- **core:** Copy the list to avoid concurrent modification exception in count ([9cb1e5d46](https://github.com/kestra-io/kestra/commit/9cb1e5d46))
|
||||
- **ui:** Only apply editor padding on main editor ([#6310](https://github.com/kestra-io/kestra/pull/6310))
|
||||
- **ui:** Filter out system labels from executing using prefill ([#6311](https://github.com/kestra-io/kestra/pull/6311))
|
||||
- **webserver:** Automatically handle trailing slash in delete endpoint for namespace files ([#6266](https://github.com/kestra-io/kestra/pull/6266))
|
||||
- **docs:** Table formatting ([13fe55dc9](https://github.com/kestra-io/kestra/commit/13fe55dc9))
|
||||
- **ui:** Show status label in dialog for changing execution status ([#6323](https://github.com/kestra-io/kestra/pull/6323))
|
||||
- **ui:** Properly handle pebble expression if it contains dash character ([#6062](https://github.com/kestra-io/kestra/pull/6062))
|
||||
- **core:** Fix potential NPE in AbstractServiceLivenessCoordinator ([85a22bcfa](https://github.com/kestra-io/kestra/commit/85a22bcfa))
|
||||
- **jdbc:** Topology was built across all tenants ([21bf09df1](https://github.com/kestra-io/kestra/commit/21bf09df1))
|
||||
- **ui:** Axios missing content type ([525fcf6b7](https://github.com/kestra-io/kestra/commit/525fcf6b7))
|
||||
- **core:** Catch errors on task run ([8a26e3745](https://github.com/kestra-io/kestra/commit/8a26e3745))
|
||||
- **ui:** Make sure that property exists ([948347ace](https://github.com/kestra-io/kestra/commit/948347ace))
|
||||
- **core:** Fix cannot create Metric from null in Worker class ([6c6f072c2](https://github.com/kestra-io/kestra/commit/6c6f072c2))
|
||||
- **ui:** Properly handle filename with multiple dots in editor sidebar ([#6362](https://github.com/kestra-io/kestra/pull/6362))
|
||||
- **ui:** Prevent undefined on triggers that don't have sources anymore ([34c4ece23](https://github.com/kestra-io/kestra/commit/34c4ece23))
|
||||
- **tests:** Select file for metadata checks in list() test ([db41d81f6](https://github.com/kestra-io/kestra/commit/db41d81f6))
|
||||
- **jdbc:** Don't delete from the queues table ([fe6d6c85b](https://github.com/kestra-io/kestra/commit/fe6d6c85b))
|
||||
- **core:** Ensure child-first strategy for plugin class loading ([#4621](https://github.com/kestra-io/kestra/pull/4621))
|
||||
- Add typescript tsconfig ([#6316](https://github.com/kestra-io/kestra/pull/6316))
|
||||
- **ui:** Pass flow revision on execution overview ([#6380](https://github.com/kestra-io/kestra/pull/6380))
|
||||
- **core:** Exclude reactivestreams from classloading isolation ([#4621](https://github.com/kestra-io/kestra/pull/4621))
|
||||
- **core:** Exclude common libs from classloading isolation ([#4621](https://github.com/kestra-io/kestra/pull/4621))
|
||||
- **ui:** Add Nuxt alias + bump some deps in package-lock.json ([d98488526](https://github.com/kestra-io/kestra/commit/d98488526))
|
||||
- Deprecated condition ([200cedaca](https://github.com/kestra-io/kestra/commit/200cedaca))
|
||||
- **ui:** Flow create was no longer generating graph ([99227a644](https://github.com/kestra-io/kestra/commit/99227a644))
|
||||
- **ui:** Total is not needed in FlowCreate.vue ([495f2cd39](https://github.com/kestra-io/kestra/commit/495f2cd39))
|
||||
- **ui:** Avoid unsaved changes pop-up upon clicking on plugin property type definition anchors ([88bb0748d](https://github.com/kestra-io/kestra/commit/88bb0748d))
|
||||
- Required Boolean & Multiselect rules ([#6445](https://github.com/kestra-io/kestra/pull/6445))
|
||||
- Avoid redirect loops when axios calls an unauthorized API ([#6450](https://github.com/kestra-io/kestra/pull/6450))
|
||||
- **tests:** Load only required flows for runner tests ([#6447](https://github.com/kestra-io/kestra/pull/6447))
|
||||
- **core-ee:** Change Objects.equals for tenant id to prevent NPE ([#6411](https://github.com/kestra-io/kestra/pull/6411))
|
||||
- **tests:** Remove merge namespace mistake ([870a3217e](https://github.com/kestra-io/kestra/commit/870a3217e))
|
||||
- **core:** Exclude def.failsafe from classloading isolation ([#4621](https://github.com/kestra-io/kestra/pull/4621))
|
||||
- **core:** Wait for running executor for liveness executor ([89ccdfc77](https://github.com/kestra-io/kestra/commit/89ccdfc77))
|
||||
- **webserver:** TriggerController endpoint issues ([9238b5600](https://github.com/kestra-io/kestra/commit/9238b5600))
|
||||
- **jdbc:** Missing SKIPPED state in DB migrations ([#6487](https://github.com/kestra-io/kestra/pull/6487))
|
||||
- **core:** Handle input & output with id of 1 chars ([73026d064](https://github.com/kestra-io/kestra/commit/73026d064))
|
||||
- **jdbc-postgres:** Escape special chars on full text search ([506460635](https://github.com/kestra-io/kestra/commit/506460635))
|
||||
- **core:** Properly check next scheduled date for backfill execution ([#6413](https://github.com/kestra-io/kestra/pull/6413))
|
||||
- **ui:** Put 0.0.0 version in package.json as we don't update it and rely on gradle.properties version instead ([a1f1987d7](https://github.com/kestra-io/kestra/commit/a1f1987d7))
|
||||
- **core:** Save flowable's output when flowable is child of another flowable ([#6500](https://github.com/kestra-io/kestra/pull/6500))
|
||||
- **webserver:** Correct the triger find endpoint URL ([556fabc3a](https://github.com/kestra-io/kestra/commit/556fabc3a))
|
||||
- **jdbc:** Read the disabled flag from the DB ([01e6d529f](https://github.com/kestra-io/kestra/commit/01e6d529f))
|
||||
- Add missing mutation when loading plugin doc form cache ([#6502](https://github.com/kestra-io/kestra/pull/6502))
|
||||
- **core:** Remove race condition on trigger runner test ([#6516](https://github.com/kestra-io/kestra/pull/6516))
|
||||
- **run:** Add LD_PRELOAD to handle duckdb/rocksdb libc conflict ([0ab4b6d10](https://github.com/kestra-io/kestra/commit/0ab4b6d10))
|
||||
- Subflow docs ([7a8d2aeb9](https://github.com/kestra-io/kestra/commit/7a8d2aeb9))
|
||||
- **webserver:** Rework tests in order to load only required flows ([#6517](https://github.com/kestra-io/kestra/pull/6517))
|
||||
- **ui:** If no trigger state filter is selected, show them all ([#6522](https://github.com/kestra-io/kestra/pull/6522))
|
||||
- **core:** Existing ns based on KV and not only flows ([f550fde76](https://github.com/kestra-io/kestra/commit/f550fde76))
|
||||
- **runner-kafka:** Interface contract of template test changed ([#6536](https://github.com/kestra-io/kestra/pull/6536))
|
||||
- **ui:** Only silence the necessary sass warnings ([edb7f5443](https://github.com/kestra-io/kestra/commit/edb7f5443))
|
||||
- **ui:** Improve namespaces filtering ([#6564](https://github.com/kestra-io/kestra/pull/6564))
|
||||
- **core:** Enable runIf property on flowable tasks ([462c50562](https://github.com/kestra-io/kestra/commit/462c50562))
|
||||
- **core:** Better method when looking for 'value' prop in a JsonNode for auditLog patch application ([#6569](https://github.com/kestra-io/kestra/pull/6569))
|
||||
- **ui:** Add check for property existence ([5da044c80](https://github.com/kestra-io/kestra/commit/5da044c80))
|
||||
- Update package json again to fix storybook tests ([7b4b90581](https://github.com/kestra-io/kestra/commit/7b4b90581))
|
||||
- **core:** Killing paused without subtask should transition to KILLED ([a9ff469f7](https://github.com/kestra-io/kestra/commit/a9ff469f7))
|
||||
- **core:** Configuration of log on the http client ([5fee07e76](https://github.com/kestra-io/kestra/commit/5fee07e76))
|
||||
- **core:** Handle http auth ([a33f85bf1](https://github.com/kestra-io/kestra/commit/a33f85bf1))
|
||||
- **core:** Fix download http tasks ([153d4ced9](https://github.com/kestra-io/kestra/commit/153d4ced9))
|
||||
- **core:** Restore deprecated on the ui for http task ([73b68294c](https://github.com/kestra-io/kestra/commit/73b68294c))
|
||||
- **core:** Make http client creation explicit ([4a5f46132](https://github.com/kestra-io/kestra/commit/4a5f46132))
|
||||
- **deps:** Include httpcore5 in platform deps ([88709a2db](https://github.com/kestra-io/kestra/commit/88709a2db))
|
||||
- **cicd:** Don't build docker image & maven if failed test ([9ebf2ac75](https://github.com/kestra-io/kestra/commit/9ebf2ac75))
|
||||
- **jdbc:** Avoid duplicating the source when deleting the flow ([44f363911](https://github.com/kestra-io/kestra/commit/44f363911))
|
||||
- **storage-local:** Path traversal guard should include File.separator ([b7f9a3201](https://github.com/kestra-io/kestra/commit/b7f9a3201))
|
||||
- **core:** ExitTest is flaky ([409d78615](https://github.com/kestra-io/kestra/commit/409d78615))
|
||||
- Fixes on select boxes ([68646b07a](https://github.com/kestra-io/kestra/commit/68646b07a))
|
||||
- Alert colors and place of figma theme ([5bec2fc06](https://github.com/kestra-io/kestra/commit/5bec2fc06))
|
||||
- Select multiple choices icon color an shape ([50a9f6132](https://github.com/kestra-io/kestra/commit/50a9f6132))
|
||||
- Element alert background ([e1987ed2e](https://github.com/kestra-io/kestra/commit/e1987ed2e))
|
||||
- **core:** Path traversal guard ([f12a0a054](https://github.com/kestra-io/kestra/commit/f12a0a054))
|
||||
- **core:** Multiple conditon tests are flaky ([eaa72dde4](https://github.com/kestra-io/kestra/commit/eaa72dde4))
|
||||
- More fixes on css variables ([e1c202a3c](https://github.com/kestra-io/kestra/commit/e1c202a3c))
|
||||
- Finish the overload of buttons ([0cdc6f062](https://github.com/kestra-io/kestra/commit/0cdc6f062))
|
||||
- Add proper typing for vuex ([73c100ce1](https://github.com/kestra-io/kestra/commit/73c100ce1))
|
||||
- **core:** Http proxy was not used ([d67d44b93](https://github.com/kestra-io/kestra/commit/d67d44b93))
|
||||
- Set the flowEditor docId on new ([869c6877b](https://github.com/kestra-io/kestra/commit/869c6877b))
|
||||
- **core:** Correctly displayed errors icons ([#6654](https://github.com/kestra-io/kestra/pull/6654))
|
||||
- **core:** Worker log are displaying the wrong state on terminated tasks ([f2a3ad557](https://github.com/kestra-io/kestra/commit/f2a3ad557))
|
||||
- **webserver:** Reset correctly nextExecutionDate when enabling schedule ([35ffe7e63](https://github.com/kestra-io/kestra/commit/35ffe7e63))
|
||||
- Fix no execution layout ([155f9b9d7](https://github.com/kestra-io/kestra/commit/155f9b9d7))
|
||||
- **ui:** Update CSS variable for log icon color in LogLine component ([e09e21633](https://github.com/kestra-io/kestra/commit/e09e21633))
|
||||
- Logline when there is a {{ ([86cb11fb8](https://github.com/kestra-io/kestra/commit/86cb11fb8))
|
||||
- **core:** Continue WaitFor loop if tasks are not failed ([#6572](https://github.com/kestra-io/kestra/pull/6572))
|
||||
- **core:** Handle runIf inside workingDirectory ([#6690](https://github.com/kestra-io/kestra/pull/6690))
|
||||
- **core:** Plugin default was not validating correctly boolean methods ([841f32a9f](https://github.com/kestra-io/kestra/commit/841f32a9f))
|
||||
- **core:** NPE in ExecutableUtils ([#6699](https://github.com/kestra-io/kestra/pull/6699))
|
||||
- ***:** Create real chart preview endpoint ([3c19c910a](https://github.com/kestra-io/kestra/commit/3c19c910a))
|
||||
- **jdbc:** Resubmitting a worker job running should create a new attempt ([b9a74b875](https://github.com/kestra-io/kestra/commit/b9a74b875))
|
||||
- **core:** If with only disabled tasks ([1c705e2d7](https://github.com/kestra-io/kestra/commit/1c705e2d7))
|
||||
- **ui:** Fix reset of flow import input ([159927d39](https://github.com/kestra-io/kestra/commit/159927d39))
|
||||
- Improve storybook styles ([6691575fa](https://github.com/kestra-io/kestra/commit/6691575fa))
|
||||
- Async docs were failing beause of issue in ui-libs MDC ([e4698b0f1](https://github.com/kestra-io/kestra/commit/e4698b0f1))
|
||||
- Storybook - only i18nize once in preview ([55b672033](https://github.com/kestra-io/kestra/commit/55b672033))
|
||||
- **jdbc:** Batch query expand query and lead to overflow of metrics ([c73305921](https://github.com/kestra-io/kestra/commit/c73305921))
|
||||
- **core, ui:** Send a "start" event to be sure the UI receive the SSE ([0c0ff37c6](https://github.com/kestra-io/kestra/commit/0c0ff37c6))
|
||||
- **core:** Always close the queue after receive ([f217d3376](https://github.com/kestra-io/kestra/commit/f217d3376))
|
||||
- **core:** Graph on dag are not attaching finally at the end of the dag task ([e98543822](https://github.com/kestra-io/kestra/commit/e98543822))
|
||||
- **ui:** Missing icons for finally ([52aba32c1](https://github.com/kestra-io/kestra/commit/52aba32c1))
|
||||
- Allow custom translations to be passed ([#6752](https://github.com/kestra-io/kestra/pull/6752))
|
||||
- **jdbc:** Ensure JdbcIndexer is only closed once ([94545fe2f](https://github.com/kestra-io/kestra/commit/94545fe2f))
|
||||
- **core:** Remove 64 characters limitation for displayName ([#6470](https://github.com/kestra-io/kestra/pull/6470))
|
||||
- **jdbc:** Update test config for flaky test on liveness ([#6656](https://github.com/kestra-io/kestra/pull/6656))
|
||||
- **core:** Parallel task should not resolved error and finally in parallel ([f1c147cc4](https://github.com/kestra-io/kestra/commit/f1c147cc4))
|
||||
- **core:** Flow should not continue tasks when having a finally ([9f12f9a63](https://github.com/kestra-io/kestra/commit/9f12f9a63))
|
||||
- Add ellipsis for Namespace charts ([#6730](https://github.com/kestra-io/kestra/pull/6730))
|
||||
- Topology css issues ([4c6f8bd90](https://github.com/kestra-io/kestra/commit/4c6f8bd90))
|
||||
- **core:** Property with default ([b2c411956](https://github.com/kestra-io/kestra/commit/b2c411956))
|
||||
- **webserver:** Add check in file creation path for _flows* ([#6228](https://github.com/kestra-io/kestra/pull/6228))
|
||||
- **ui:** Flows overview are not scoped to current flow ([554ea5850](https://github.com/kestra-io/kestra/commit/554ea5850))
|
||||
- **jdbc:** Summary count should be a prefix on the namespace ([9a19c0108](https://github.com/kestra-io/kestra/commit/9a19c0108))
|
||||
- Update custom upload button ([f5dc67f0e](https://github.com/kestra-io/kestra/commit/f5dc67f0e))
|
||||
- El-empty needed a backdrop ([979c131b9](https://github.com/kestra-io/kestra/commit/979c131b9))
|
||||
- **core:** ForEach failed with errors, finally and concurrency = 0 ([246602f9f](https://github.com/kestra-io/kestra/commit/246602f9f))
|
||||
- Reduce log borders on gantt chart logs ([960a1bba4](https://github.com/kestra-io/kestra/commit/960a1bba4))
|
||||
- **ui:** Properly encode text filter parameter to url string ([#6803](https://github.com/kestra-io/kestra/pull/6803))
|
||||
- **ui:** Amend the ability to create tasks ([#6808](https://github.com/kestra-io/kestra/pull/6808))
|
||||
- **ui:** Improve the editing of labels and variables ([#6810](https://github.com/kestra-io/kestra/pull/6810))
|
||||
- **core:** Fix some labels are lost when having same prefix key ([207e8c3a7](https://github.com/kestra-io/kestra/commit/207e8c3a7))
|
||||
- Color of active item in sidebar ([05e5af73a](https://github.com/kestra-io/kestra/commit/05e5af73a))
|
||||
- Correct the description for DayWeek condition ([#6834](https://github.com/kestra-io/kestra/pull/6834))
|
||||
- **core:** Deprecated allowFailed should not have any default ([02831295b](https://github.com/kestra-io/kestra/commit/02831295b))
|
||||
- **sidebar:** Implement the _hover class in addition to hover pseudo class ([7e926fc8e](https://github.com/kestra-io/kestra/commit/7e926fc8e))
|
||||
- **core:** Avoid double STRING schema type for types that resolves to a String ([1a130daa2](https://github.com/kestra-io/kestra/commit/1a130daa2))
|
||||
- Correct the condition name ([#6843](https://github.com/kestra-io/kestra/pull/6843))
|
||||
- **core:** RandomInt receive a Long not an Integer ([01a77aff0](https://github.com/kestra-io/kestra/commit/01a77aff0))
|
||||
- Update the vite config to use the rollup dependencies properly ([#6853](https://github.com/kestra-io/kestra/pull/6853))
|
||||
- **Count:** Fix Count task ([db496a8fa](https://github.com/kestra-io/kestra/commit/db496a8fa))
|
||||
- Logs page redirect ([40c43256a](https://github.com/kestra-io/kestra/commit/40c43256a))
|
||||
- **core:** FromIon is reading only the first rows by default, adding a parameter to read all rows ([1926ad5e0](https://github.com/kestra-io/kestra/commit/1926ad5e0))
|
||||
- **ui:** Amend namespace editor problem ([#6883](https://github.com/kestra-io/kestra/pull/6883))
|
||||
- Add title to TemplatedTask ([#6891](https://github.com/kestra-io/kestra/pull/6891))
|
||||
- **ui:** Properly handle file explorer visibility in namespace files section ([#6895](https://github.com/kestra-io/kestra/pull/6895))
|
||||
- **ui:** Fix multiple issues on blueprints ([2e18c295e](https://github.com/kestra-io/kestra/commit/2e18c295e))
|
||||
- **ui:** Fix blueprints use button ([8e5ffe835](https://github.com/kestra-io/kestra/commit/8e5ffe835))
|
||||
- **ui:** Amend flow concurrency overview ([0a047a7b7](https://github.com/kestra-io/kestra/commit/0a047a7b7))
|
||||
- **webserver:** Continue to warn, but do not return trigger with missing flow ([#6905](https://github.com/kestra-io/kestra/pull/6905))
|
||||
- **ui:** Prevent responsive on monaco editor for diff ([c99476276](https://github.com/kestra-io/kestra/commit/c99476276))
|
||||
- **ui:** Fix use button for dashboard blueprint ([699e232dd](https://github.com/kestra-io/kestra/commit/699e232dd))
|
||||
- **ui:** Add missing use button into blueprin details ([9781ff156](https://github.com/kestra-io/kestra/commit/9781ff156))
|
||||
- **ui:** Make disabled select less visible ([fc84ee3e5](https://github.com/kestra-io/kestra/commit/fc84ee3e5))
|
||||
- **ui:** Proper log display without multiple line ([a97448f19](https://github.com/kestra-io/kestra/commit/a97448f19))
|
||||
- **ui:** Add confirmation message for crud on custom dashboard ([c795f3756](https://github.com/kestra-io/kestra/commit/c795f3756))
|
||||
- **ui:** Rollouver on side menu was blinking ([ae2d55570](https://github.com/kestra-io/kestra/commit/ae2d55570))
|
||||
- **ui:** Left menu is blinking on blueprint page ([6bc66d66e](https://github.com/kestra-io/kestra/commit/6bc66d66e))
|
||||
- **ui:** Remove some warnings about missing props for blueprints ([fccaef462](https://github.com/kestra-io/kestra/commit/fccaef462))
|
||||
- **ui:** More explicit else-if branches for switch view ([ae408209f](https://github.com/kestra-io/kestra/commit/ae408209f))
|
||||
- **ui:** Cleanup imports and get rid of some warnings ([09ef11507](https://github.com/kestra-io/kestra/commit/09ef11507))
|
||||
- **core:** Flow equalsWithoutRevision don't use serialization to compare flows so that map orders don't matter ([275951011](https://github.com/kestra-io/kestra/commit/275951011))
|
||||
- **ui:** Save content to proper file using the namespace file editor ([#6931](https://github.com/kestra-io/kestra/pull/6931))
|
||||
- **script:** Update release-plugins ([3073d9e92](https://github.com/kestra-io/kestra/commit/3073d9e92))
|
||||
- **ui:** Invalid hover on left menu for white theme ([17e0340ab](https://github.com/kestra-io/kestra/commit/17e0340ab))
|
||||
- Update ui-libs for docs ([2e23269a9](https://github.com/kestra-io/kestra/commit/2e23269a9))
|
||||
- See all is-text button color closes #6898 ([#6898](https://github.com/kestra-io/kestra/issues/6898))
|
||||
- Return task docs ([fedb388e5](https://github.com/kestra-io/kestra/commit/fedb388e5))
|
||||
- **ui:** Namepsace > blueprint display 404 page ([4f96f8124](https://github.com/kestra-io/kestra/commit/4f96f8124))
|
||||
- **core:** Fix unit test on DocumentationGeneratorTest ([f8ff2e020](https://github.com/kestra-io/kestra/commit/f8ff2e020))
|
||||
- **webserver:** Ensure queues are not closed in nioEventLoop ([c52fdd064](https://github.com/kestra-io/kestra/commit/c52fdd064))
|
||||
- **ui:** Let filter dropdown fit width of the content ([adb8d9a9d](https://github.com/kestra-io/kestra/commit/adb8d9a9d))
|
||||
- **core, jdbc:** Count task ([2359c4f74](https://github.com/kestra-io/kestra/commit/2359c4f74))
|
||||
- **ui:** Missing translations ([a1a1fbc31](https://github.com/kestra-io/kestra/commit/a1a1fbc31))
|
||||
- **ui:** Remove dot from template ([e0e7bd132](https://github.com/kestra-io/kestra/commit/e0e7bd132))
|
||||
- Dashboards in plural ([#6971](https://github.com/kestra-io/kestra/pull/6971))
|
||||
- **core:** ThresholdFilter is now stricly lower ([bd82f5e3b](https://github.com/kestra-io/kestra/commit/bd82f5e3b))
|
||||
- Use new color scheme for execution stats on flows list ([1c08176c9](https://github.com/kestra-io/kestra/commit/1c08176c9))
|
||||
- **ui:** Taskrun filters and new charts ([f1c5555f6](https://github.com/kestra-io/kestra/commit/f1c5555f6))
|
||||
- **ui:** Don't display max displayable since not relevant ([bcaa613f7](https://github.com/kestra-io/kestra/commit/bcaa613f7))
|
||||
- **ui:** Better layout filters comparator ([125a277aa](https://github.com/kestra-io/kestra/commit/125a277aa))
|
||||
- **ui:** Use color for log on custom charts ([5268c793e](https://github.com/kestra-io/kestra/commit/5268c793e))
|
||||
- **core:** Generate properly taskrunners and log exporters implementations in json schema after adding generic types ([94e42a9dc](https://github.com/kestra-io/kestra/commit/94e42a9dc))
|
||||
- **core:** Add log exporters to plugins ([16be38aaf](https://github.com/kestra-io/kestra/commit/16be38aaf))
|
||||
- **ui:** Add log exporters to plugins ([0438fbff6](https://github.com/kestra-io/kestra/commit/0438fbff6))
|
||||
- **ui:** Better filtering for yamlUtils.extractFieldFromMaps ([cf316eb83](https://github.com/kestra-io/kestra/commit/cf316eb83))
|
||||
- **ui:** Missing image ([325c68094](https://github.com/kestra-io/kestra/commit/325c68094))
|
||||
- **ui:** Rename cluster to instance ([41e4abba7](https://github.com/kestra-io/kestra/commit/41e4abba7))
|
||||
- **ui:** Avoid monaco to show every words used as autocompletion when there is no suggestion ([fd4a51259](https://github.com/kestra-io/kestra/commit/fd4a51259))
|
||||
- **core:** Reset the MDC when we clean the run context ([c9095bd87](https://github.com/kestra-io/kestra/commit/c9095bd87))
|
||||
- **core:** Use DCL when creating the logger ([3640b0863](https://github.com/kestra-io/kestra/commit/3640b0863))
|
||||
- **core:** Use the MDC from the loggin context ([151d4d9da](https://github.com/kestra-io/kestra/commit/151d4d9da))
|
||||
- **ui:** Fix blueprints ([9d974ad84](https://github.com/kestra-io/kestra/commit/9d974ad84))
|
||||
- **ui:** Handle light theme properly in plugin doc ([dc13e9117](https://github.com/kestra-io/kestra/commit/dc13e9117))
|
||||
- **ui:** Better css with in editor doc for dashboard ([49d64b7f8](https://github.com/kestra-io/kestra/commit/49d64b7f8))
|
||||
- **ui:** Better handling of plugin type to display in doc from editor ([7ec9f6c81](https://github.com/kestra-io/kestra/commit/7ec9f6c81))
|
||||
- **ui:** Remove console.log() ([035f6b3b6](https://github.com/kestra-io/kestra/commit/035f6b3b6))
|
||||
- **core:** Dashboard end date should apply to the execution start date ([82150e606](https://github.com/kestra-io/kestra/commit/82150e606))
|
||||
- Ee empty pages background and add flare ([34247a971](https://github.com/kestra-io/kestra/commit/34247a971))
|
||||
- Debouncing of input validation ([3a01a44b2](https://github.com/kestra-io/kestra/commit/3a01a44b2))
|
||||
- **ui:** Easier gantt painting handling ([e4016ebb7](https://github.com/kestra-io/kestra/commit/e4016ebb7))
|
||||
- Set docId for namespace demo ([48427f185](https://github.com/kestra-io/kestra/commit/48427f185))
|
||||
- **core:** Add de app block to plugin controller (kestra-io/kestra-ee#2773) ([#2773](https://github.com/kestra-io/kestra/issues/2773))
|
||||
- Align the flare regardless of language ([80d1f2d6c](https://github.com/kestra-io/kestra/commit/80d1f2d6c))
|
||||
- **core:** Catch linkageError when a class in already in classpath ([#7029](https://github.com/kestra-io/kestra/pull/7029))
|
||||
- **cli:** Doc generation failure handling to avoid infinite wait ([1a23df3a7](https://github.com/kestra-io/kestra/commit/1a23df3a7))
|
||||
- Various fixes for the empty pages ([a10f65c1d](https://github.com/kestra-io/kestra/commit/a10f65c1d))
|
||||
- Wrong title for apps page ([061e4f5ec](https://github.com/kestra-io/kestra/commit/061e4f5ec))
|
||||
- **core:** Properties as map is now working properly with expression within base maps ([b846f11b3](https://github.com/kestra-io/kestra/commit/b846f11b3))
|
||||
- **script:** InjectDefault need the runContext ([c5e7b4281](https://github.com/kestra-io/kestra/commit/c5e7b4281))
|
||||
- **script:** TargetOS must be rendered ([3e8d32cfc](https://github.com/kestra-io/kestra/commit/3e8d32cfc))
|
||||
- Realign vue tour ui element for onboarding closes #7010 ([#7010](https://github.com/kestra-io/kestra/issues/7010))
|
||||
- **ui:** Better inputs validation for backfill ([95ea6536c](https://github.com/kestra-io/kestra/commit/95ea6536c))
|
||||
- Label colors in executions list ([6dfa08af8](https://github.com/kestra-io/kestra/commit/6dfa08af8))
|
||||
- **cli:** Flow watcher should compute plugin defaults ([2d98f909d](https://github.com/kestra-io/kestra/commit/2d98f909d))
|
||||
- **ui:** Dynamic format date ([acdb46cea](https://github.com/kestra-io/kestra/commit/acdb46cea))
|
||||
- **core:** RestartForEachItem() is flaky ([d12fbf05b](https://github.com/kestra-io/kestra/commit/d12fbf05b))
|
||||
- **ui:** Amend no code editor breadcrumbs issue ([#7054](https://github.com/kestra-io/kestra/pull/7054))
|
||||
- **script:** AbstractExecScript.injectDefaults should throw IllegalVariableEvaluationException ([c08f4f24c](https://github.com/kestra-io/kestra/commit/c08f4f24c))
|
||||
- **cli:** Repeate flaky tests FileChangedEventListenerTest ([c792d9b6e](https://github.com/kestra-io/kestra/commit/c792d9b6e))
|
||||
- **docs:** Remove custom dashboard website component ([5f7468a9a](https://github.com/kestra-io/kestra/commit/5f7468a9a))
|
||||
- **core:** Subflow validation didn't work anymore ([ceda5eb8e](https://github.com/kestra-io/kestra/commit/ceda5eb8e))
|
||||
- **core:** Subflow labels must not be overriden by parent flow ones ([d12dd179c](https://github.com/kestra-io/kestra/commit/d12dd179c))
|
||||
- **core:** Retry flaky test TimeoutTest.timeout() ([95d2d1dfa](https://github.com/kestra-io/kestra/commit/95d2d1dfa))
|
||||
- **ui:** Fix missing param kind for blueprint in flow editor ([#7087](https://github.com/kestra-io/kestra/pull/7087))
|
||||
- **ci:** Update scripts/workflows for plugins ([2b72306b3](https://github.com/kestra-io/kestra/commit/2b72306b3))
|
||||
- **ui:** Restore namespace filter manual typing & various improvements ([#7127](https://github.com/kestra-io/kestra/pull/7127))
|
||||
- **core:** Remove the dynamic property patterns ([1ed882e8f](https://github.com/kestra-io/kestra/commit/1ed882e8f))
|
||||
- **ui:** Switching from custom Flow blueprints tab to dashboard was not working ([3f2d91014](https://github.com/kestra-io/kestra/commit/3f2d91014))
|
||||
- **ui:** Custom Dashboard name overflows. ([#7124](https://github.com/kestra-io/kestra/pull/7124))
|
||||
- **ui:** Get the string fields in no code to use editor and have auto completion back ([#7150](https://github.com/kestra-io/kestra/pull/7150))
|
||||
- Setup docId for blueprints ([39a2293a4](https://github.com/kestra-io/kestra/commit/39a2293a4))
|
||||
- Sidemenu bring back the gray hover ([33ecf8d5f](https://github.com/kestra-io/kestra/commit/33ecf8d5f))
|
||||
- Bring back hover in main menu ([cf4b91f44](https://github.com/kestra-io/kestra/commit/cf4b91f44))
|
||||
- Enterprise edition tag in light mode ([cb3195900](https://github.com/kestra-io/kestra/commit/cb3195900))
|
||||
- **core:** Process runner are not serialized correctly on worker ([4392c89ec](https://github.com/kestra-io/kestra/commit/4392c89ec))
|
||||
- **ui:** Null-safe search filters ([a6ce86d70](https://github.com/kestra-io/kestra/commit/a6ce86d70))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **ui:** Enforce block order in ESLint configuration ([#6299](https://github.com/kestra-io/kestra/pull/6299))
|
||||
- Replace getCurrentInstance router access with useRouter ([#6210](https://github.com/kestra-io/kestra/pull/6210))
|
||||
- **core:** Optimize keepLastVersionCollector method ([ff0b7c4a0](https://github.com/kestra-io/kestra/commit/ff0b7c4a0))
|
||||
- **test:** Create an ExecutionUtils to load flow ([bfea840fa](https://github.com/kestra-io/kestra/commit/bfea840fa))
|
||||
- **test:** Use application context from the current test suite on FlowLoad ([706764415](https://github.com/kestra-io/kestra/commit/706764415))
|
||||
- **ui:** Move stories to tests/storybook ([#6515](https://github.com/kestra-io/kestra/pull/6515))
|
||||
- Migrate AbstractExecScript, CommandsWrapper, NamespaceFiles, OutputFilesInterface to dynamic properties ([c6a09cd9d](https://github.com/kestra-io/kestra/commit/c6a09cd9d))
|
||||
- Major css variable overhaul ([#6497](https://github.com/kestra-io/kestra/pull/6497))
|
||||
- Removed unused css in sidebar ([20276a9e7](https://github.com/kestra-io/kestra/commit/20276a9e7))
|
||||
- Remove spacer form sidebar ([035a27c91](https://github.com/kestra-io/kestra/commit/035a27c91))
|
||||
- Remove bootstrap color from sidebar ([9b59c75d2](https://github.com/kestra-io/kestra/commit/9b59c75d2))
|
||||
- **core:** Add new interface HasSource ([d96742d7d](https://github.com/kestra-io/kestra/commit/d96742d7d))
|
||||
- **webserver:** Move method to read source file/archive to HasSource interface ([17120cff8](https://github.com/kestra-io/kestra/commit/17120cff8))
|
||||
- Add async loading for better performance ([#6643](https://github.com/kestra-io/kestra/pull/6643))
|
||||
- Migrate package plugin.core.http to dynamic properties ([a934414db](https://github.com/kestra-io/kestra/commit/a934414db))
|
||||
- Add [at]deprecated javadoc message : sonar ([589638cc3](https://github.com/kestra-io/kestra/commit/589638cc3))
|
||||
- Migrate package plugin.core.kv to dynamic properties ([d052a87c0](https://github.com/kestra-io/kestra/commit/d052a87c0))
|
||||
- Address sonar issues ([f2e4a3531](https://github.com/kestra-io/kestra/commit/f2e4a3531))
|
||||
- Migrate plugin.core.output to dynamic properties ([4de65e7a3](https://github.com/kestra-io/kestra/commit/4de65e7a3))
|
||||
- Migrate package plugin.core.debug to dynamic properties ([#6697](https://github.com/kestra-io/kestra/pull/6697))
|
||||
- Migrate package plugin.core.state to dynamic properties ([#6755](https://github.com/kestra-io/kestra/pull/6755))
|
||||
- Migrate package plugin.core.storage to dynamic properties ([#6770](https://github.com/kestra-io/kestra/pull/6770))
|
||||
- Migrate package plugin.core.templating to dynamic properties ([#6775](https://github.com/kestra-io/kestra/pull/6775))
|
||||
- Avoid usage of runtime template to smaller bundle ([#6779](https://github.com/kestra-io/kestra/pull/6779))
|
||||
- Migrate package plugin.core.execution to dynamic properties ([#6708](https://github.com/kestra-io/kestra/pull/6708))
|
||||
- Migrate plugin.core.log to dynamic properties ([#6823](https://github.com/kestra-io/kestra/pull/6823))
|
||||
- Migrate plugin.core.namespace to dynamic properties ([#6832](https://github.com/kestra-io/kestra/pull/6832))
|
||||
- Migrate plugin.core.log to dynamic properties ([#6732](https://github.com/kestra-io/kestra/pull/6732))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Context docs appId basic setup ([#6341](https://github.com/kestra-io/kestra/pull/6341))
|
||||
- Make windows instructions more specific ([7cdc04029](https://github.com/kestra-io/kestra/commit/7cdc04029))
|
||||
- Conditional inputs and outputs ([2594f2816](https://github.com/kestra-io/kestra/commit/2594f2816))
|
||||
- Add more keyboard shortcuts ([0f23d3382](https://github.com/kestra-io/kestra/commit/0f23d3382))
|
||||
- Add nested loop example ([#6948](https://github.com/kestra-io/kestra/pull/6948))
|
||||
- **custom-dashboard:** Update example to use metrics and logs ([#7007](https://github.com/kestra-io/kestra/pull/7007))
|
||||
|
||||
### 📦 Build
|
||||
|
||||
- Setup storybook for development in isolation ([#6499](https://github.com/kestra-io/kestra/pull/6499))
|
||||
- **ci:** Allow docker-build when skipping tests ([729972fd1](https://github.com/kestra-io/kestra/commit/729972fd1))
|
||||
- Use husky instead of ghooks ([dba5c39de](https://github.com/kestra-io/kestra/commit/dba5c39de))
|
||||
- Attempt to fix storybook tests swc issue ([0cc5fb964](https://github.com/kestra-io/kestra/commit/0cc5fb964))
|
||||
- Fix storybook tests ([5f7695e3e](https://github.com/kestra-io/kestra/commit/5f7695e3e))
|
||||
- Add new project properties for release ([d79e40dd9](https://github.com/kestra-io/kestra/commit/d79e40dd9))
|
||||
- Add script and github workflow to tag all plugins ([64dcc9649](https://github.com/kestra-io/kestra/commit/64dcc9649))
|
||||
- Try and fix FE CI ([f3852a3c2](https://github.com/kestra-io/kestra/commit/f3852a3c2))
|
||||
- Prevent corepack crash ([f609d57a0](https://github.com/kestra-io/kestra/commit/f609d57a0))
|
||||
|
||||
### 🏡 Chores
|
||||
|
||||
- **version:** Update to version 'v0.20.0'. ([279be8344](https://github.com/kestra-io/kestra/commit/279be8344))
|
||||
- **version:** Update snapshot version 'v0.21.0-SNAPSHOT'. ([fba4a7929](https://github.com/kestra-io/kestra/commit/fba4a7929))
|
||||
- **version:** Update to version 'v0.20.0'. ([aa1ba5998](https://github.com/kestra-io/kestra/commit/aa1ba5998))
|
||||
- **ui:** Properly highlight selected options in all of the filter dropdowns ([#6173](https://github.com/kestra-io/kestra/pull/6173))
|
||||
- **ui:** Add top and left padding to editor component ([#6191](https://github.com/kestra-io/kestra/pull/6191))
|
||||
- **ui:** Add a confirmation dialog before removing a bookmark ([#6217](https://github.com/kestra-io/kestra/pull/6217))
|
||||
- **translations:** Auto generate values for languages other than english ([02898d29a](https://github.com/kestra-io/kestra/commit/02898d29a))
|
||||
- **ui:** Remove search field background on single plugin page ([#6220](https://github.com/kestra-io/kestra/pull/6220))
|
||||
- **ui:** Amend spacing on plugins page ([#6223](https://github.com/kestra-io/kestra/pull/6223))
|
||||
- **core:** Avoid using applicationContext.init() in the RunContext ([fb9691d67](https://github.com/kestra-io/kestra/commit/fb9691d67))
|
||||
- **core:** Remove deprecated RunContext.render(Property) methods ([e16505269](https://github.com/kestra-io/kestra/commit/e16505269))
|
||||
- **ci:** Add workflow to release all plugins ([5330aa0e8](https://github.com/kestra-io/kestra/commit/5330aa0e8))
|
||||
- **ui:** Improvement in Welcome Page. ([#6077](https://github.com/kestra-io/kestra/pull/6077))
|
||||
- **translations:** Auto generate values for languages other than english ([7171c5697](https://github.com/kestra-io/kestra/commit/7171c5697))
|
||||
- **ui:** Amend tags design on blueprints section ([#6229](https://github.com/kestra-io/kestra/pull/6229))
|
||||
- **ui:** Automatically add namespace filter where needed ([#6296](https://github.com/kestra-io/kestra/pull/6296))
|
||||
- **ui:** Make confirmation dialogs properly render markdown content ([#5986](https://github.com/kestra-io/kestra/pull/5986))
|
||||
- **ui:** Initial work on debug expression sidebar resizing on outputs page ([#5878](https://github.com/kestra-io/kestra/pull/5878))
|
||||
- **ui:** Initial work on output icons issue ([#5746](https://github.com/kestra-io/kestra/pull/5746))
|
||||
- Upgrade vue-router to 4.5.0 ([#6298](https://github.com/kestra-io/kestra/pull/6298))
|
||||
- **ui:** Remove default editor outline ([#6303](https://github.com/kestra-io/kestra/pull/6303))
|
||||
- **ui:** Uniform the height of cards on main dashboard ([#6213](https://github.com/kestra-io/kestra/pull/6213))
|
||||
- **ui:** Clean up of welcome page usage of markdown files ([#6315](https://github.com/kestra-io/kestra/pull/6315))
|
||||
- **translations:** Auto generate values for languages other than english ([b63c2361c](https://github.com/kestra-io/kestra/commit/b63c2361c))
|
||||
- **ui:** Uniform log line buttons styling ([#6254](https://github.com/kestra-io/kestra/pull/6254))
|
||||
- **core:** Remove Flow source auto-generation ([985a10610](https://github.com/kestra-io/kestra/commit/985a10610))
|
||||
- **translations:** Align keys with english ([#6324](https://github.com/kestra-io/kestra/pull/6324))
|
||||
- **ui:** Add tooltips for filter section buttons ([#5942](https://github.com/kestra-io/kestra/pull/5942))
|
||||
- **ui:** Improve bulk actions design in the executions listing ([#6240](https://github.com/kestra-io/kestra/pull/6240))
|
||||
- **ui:** Add scrolling to totals chart legend if more than 4 items present ([#5971](https://github.com/kestra-io/kestra/pull/5971))
|
||||
- **ui:** Respect date format form setting inside filter label ([#6335](https://github.com/kestra-io/kestra/pull/6335))
|
||||
- **ui:** Prevent text wrap inside trigger id column ([#6336](https://github.com/kestra-io/kestra/pull/6336))
|
||||
- **ui:** Vertically center content in flows table rows ([#6330](https://github.com/kestra-io/kestra/pull/6330))
|
||||
- **ui:** Improve the styling of boolean buttons on inputs form ([#6055](https://github.com/kestra-io/kestra/pull/6055))
|
||||
- **ui:** Initial work on improving display of topology ([#5606](https://github.com/kestra-io/kestra/pull/5606))
|
||||
- **jdbc:** Remove not used method ([6a953d194](https://github.com/kestra-io/kestra/commit/6a953d194))
|
||||
- **ui:** Remove unused files ([#6348](https://github.com/kestra-io/kestra/pull/6348))
|
||||
- **ui:** Improve text label in filter bar ([#6350](https://github.com/kestra-io/kestra/pull/6350))
|
||||
- **ci:** Remove generation of latest-full tag ([6bb521f4e](https://github.com/kestra-io/kestra/commit/6bb521f4e))
|
||||
- **core:** Add unit test for if nested in parallel ([c6a7c1270](https://github.com/kestra-io/kestra/commit/c6a7c1270))
|
||||
- **ui:** Amend font size in tables ([#6363](https://github.com/kestra-io/kestra/pull/6363))
|
||||
- **ui:** Prevent opening flow trigger details on row double click ([#6354](https://github.com/kestra-io/kestra/pull/6354))
|
||||
- ***:** Bump node version used in project ([#6327](https://github.com/kestra-io/kestra/pull/6327))
|
||||
- **docs:** Switch link to good first issues in readme file ([a78566fc5](https://github.com/kestra-io/kestra/commit/a78566fc5))
|
||||
- **core:** Avoid serialzing the value multiple time ([3cd0c7fbe](https://github.com/kestra-io/kestra/commit/3cd0c7fbe))
|
||||
- **ui:** Respect system theme for editor ([#6376](https://github.com/kestra-io/kestra/pull/6376))
|
||||
- **ui:** Amend description color of system namespace ([#6382](https://github.com/kestra-io/kestra/pull/6382))
|
||||
- **ui:** Temporarily disable filters bar highlighting ([e9be78947](https://github.com/kestra-io/kestra/commit/e9be78947))
|
||||
- **translations:** Auto generate values for languages other than english ([3ddfe5e8d](https://github.com/kestra-io/kestra/commit/3ddfe5e8d))
|
||||
- **ui:** Amended the css for filter bar width ([e9033462c](https://github.com/kestra-io/kestra/commit/e9033462c))
|
||||
- **translations:** Auto generate values for languages other than english ([c867db8ae](https://github.com/kestra-io/kestra/commit/c867db8ae))
|
||||
- **translations:** Auto generate values for languages other than english ([41e64cc04](https://github.com/kestra-io/kestra/commit/41e64cc04))
|
||||
- **ui:** Removing recent filter functionality ([#6420](https://github.com/kestra-io/kestra/pull/6420))
|
||||
- **translations:** Auto generate values for languages other than english ([019097465](https://github.com/kestra-io/kestra/commit/019097465))
|
||||
- **ui:** Improve filters expanding ([#6415](https://github.com/kestra-io/kestra/pull/6415))
|
||||
- **ui:** Prevent applying same filter multiple times ([#6219](https://github.com/kestra-io/kestra/pull/6219))
|
||||
- Update ESlint to v9 (next major) ([#6389](https://github.com/kestra-io/kestra/pull/6389))
|
||||
- **deps-dev:** Bump typescript-eslint from 8.17.0 to 8.18.0 in /ui ([#6432](https://github.com/kestra-io/kestra/pull/6432))
|
||||
- **ui:** Align with new version of linter ([2358bf43f](https://github.com/kestra-io/kestra/commit/2358bf43f))
|
||||
- **core:** Refactor SecretService ([1267e9b02](https://github.com/kestra-io/kestra/commit/1267e9b02))
|
||||
- **ui:** Amend filter selection ([25818878e](https://github.com/kestra-io/kestra/commit/25818878e))
|
||||
- **translations:** Add missing keys ([3f104ae9b](https://github.com/kestra-io/kestra/commit/3f104ae9b))
|
||||
- **translations:** Auto generate values for languages other than english ([909015467](https://github.com/kestra-io/kestra/commit/909015467))
|
||||
- **ui:** Improve design of the flow overview page with no executions ([#6446](https://github.com/kestra-io/kestra/pull/6446))
|
||||
- **translations:** Auto generate values for languages other than english ([aae4e6b93](https://github.com/kestra-io/kestra/commit/aae4e6b93))
|
||||
- Exclude liveness/readiness health checks from access logs ([#6453](https://github.com/kestra-io/kestra/pull/6453))
|
||||
- **ui:** Use global scope for translations throughout the product ([#6466](https://github.com/kestra-io/kestra/pull/6466))
|
||||
- **ui:** Add clearable property to select fields on inputs form ([#6477](https://github.com/kestra-io/kestra/pull/6477))
|
||||
- Avoid global constants (use window instead) ([#6478](https://github.com/kestra-io/kestra/pull/6478))
|
||||
- **ui:** Rework the file structure in filters section ([#6452](https://github.com/kestra-io/kestra/pull/6452))
|
||||
- **translations:** Auto generate values for languages other than english ([3fb12a66d](https://github.com/kestra-io/kestra/commit/3fb12a66d))
|
||||
- **translations:** Handle filters key translation ([#6479](https://github.com/kestra-io/kestra/pull/6479))
|
||||
- **ui:** Improve passing of props to filter component ([#6483](https://github.com/kestra-io/kestra/pull/6483))
|
||||
- **translations:** Auto generate values for languages other than english ([c57984709](https://github.com/kestra-io/kestra/commit/c57984709))
|
||||
- **ui:** Improve the styling of error component on executions overview ([#6465](https://github.com/kestra-io/kestra/pull/6465))
|
||||
- **ui:** Autmatically open date picker if absolute date is selected as type ([#6484](https://github.com/kestra-io/kestra/pull/6484))
|
||||
- **ui:** Amend scope lables on dasboard filters ([#6485](https://github.com/kestra-io/kestra/pull/6485))
|
||||
- **ui:** Re-work same filter applying multiple times ([6f3aff146](https://github.com/kestra-io/kestra/commit/6f3aff146))
|
||||
- **ui:** Make tab arrows hidden when there is no need for them ([#6486](https://github.com/kestra-io/kestra/pull/6486))
|
||||
- **ui:** Complete the audit log filtering options ([#6496](https://github.com/kestra-io/kestra/pull/6496))
|
||||
- **translations:** Auto generate values for languages other than english ([aa8588c3b](https://github.com/kestra-io/kestra/commit/aa8588c3b))
|
||||
- **ui:** Improved filter usage throughout the product ([#6513](https://github.com/kestra-io/kestra/pull/6513))
|
||||
- **translations:** Auto generate values for languages other than english ([cdc30080f](https://github.com/kestra-io/kestra/commit/cdc30080f))
|
||||
- **ui:** Implement new filtering system where it was missing ([#6531](https://github.com/kestra-io/kestra/pull/6531))
|
||||
- **translations:** Auto generate values for languages other than english ([5b6b501bc](https://github.com/kestra-io/kestra/commit/5b6b501bc))
|
||||
- **ui:** Expand doughnut chart if there is no legend shown ([804c3b2d5](https://github.com/kestra-io/kestra/commit/804c3b2d5))
|
||||
- **ui:** Make the entire row clickable on the gantt page ([#6539](https://github.com/kestra-io/kestra/pull/6539))
|
||||
- **ui:** Improve behavior on changing theme or language ([#6520](https://github.com/kestra-io/kestra/pull/6520))
|
||||
- **ui:** Lint code properly for ci to pass ([4a94b2237](https://github.com/kestra-io/kestra/commit/4a94b2237))
|
||||
- **ui:** Rename flow editor tab to edit ([#6552](https://github.com/kestra-io/kestra/pull/6552))
|
||||
- **ui:** Limit accepted file types for flow import ([#6550](https://github.com/kestra-io/kestra/pull/6550))
|
||||
- **ui:** Improvement to blueprints inside flow editor tab ([#6548](https://github.com/kestra-io/kestra/pull/6548))
|
||||
- **ui:** Executions in progress link is taking proper filters into account ([#6556](https://github.com/kestra-io/kestra/pull/6556))
|
||||
- **ui:** Show artwork on flow triggers page instead of locking it ([#6555](https://github.com/kestra-io/kestra/pull/6555))
|
||||
- **translations:** Auto generate values for languages other than english ([f0a15cdae](https://github.com/kestra-io/kestra/commit/f0a15cdae))
|
||||
- **ui:** Handle creation of nested folders properly ([#6554](https://github.com/kestra-io/kestra/pull/6554))
|
||||
- **ui:** Prevent adding filter parameters if decode prop is passed as false ([#6563](https://github.com/kestra-io/kestra/pull/6563))
|
||||
- **ui:** Replace language right after saving changes ([#6566](https://github.com/kestra-io/kestra/pull/6566))
|
||||
- **ui:** Prevent display of file helpers if there is no file ([#6568](https://github.com/kestra-io/kestra/pull/6568))
|
||||
- **ui:** Tweak font size for tag component ([8357e0e23](https://github.com/kestra-io/kestra/commit/8357e0e23))
|
||||
- **ui:** Introduce new filter bar on namespace kv page ([#6570](https://github.com/kestra-io/kestra/pull/6570))
|
||||
- **ui:** Prevent ability to add multiple filters on second try ([#6573](https://github.com/kestra-io/kestra/pull/6573))
|
||||
- **ui:** Always show flow revision column on executions listing ([807a44d18](https://github.com/kestra-io/kestra/commit/807a44d18))
|
||||
- **ui:** Introduce horizontal scroll to cascader items ([96d8239b5](https://github.com/kestra-io/kestra/commit/96d8239b5))
|
||||
- **ui:** Add limit to environment name length ([#6597](https://github.com/kestra-io/kestra/pull/6597))
|
||||
- **ui:** Add a horizontal scroll bar to editor file tree ([#6598](https://github.com/kestra-io/kestra/pull/6598))
|
||||
- **translations:** Auto generate values for languages other than english ([27f1430ff](https://github.com/kestra-io/kestra/commit/27f1430ff))
|
||||
- **ui:** Add header title for column on triggers page ([#6608](https://github.com/kestra-io/kestra/pull/6608))
|
||||
- **tasks:** Improve unit test of http tasks ([9e643dcc4](https://github.com/kestra-io/kestra/commit/9e643dcc4))
|
||||
- **core:** Tiny perf improvement in MapUtils.merge() ([92ff55751](https://github.com/kestra-io/kestra/commit/92ff55751))
|
||||
- **build:** Increase Gradle memory limits ([2307b2452](https://github.com/kestra-io/kestra/commit/2307b2452))
|
||||
- **translations:** Auto generate values for languages other than english ([4dbf938de](https://github.com/kestra-io/kestra/commit/4dbf938de))
|
||||
- **core:** Move builder default on TableColumnDescriptor ([4fa6adc9c](https://github.com/kestra-io/kestra/commit/4fa6adc9c))
|
||||
- **test:** Minor pebble error ([47d2b0938](https://github.com/kestra-io/kestra/commit/47d2b0938))
|
||||
- **translations:** Auto generate values for languages other than english ([a1238a947](https://github.com/kestra-io/kestra/commit/a1238a947))
|
||||
- **translations:** Auto generate values for languages other than english ([4e543a29c](https://github.com/kestra-io/kestra/commit/4e543a29c))
|
||||
- **ui:** Add utility method extratFileNameFromContentDisposition ([16a7e06c5](https://github.com/kestra-io/kestra/commit/16a7e06c5))
|
||||
- Remove an unwanted comment ([2a56ba88c](https://github.com/kestra-io/kestra/commit/2a56ba88c))
|
||||
- **docs:** Split pause title and description ([#6733](https://github.com/kestra-io/kestra/pull/6733))
|
||||
- **core:** Reduce log level of property validation ([708c1127c](https://github.com/kestra-io/kestra/commit/708c1127c))
|
||||
- **ui:** Amend passing of disabled property to no code editor ([33c5cb507](https://github.com/kestra-io/kestra/commit/33c5cb507))
|
||||
- **translations:** Amend translation key/value pairs ([#6788](https://github.com/kestra-io/kestra/pull/6788))
|
||||
- **translations:** Auto generate values for languages other than english ([3c6aa6980](https://github.com/kestra-io/kestra/commit/3c6aa6980))
|
||||
- **ui:** Amend width of execute flow inputs section ([#6720](https://github.com/kestra-io/kestra/pull/6720))
|
||||
- **ui:** Mark taskruns with multiple attemps in gantt view ([#6721](https://github.com/kestra-io/kestra/pull/6721))
|
||||
- **ui:** Amend dialog close button styling ([fdbe16387](https://github.com/kestra-io/kestra/commit/fdbe16387))
|
||||
- **ui:** Add empty view on flow concurrency page ([#6640](https://github.com/kestra-io/kestra/pull/6640))
|
||||
- **translations:** Auto generate values for languages other than english ([46c3e3ff7](https://github.com/kestra-io/kestra/commit/46c3e3ff7))
|
||||
- **translations:** Auto generate values for languages other than english ([3435e345e](https://github.com/kestra-io/kestra/commit/3435e345e))
|
||||
- **ui:** Mark places where we need to replace old charts with the new ones ([#6623](https://github.com/kestra-io/kestra/pull/6623))
|
||||
- **ui:** Change filter value by clicking on already selected one ([#6705](https://github.com/kestra-io/kestra/pull/6705))
|
||||
- **test:** Add required attributes for e2e test ([#6797](https://github.com/kestra-io/kestra/pull/6797))
|
||||
- **translations:** Auto generate values for languages other than english ([e037e548b](https://github.com/kestra-io/kestra/commit/e037e548b))
|
||||
- **ui:** Show status label on dialog ([96780a976](https://github.com/kestra-io/kestra/commit/96780a976))
|
||||
- **ui:** Amend flow export method ([#6835](https://github.com/kestra-io/kestra/pull/6835))
|
||||
- **translations:** Auto generate values for languages other than english ([a01170572](https://github.com/kestra-io/kestra/commit/a01170572))
|
||||
- **ui:** Handle task dict type fields ([#6884](https://github.com/kestra-io/kestra/pull/6884))
|
||||
- **ui:** Make sure input pair component updates only what's needed ([#6892](https://github.com/kestra-io/kestra/pull/6892))
|
||||
- **translations:** Auto generate values for languages other than english ([c6d69762c](https://github.com/kestra-io/kestra/commit/c6d69762c))
|
||||
- **cli:** Invalid description on worker thread ([61e0668ad](https://github.com/kestra-io/kestra/commit/61e0668ad))
|
||||
- **ui:** Start product tour by clicking on first car on welcome page ([#6934](https://github.com/kestra-io/kestra/pull/6934))
|
||||
- **ui:** Start product tour by clicking on first card on welcome page ([#6935](https://github.com/kestra-io/kestra/pull/6935))
|
||||
- **build:** Fix .plugins file ([01e565f3c](https://github.com/kestra-io/kestra/commit/01e565f3c))
|
||||
- **build:** Fix release-plugins script ([83e99edcf](https://github.com/kestra-io/kestra/commit/83e99edcf))
|
||||
- **script:** Update release-plugins to support pushReleaseVersionBranch ([4d7d8e008](https://github.com/kestra-io/kestra/commit/4d7d8e008))
|
||||
- **ui:** Improvements of welcome page ([#6938](https://github.com/kestra-io/kestra/pull/6938))
|
||||
- **translations:** Auto generate values for languages other than english ([cb7ee6a0b](https://github.com/kestra-io/kestra/commit/cb7ee6a0b))
|
||||
- **cli:** Improve CLI help messages ([#6920](https://github.com/kestra-io/kestra/pull/6920))
|
||||
- **ui:** Refactor the namespace flows ([a69f8b94f](https://github.com/kestra-io/kestra/commit/a69f8b94f))
|
||||
- **translations:** Auto generate values for languages other than english ([4aa0a57e0](https://github.com/kestra-io/kestra/commit/4aa0a57e0))
|
||||
- **translations:** Auto generate values for languages other than english ([2bca260a3](https://github.com/kestra-io/kestra/commit/2bca260a3))
|
||||
- **ui:** Amend plus button action on flow editor topology ([#6983](https://github.com/kestra-io/kestra/pull/6983))
|
||||
- **translations:** Auto generate values for languages other than english ([32da58eee](https://github.com/kestra-io/kestra/commit/32da58eee))
|
||||
- **translations:** Auto generate values for languages other than english ([9c56ffa91](https://github.com/kestra-io/kestra/commit/9c56ffa91))
|
||||
- Remove plugin-langchain ([dd8a45f42](https://github.com/kestra-io/kestra/commit/dd8a45f42))
|
||||
- **version:** Update to version 'v0.21.0-rc0-SNAPSHOT'. ([f4fdfc250](https://github.com/kestra-io/kestra/commit/f4fdfc250))
|
||||
- **ui:** Move apps link in left menu just below the flows ([#7063](https://github.com/kestra-io/kestra/pull/7063))
|
||||
- **ui:** Properly check the existence of fields inside schema ([aa24c888a](https://github.com/kestra-io/kestra/commit/aa24c888a))
|
||||
- **translations:** Auto generate values for languages other than english ([804ff6a81](https://github.com/kestra-io/kestra/commit/804ff6a81))
|
||||
- **version:** Update to version 'v0.21.0-rc1-SNAPSHOT' ([86aec88de](https://github.com/kestra-io/kestra/commit/86aec88de))
|
||||
- **ui:** Properly pass a prop related to saved searches ([6afe5ff41](https://github.com/kestra-io/kestra/commit/6afe5ff41))
|
||||
- Version 0.21.0-rc2-SNAPSHOT ([d74a31ba7](https://github.com/kestra-io/kestra/commit/d74a31ba7))
|
||||
- Version 0.21.0 ([aca5a9ff4](https://github.com/kestra-io/kestra/commit/aca5a9ff4))
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- **runner tests:** Add logs to track race condition ([#6455](https://github.com/kestra-io/kestra/pull/6455))
|
||||
- Add a story & tests for filter labels ([#6526](https://github.com/kestra-io/kestra/pull/6526))
|
||||
- **core:** Add coverage on http logger ([62badb1e5](https://github.com/kestra-io/kestra/commit/62badb1e5))
|
||||
- **core:** Request test use an internal https server to be stable ([bccd95345](https://github.com/kestra-io/kestra/commit/bccd95345))
|
||||
- **core:** Add configurable timeout on ExecuteFlow ([317284dfe](https://github.com/kestra-io/kestra/commit/317284dfe))
|
||||
|
||||
### 🎨 Styles
|
||||
|
||||
- **ui:** Update SideBar link styles to match design ([6a5ec8dcf](https://github.com/kestra-io/kestra/commit/6a5ec8dcf))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Update workflow docker ([a246ac38f](https://github.com/kestra-io/kestra/commit/a246ac38f))
|
||||
- Update workflow docker ([c33d08afd](https://github.com/kestra-io/kestra/commit/c33d08afd))
|
||||
- Fix workflow docker ([4e4ab80b2](https://github.com/kestra-io/kestra/commit/4e4ab80b2))
|
||||
- Fix workflow docker for all plugins ([f0d5d4b93](https://github.com/kestra-io/kestra/commit/f0d5d4b93))
|
||||
- Fix runner on release workflows ([6919848ab](https://github.com/kestra-io/kestra/commit/6919848ab))
|
||||
- Fix release workflows ([41149a83b](https://github.com/kestra-io/kestra/commit/41149a83b))
|
||||
|
||||
### 📖 Commits
|
||||
|
||||
- 987f491: keep active state on hover (Bart Ledoux)
|
||||
- 828d9a7: add taskrun.iteration (#6723) (AJ Emerich) [#6723](https://github.com/kestra-io/kestra/pull/6723)
|
||||
- 0c0ff37: fix(core, ui): send a "start" event to be sure the UI receive the SSE (Loïc Mathieu) [#6731](https://github.com/kestra-io/kestra/pull/6731)
|
||||
- fbfab90: fix(#6745) vue flow needs a height on the container 🥸 (Bart Ledoux)
|
||||
- 717d556: feat(webserver, ui): avoid cancelled SSE connection from following exec (Loïc Mathieu) [#6738](https://github.com/kestra-io/kestra/pull/6738)
|
||||
- 51088c8: fix logline css variables (Bart Ledoux)
|
||||
- 7f048af: add full logline stories (Bart Ledoux)
|
||||
- Define the langchain4j ollama, openai & gemini plugins in the .plugin file list (#6813) [#6813](https://github.com/kestra-io/kestra/pull/6813) ([aeSouid](https://github.com/kestra-io/kestra/commit/1ddb544c3662e9a14cbabfdbe4f9d673a4e1025e))
|
||||
- aa869eb: closes https://github.com/kestra-io/kestra/issues/6814 (Anna Geller)
|
||||
- 4e3ed33: feat(ui, webserver): rename "Change status" to "Change state" and enhance the infos (Loïc Mathieu) [#6799](https://github.com/kestra-io/kestra/pull/6799)
|
||||
- 7cf4955: feat(core, jdbc): change the state of a subflow restart parent execution (Loïc Mathieu) [#6799](https://github.com/kestra-io/kestra/pull/6799)
|
||||
- 2359c4f: fix(core, jdbc): Count task (Loïc Mathieu) [#6952](https://github.com/kestra-io/kestra/pull/6952)
|
||||
- db84595: (docs): add custom dashboard in app documentation (AJ Emerich) [#6988](https://github.com/kestra-io/kestra/pull/6988)
|
||||
- b8bc50f: fix flare effect (Bart Ledoux)
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
|
||||
#### Git Plugin: **Default Branch Name Changed**
|
||||
|
||||
The default branch within Git tasks has been renamed from `kestra` to `main` ([PR #98](https://github.com/kestra-io/plugin-git/pull/98)). Make sure to update any workflows that implicitly rely on the former default branch.
|
||||
|
||||
#### Secrets: **Exception Thrown on Missing Secret**
|
||||
|
||||
Fetching a non-existing secret using the `secret()` function now throws an exception instead of returning `null`, aligning the open-source behavior with the behavior in the Enterprise Edition ([PR #6495](https://github.com/kestra-io/kestra/pull/6495)).
|
||||
|
||||
#### Change State: **Restart Downstream Task Runs**
|
||||
|
||||
Manually changing a task run's status from `Failed` to a non-failed state (e.g. `Success`) via the Change state interface now restarts all downstream task runs, including subflows ([PR #6799](https://github.com/kestra-io/kestra/pull/6799)).
|
||||
|
||||
#### Restarting Parent Flow with Failed Subflow
|
||||
|
||||
When restarting an execution, `Subflow` or `ForEachItem` tasks now restart the existing failed subflow execution rather than creating a new one. This behavior is configurable via the new `restartBehavior` enum property; setting it to `NEW_EXECUTION` retains the previous behavior ([PR #6799](https://github.com/kestra-io/kestra/pull/6799); [Issue #6722](https://github.com/kestra-io/kestra/issues/6722)). A `system.restarted: true` label is added during restart for tracking, and the underlying subflow execution storage table is retained to avoid migration issues (scheduled for removal in v0.22).
|
||||
|
||||
#### Script Tasks: **STDERR Logged at ERROR Level**
|
||||
|
||||
Script tasks now log output sent to `stderr` at the ERROR level instead of WARNING ([PR #6383](https://github.com/kestra-io/kestra/pull/6383); [Issue #190](https://github.com/kestra-io/plugin-scripts/issues/190)).
|
||||
|
||||
#### Flows Created Before v0.9: **Redeployment Required**
|
||||
|
||||
Flows created before v0.9 and not updated since require editing or redeployment due to changes in source auto-generation ([PR #6264](https://github.com/kestra-io/kestra/pull/6264)).
|
||||
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Loïc Mathieu ([@loicmathieu](http://github.com/loicmathieu))
|
||||
- Ludovic DEHON ([@tchiotludo](http://github.com/tchiotludo))
|
||||
- Bart Ledoux <bledoux@kestra.io>
|
||||
- Miloš Paunović ([@MilosPaunovic](http://github.com/MilosPaunovic))
|
||||
- Piyush Bhaskar ([@Piyush-r-bhaskar](http://github.com/Piyush-r-bhaskar))
|
||||
- Florian Hussonnois ([@fhussonnois](http://github.com/fhussonnois))
|
||||
- Brian-mulier-p ([@brian-mulier-p](http://github.com/brian-mulier-p))
|
||||
- GitHub Action ([@Github-Action-Bot](http://github.com/Github-Action-Bot))
|
||||
- AJ Emerich ([@aj-emerich](http://github.com/aj-emerich))
|
||||
- YannC ([@Skraye](http://github.com/Skraye))
|
||||
- Nicolas K. <nk_mikmak@hotmail.com>
|
||||
- Rajat Singh <rs2382001@gmail.com>
|
||||
- Barthélémy Ledoux <ledouxb@me.com>
|
||||
- Rajatsingh23 ([@rajatsingh23](http://github.com/rajatsingh23))
|
||||
- Anna Geller <anna.m.geller@gmail.com>
|
||||
- Yuri <1969yuri1969@gmail.com>
|
||||
- Mathieu Gabelle ([@mgabelle](http://github.com/mgabelle))
|
||||
- Shruti Mantri <shruti1810@gmail.com>
|
||||
- Aabhas Sao ([@aabhas-sao](http://github.com/aabhas-sao))
|
||||
- CoderKill ([@coderkill](http://github.com/coderkill))
|
||||
- Saumya Gaur ([@SaumyaG1318](http://github.com/SaumyaG1318))
|
||||
- Hashim Khalifa ([@hashimzs](http://github.com/hashimzs))
|
||||
- Malay Dewangan ([@Malaydewangan09](http://github.com/Malaydewangan09))
|
||||
- NKwiatkowski <nkwiatkowski@kestra.io>
|
||||
- Rajarajan <rajarajangunapal985@gmail.com>
|
||||
- Ruturaj Dhakane ([@rd-99](http://github.com/rd-99))
|
||||
- Sayed Qassim ([@SayedQassim](http://github.com/SayedQassim))
|
||||
- Maheshwara Sampath ([@sampath24-ss](http://github.com/sampath24-ss))
|
||||
- Shreeup <shree912@yahoo.com>
|
||||
- Arpit Gupta ([@arpitgupta-ITT](http://github.com/arpitgupta-ITT))
|
||||
- Yoann Vernageau ([@yvrng](http://github.com/yvrng))
|
||||
- Marco Sabatini ([@MarcoSaba](http://github.com/MarcoSaba))
|
||||
- Kratos ([@kratosmy](http://github.com/kratosmy))
|
||||
- Michascant ([@MichaScant](http://github.com/MichaScant))
|
||||
- Tejas Patil ([@tejas2292](http://github.com/tejas2292))
|
||||
- Shivam ([@shivam221098](http://github.com/shivam221098))
|
||||
- ANKIT KUMAR <ankit1842kumar@gmail.com>
|
||||
- Sanketmagar2001 ([@sanketmagar2001](http://github.com/sanketmagar2001))
|
||||
- Nitin Kumar Pal ([@nitinkumarpals](http://github.com/nitinkumarpals))
|
||||
- Yerin Lee ([@DVUN716](http://github.com/DVUN716))
|
||||
- Manoj Balaraj ([@ManojTauro](http://github.com/ManojTauro))
|
||||
- Will Russell <wrussell@kestra.io>
|
||||
- Abhishek Pawar ([@abhishekpawar1060](http://github.com/abhishekpawar1060))
|
||||
- Rohit Ghumare ([@rohitg00](http://github.com/rohitg00))
|
||||
- OsmaneTKT ([@osmaneTKT](http://github.com/osmaneTKT))
|
||||
- Bardan Putra Prananto <bppdanto@gmail.com>
|
||||
- Pphy03 ([@pphy03](http://github.com/pphy03))
|
||||
- Ian Cheng ([@chengtc-dev](http://github.com/chengtc-dev))
|
||||
- Nitin Bisht ([@nitsbat](http://github.com/nitsbat))
|
||||
- Joe Celaster ([@JoeCelaster](http://github.com/JoeCelaster))
|
||||
- Morri12 ([@morri12](http://github.com/morri12))
|
||||
- Ines Qian ([@inesqyx](http://github.com/inesqyx))
|
||||
@@ -39,7 +39,7 @@ public class FlowValidateCommand extends AbstractValidateCommand {
|
||||
Flow flow = (Flow) object;
|
||||
List<String> warnings = new ArrayList<>();
|
||||
warnings.addAll(flowService.deprecationPaths(flow).stream().map(deprecation -> deprecation + " is deprecated").toList());
|
||||
warnings.addAll(flowService.warnings(flow));
|
||||
warnings.addAll(flowService.warnings(flow, this.tenantId));
|
||||
return warnings;
|
||||
},
|
||||
(Object object) -> {
|
||||
|
||||
@@ -3,7 +3,7 @@ package io.kestra.cli.commands.plugins;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import io.kestra.cli.AbstractCommand;
|
||||
import io.kestra.cli.plugins.PluginDownloader;
|
||||
import io.kestra.cli.plugins.RepositoryConfig;
|
||||
import io.kestra.cli.plugins.MavenPluginRepositoryConfig;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import picocli.CommandLine;
|
||||
@@ -59,13 +59,13 @@ public class PluginInstallCommand extends AbstractCommand {
|
||||
.forEach(throwConsumer(s -> {
|
||||
URIBuilder uriBuilder = new URIBuilder(s);
|
||||
|
||||
RepositoryConfig.RepositoryConfigBuilder builder = RepositoryConfig.builder()
|
||||
var builder = MavenPluginRepositoryConfig.builder()
|
||||
.id(IdUtils.create());
|
||||
|
||||
if (uriBuilder.getUserInfo() != null) {
|
||||
int index = uriBuilder.getUserInfo().indexOf(":");
|
||||
|
||||
builder.basicAuth(new RepositoryConfig.BasicAuth(
|
||||
builder.basicAuth(new MavenPluginRepositoryConfig.BasicAuth(
|
||||
uriBuilder.getUserInfo().substring(0, index),
|
||||
uriBuilder.getUserInfo().substring(index + 1)
|
||||
));
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.kestra.cli.plugins;
|
||||
|
||||
import io.micronaut.context.annotation.ConfigurationProperties;
|
||||
import io.micronaut.context.annotation.EachProperty;
|
||||
import io.micronaut.context.annotation.Parameter;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Builder
|
||||
@EachProperty("kestra.plugins.repositories")
|
||||
public record MavenPluginRepositoryConfig(
|
||||
@Parameter
|
||||
String id,
|
||||
String url,
|
||||
|
||||
@Nullable
|
||||
BasicAuth basicAuth
|
||||
) {
|
||||
|
||||
@Builder
|
||||
@ConfigurationProperties("basic-auth")
|
||||
public record BasicAuth(
|
||||
String username,
|
||||
String password
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
import org.eclipse.aether.RepositorySystem;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
@@ -31,18 +30,17 @@ import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class PluginDownloader {
|
||||
private final List<RepositoryConfig> repositoryConfigs;
|
||||
private final List<MavenPluginRepositoryConfig> repositoryConfigs;
|
||||
private final RepositorySystem system;
|
||||
private final RepositorySystemSession session;
|
||||
|
||||
@Inject
|
||||
public PluginDownloader(
|
||||
List<RepositoryConfig> repositoryConfigs,
|
||||
List<MavenPluginRepositoryConfig> repositoryConfigs,
|
||||
@Nullable @Value("${kestra.plugins.local-repository-path}") String localRepositoryPath
|
||||
) {
|
||||
this.repositoryConfigs = repositoryConfigs;
|
||||
@@ -50,7 +48,7 @@ public class PluginDownloader {
|
||||
this.session = repositorySystemSession(system, localRepositoryPath);
|
||||
}
|
||||
|
||||
public void addRepository(RepositoryConfig repositoryConfig) {
|
||||
public void addRepository(MavenPluginRepositoryConfig repositoryConfig) {
|
||||
this.repositoryConfigs.add(repositoryConfig);
|
||||
}
|
||||
|
||||
@@ -69,15 +67,15 @@ public class PluginDownloader {
|
||||
.stream()
|
||||
.map(repositoryConfig -> {
|
||||
var build = new RemoteRepository.Builder(
|
||||
repositoryConfig.getId(),
|
||||
repositoryConfig.id(),
|
||||
"default",
|
||||
repositoryConfig.getUrl()
|
||||
repositoryConfig.url()
|
||||
);
|
||||
|
||||
if (repositoryConfig.getBasicAuth() != null) {
|
||||
if (repositoryConfig.basicAuth() != null) {
|
||||
var authenticationBuilder = new AuthenticationBuilder();
|
||||
authenticationBuilder.addUsername(repositoryConfig.getBasicAuth().getUsername());
|
||||
authenticationBuilder.addPassword(repositoryConfig.getBasicAuth().getPassword());
|
||||
authenticationBuilder.addUsername(repositoryConfig.basicAuth().username());
|
||||
authenticationBuilder.addPassword(repositoryConfig.basicAuth().password());
|
||||
|
||||
build.setAuthentication(authenticationBuilder.build());
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package io.kestra.cli.plugins;
|
||||
|
||||
import io.micronaut.context.annotation.EachProperty;
|
||||
import io.micronaut.context.annotation.Parameter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@EachProperty("kestra.plugins.repositories")
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class RepositoryConfig {
|
||||
String id;
|
||||
|
||||
String url;
|
||||
|
||||
BasicAuth basicAuth;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class BasicAuth {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
|
||||
public RepositoryConfig(@Parameter String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import io.kestra.core.models.validations.ModelValidator;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.serializers.YamlParser;
|
||||
import io.kestra.core.services.FlowListenersInterface;
|
||||
import io.kestra.core.services.PluginDefaultService;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.micronaut.scheduling.io.watch.FileWatchConfiguration;
|
||||
@@ -36,6 +37,9 @@ public class FileChangedEventListener {
|
||||
@Inject
|
||||
private FlowRepositoryInterface flowRepositoryInterface;
|
||||
|
||||
@Inject
|
||||
private PluginDefaultService pluginDefaultService;
|
||||
|
||||
@Inject
|
||||
private YamlParser yamlParser;
|
||||
|
||||
@@ -64,7 +68,7 @@ public class FileChangedEventListener {
|
||||
|
||||
public void startListeningFromConfig() throws IOException, InterruptedException {
|
||||
if (fileWatchConfiguration != null && fileWatchConfiguration.isEnabled()) {
|
||||
this.flowFilesManager = new LocalFlowFileWatcher(flowRepositoryInterface);
|
||||
this.flowFilesManager = new LocalFlowFileWatcher(flowRepositoryInterface, pluginDefaultService);
|
||||
List<Path> paths = fileWatchConfiguration.getPaths();
|
||||
this.setup(paths);
|
||||
|
||||
@@ -107,7 +111,6 @@ public class FileChangedEventListener {
|
||||
} else {
|
||||
log.info("File watching is disabled.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void startListening(List<Path> paths) throws IOException, InterruptedException {
|
||||
@@ -118,60 +121,64 @@ public class FileChangedEventListener {
|
||||
WatchKey key;
|
||||
while ((key = watchService.take()) != null) {
|
||||
for (WatchEvent<?> watchEvent : key.pollEvents()) {
|
||||
WatchEvent.Kind<?> kind = watchEvent.kind();
|
||||
Path entry = (Path) watchEvent.context();
|
||||
try {
|
||||
WatchEvent.Kind<?> kind = watchEvent.kind();
|
||||
Path entry = (Path) watchEvent.context();
|
||||
|
||||
if (entry.toString().endsWith(".yml") || entry.toString().endsWith(".yaml")) {
|
||||
if (entry.toString().endsWith(".yml") || entry.toString().endsWith(".yaml")) {
|
||||
|
||||
if (kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
if (kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
|
||||
Path filePath = ((Path) key.watchable()).resolve(entry);
|
||||
if (Files.isDirectory(filePath)) {
|
||||
loadFlowsFromFolder(filePath);
|
||||
} else {
|
||||
Path filePath = ((Path) key.watchable()).resolve(entry);
|
||||
if (Files.isDirectory(filePath)) {
|
||||
loadFlowsFromFolder(filePath);
|
||||
} else {
|
||||
|
||||
try {
|
||||
String content = Files.readString(filePath, Charset.defaultCharset());
|
||||
try {
|
||||
String content = Files.readString(filePath, Charset.defaultCharset());
|
||||
|
||||
Optional<Flow> flow = parseFlow(content, entry);
|
||||
if (flow.isPresent()) {
|
||||
if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
// Check if we already have a file with the given path
|
||||
if (flows.stream().anyMatch(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()))) {
|
||||
Optional<FlowWithPath> previous = flows.stream().filter(flowWithPath -> flowWithPath.getPath().equals(filePath.toString())).findFirst();
|
||||
// Check if Flow from file has id/namespace updated
|
||||
if (previous.isPresent() && !previous.get().uidWithoutRevision().equals(flow.get().uidWithoutRevision())) {
|
||||
flows.removeIf(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()));
|
||||
flowFilesManager.deleteFlow(previous.get().getTenantId(), previous.get().getNamespace(), previous.get().getId());
|
||||
Optional<Flow> flow = parseFlow(content, entry);
|
||||
if (flow.isPresent()) {
|
||||
if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
// Check if we already have a file with the given path
|
||||
if (flows.stream().anyMatch(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()))) {
|
||||
Optional<FlowWithPath> previous = flows.stream().filter(flowWithPath -> flowWithPath.getPath().equals(filePath.toString())).findFirst();
|
||||
// Check if Flow from file has id/namespace updated
|
||||
if (previous.isPresent() && !previous.get().uidWithoutRevision().equals(flow.get().uidWithoutRevision())) {
|
||||
flows.removeIf(flowWithPath -> flowWithPath.getPath().equals(filePath.toString()));
|
||||
flowFilesManager.deleteFlow(previous.get().getTenantId(), previous.get().getNamespace(), previous.get().getId());
|
||||
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
|
||||
}
|
||||
} else {
|
||||
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
|
||||
}
|
||||
} else {
|
||||
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
|
||||
}
|
||||
} else {
|
||||
flows.add(FlowWithPath.of(flow.get(), filePath.toString()));
|
||||
|
||||
flowFilesManager.createOrUpdateFlow(flow.get(), content);
|
||||
log.info("Flow {} from file {} has been created or modified", flow.get().getId(), entry);
|
||||
}
|
||||
|
||||
flowFilesManager.createOrUpdateFlow(flow.get(), content);
|
||||
log.info("Flow {} from file {} has been created or modified", flow.get().getId(), entry);
|
||||
} catch (NoSuchFileException e) {
|
||||
log.error("File not found: {}", entry, e);
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading file: {}", entry, e);
|
||||
}
|
||||
|
||||
} catch (NoSuchFileException e) {
|
||||
log.error("File not found: {}", entry, e);
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading file: {}", entry, e);
|
||||
}
|
||||
} else {
|
||||
Path filePath = ((Path) key.watchable()).resolve(entry);
|
||||
flows.stream()
|
||||
.filter(flow -> flow.getPath().equals(filePath.toString()))
|
||||
.findFirst()
|
||||
.ifPresent(flowWithPath -> {
|
||||
flowFilesManager.deleteFlow(flowWithPath.getTenantId(), flowWithPath.getNamespace(), flowWithPath.getId());
|
||||
this.flows.removeIf(fwp -> fwp.uidWithoutRevision().equals(flowWithPath.uidWithoutRevision()));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Path filePath = ((Path) key.watchable()).resolve(entry);
|
||||
flows.stream()
|
||||
.filter(flow -> flow.getPath().equals(filePath.toString()))
|
||||
.findFirst()
|
||||
.ifPresent(flowWithPath -> {
|
||||
flowFilesManager.deleteFlow(flowWithPath.getTenantId(), flowWithPath.getNamespace(), flowWithPath.getId());
|
||||
this.flows.removeIf(fwp -> fwp.uidWithoutRevision().equals(flowWithPath.uidWithoutRevision()));
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error while watching flows", e);
|
||||
}
|
||||
}
|
||||
key.reset();
|
||||
@@ -230,7 +237,8 @@ public class FileChangedEventListener {
|
||||
private Optional<Flow> parseFlow(String content, Path entry) {
|
||||
try {
|
||||
Flow flow = yamlParser.parse(content, Flow.class);
|
||||
modelValidator.validate(flow);
|
||||
FlowWithSource withPluginDefault = pluginDefaultService.injectDefaults(FlowWithSource.of(flow, content));
|
||||
modelValidator.validate(withPluginDefault);
|
||||
return Optional.of(flow);
|
||||
} catch (ConstraintViolationException e) {
|
||||
log.warn("Error while parsing flow: {}", entry, e);
|
||||
|
||||
@@ -3,32 +3,36 @@ package io.kestra.cli.services;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.kestra.core.services.PluginDefaultService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Requires(property = "micronaut.io.watch.enabled", value = "true")
|
||||
@Slf4j
|
||||
public class LocalFlowFileWatcher implements FlowFilesManager {
|
||||
private FlowRepositoryInterface flowRepositoryInterface;
|
||||
private final FlowRepositoryInterface flowRepository;
|
||||
private final PluginDefaultService pluginDefaultService;
|
||||
|
||||
public LocalFlowFileWatcher(FlowRepositoryInterface flowRepositoryInterface) {
|
||||
this.flowRepositoryInterface = flowRepositoryInterface;
|
||||
public LocalFlowFileWatcher(FlowRepositoryInterface flowRepository, PluginDefaultService pluginDefaultService) {
|
||||
this.flowRepository = flowRepository;
|
||||
this.pluginDefaultService = pluginDefaultService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlowWithSource createOrUpdateFlow(Flow flow, String content) {
|
||||
return flowRepositoryInterface.findById(null, flow.getNamespace(), flow.getId())
|
||||
.map(previous -> flowRepositoryInterface.update(flow, previous, content, flow))
|
||||
.orElseGet(() -> flowRepositoryInterface.create(flow, content, flow));
|
||||
FlowWithSource withDefault = pluginDefaultService.injectDefaults(FlowWithSource.of(flow, content));
|
||||
return flowRepository.findById(null, flow.getNamespace(), flow.getId())
|
||||
.map(previous -> flowRepository.update(flow, previous, content, withDefault))
|
||||
.orElseGet(() -> flowRepository.create(flow, content, withDefault));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFlow(FlowWithSource toDelete) {
|
||||
flowRepositoryInterface.findByIdWithSource(toDelete.getTenantId(), toDelete.getNamespace(), toDelete.getId()).ifPresent(flowRepositoryInterface::delete);
|
||||
log.error("Flow {} has been deleted", toDelete.getId());
|
||||
flowRepository.findByIdWithSource(toDelete.getTenantId(), toDelete.getNamespace(), toDelete.getId()).ifPresent(flowRepository::delete);
|
||||
log.info("Flow {} has been deleted", toDelete.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFlow(String tenantId, String namespace, String id) {
|
||||
flowRepositoryInterface.findByIdWithSource(tenantId, namespace, id).ifPresent(flowRepositoryInterface::delete);
|
||||
log.error("Flow {} has been deleted", id);
|
||||
flowRepository.findByIdWithSource(tenantId, namespace, id).ifPresent(flowRepository::delete);
|
||||
log.info("Flow {} has been deleted", id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.kestra.cli.services;
|
||||
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.utils.Await;
|
||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junitpioneer.jupiter.RetryingTest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static io.kestra.core.utils.Rethrow.throwRunnable;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
@MicronautTest(environments = {"test", "file-watch"}, transactional = false)
|
||||
class FileChangedEventListenerTest {
|
||||
public static final String FILE_WATCH = "build/file-watch";
|
||||
@Inject
|
||||
private FileChangedEventListener fileWatcher;
|
||||
|
||||
@Inject
|
||||
private FlowRepositoryInterface flowRepository;
|
||||
|
||||
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws IOException {
|
||||
if (!Files.exists(Path.of(FILE_WATCH))) {
|
||||
Files.createDirectories(Path.of(FILE_WATCH));
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() throws IOException {
|
||||
if (Files.exists(Path.of(FILE_WATCH))) {
|
||||
FileUtils.deleteDirectory(Path.of(FILE_WATCH).toFile());
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
if (started.compareAndSet(false, true)) {
|
||||
executorService.execute(throwRunnable(() -> fileWatcher.startListeningFromConfig()));
|
||||
}
|
||||
}
|
||||
|
||||
@RetryingTest(5) // Flaky on CI but always pass locally
|
||||
void test() throws IOException, TimeoutException {
|
||||
// remove the flow if it already exists
|
||||
flowRepository.findByIdWithSource(null, "io.kestra.tests.watch", "myflow").ifPresent(flow -> flowRepository.delete(flow));
|
||||
|
||||
// create a basic flow
|
||||
String flow = """
|
||||
id: myflow
|
||||
namespace: io.kestra.tests.watch
|
||||
|
||||
tasks:
|
||||
- id: hello
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: Hello World! 🚀
|
||||
""";
|
||||
Files.write(Path.of(FILE_WATCH + "/myflow.yaml"), flow.getBytes());
|
||||
Await.until(
|
||||
() -> flowRepository.findById(null, "io.kestra.tests.watch", "myflow").isPresent(),
|
||||
Duration.ofMillis(100),
|
||||
Duration.ofSeconds(10)
|
||||
);
|
||||
Flow myflow = flowRepository.findById(null, "io.kestra.tests.watch", "myflow").orElseThrow();
|
||||
assertThat(myflow.getTasks(), hasSize(1));
|
||||
assertThat(myflow.getTasks().getFirst().getId(), is("hello"));
|
||||
assertThat(myflow.getTasks().getFirst().getType(), is("io.kestra.plugin.core.log.Log"));
|
||||
|
||||
// delete the flow
|
||||
Files.delete(Path.of(FILE_WATCH + "/myflow.yaml"));
|
||||
Await.until(
|
||||
() -> flowRepository.findById(null, "io.kestra.tests.watch", "myflow").isEmpty(),
|
||||
Duration.ofMillis(100),
|
||||
Duration.ofSeconds(10)
|
||||
);
|
||||
}
|
||||
|
||||
@RetryingTest(5) // Flaky on CI but always pass locally
|
||||
void testWithPluginDefault() throws IOException, TimeoutException {
|
||||
// remove the flow if it already exists
|
||||
flowRepository.findByIdWithSource(null, "io.kestra.tests.watch", "pluginDefault").ifPresent(flow -> flowRepository.delete(flow));
|
||||
|
||||
// create a flow with plugin default
|
||||
String pluginDefault = """
|
||||
id: pluginDefault
|
||||
namespace: io.kestra.tests.watch
|
||||
|
||||
tasks:
|
||||
- id: helloWithDefault
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
|
||||
pluginDefaults:
|
||||
- type: io.kestra.plugin.core.log.Log
|
||||
values:
|
||||
message: Hello World!
|
||||
""";
|
||||
Files.write(Path.of(FILE_WATCH + "/plugin-default.yaml"), pluginDefault.getBytes());
|
||||
Await.until(
|
||||
() -> flowRepository.findById(null, "io.kestra.tests.watch", "pluginDefault").isPresent(),
|
||||
Duration.ofMillis(100),
|
||||
Duration.ofSeconds(10)
|
||||
);
|
||||
Flow pluginDefaultFlow = flowRepository.findById(null, "io.kestra.tests.watch", "pluginDefault").orElseThrow();
|
||||
assertThat(pluginDefaultFlow.getTasks(), hasSize(1));
|
||||
assertThat(pluginDefaultFlow.getTasks().getFirst().getId(), is("helloWithDefault"));
|
||||
assertThat(pluginDefaultFlow.getTasks().getFirst().getType(), is("io.kestra.plugin.core.log.Log"));
|
||||
|
||||
// delete both files
|
||||
Files.delete(Path.of(FILE_WATCH + "/plugin-default.yaml"));
|
||||
Await.until(
|
||||
() -> flowRepository.findById(null, "io.kestra.tests.watch", "pluginDefault").isEmpty(),
|
||||
Duration.ofMillis(100),
|
||||
Duration.ofSeconds(10)
|
||||
);
|
||||
}
|
||||
}
|
||||
12
cli/src/test/resources/application-file-watch.yml
Normal file
12
cli/src/test/resources/application-file-watch.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
micronaut:
|
||||
io:
|
||||
watch:
|
||||
enabled: true
|
||||
paths:
|
||||
- build/file-watch
|
||||
|
||||
kestra:
|
||||
repository:
|
||||
type: memory
|
||||
queue:
|
||||
type: memory
|
||||
@@ -82,6 +82,7 @@ public class JsonSchemaGenerator {
|
||||
}
|
||||
replaceAnyOfWithOneOf(objectNode);
|
||||
pullOfDefaultFromOneOf(objectNode);
|
||||
removeRequiredOnPropsWithDefaults(objectNode);
|
||||
|
||||
return JacksonMapper.toMap(objectNode);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -89,6 +90,27 @@ public class JsonSchemaGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private void removeRequiredOnPropsWithDefaults(ObjectNode objectNode) {
|
||||
objectNode.findParents("required").forEach(jsonNode -> {
|
||||
if (jsonNode instanceof ObjectNode clazzSchema && clazzSchema.get("required") instanceof ArrayNode requiredPropsNode && clazzSchema.get("properties") instanceof ObjectNode properties) {
|
||||
List<String> requiredFieldValues = StreamSupport.stream(requiredPropsNode.spliterator(), false)
|
||||
.map(JsonNode::asText)
|
||||
.toList();
|
||||
|
||||
properties.fields().forEachRemaining(e -> {
|
||||
int indexInRequiredArray = requiredFieldValues.indexOf(e.getKey());
|
||||
if (indexInRequiredArray != -1 && e.getValue() instanceof ObjectNode valueNode && valueNode.has("default")) {
|
||||
requiredPropsNode.remove(indexInRequiredArray);
|
||||
}
|
||||
});
|
||||
|
||||
if (requiredPropsNode.isEmpty()) {
|
||||
clazzSchema.remove("required");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void replaceAnyOfWithOneOf(ObjectNode objectNode) {
|
||||
objectNode.findParents("anyOf").forEach(jsonNode -> {
|
||||
if (jsonNode instanceof ObjectNode oNode) {
|
||||
@@ -311,10 +333,12 @@ public class JsonSchemaGenerator {
|
||||
if (member.getDeclaredType().isInstanceOf(Property.class)) {
|
||||
memberAttributes.put("$dynamic", true);
|
||||
// if we are in the String definition of a Property but the target type is not String: we configure the pattern
|
||||
Class<?> targetType = member.getDeclaredType().getTypeParameters().getFirst().getErasedType();
|
||||
if (!String.class.isAssignableFrom(targetType) && String.class.isAssignableFrom(member.getType().getErasedType())) {
|
||||
memberAttributes.put("pattern", ".*{{.*}}.*");
|
||||
}
|
||||
// TODO this was a good idea but their is too much cases where it didn't work like in List or Map so if we want it we need to make it more clever
|
||||
// I keep it for now commented but at some point we may want to re-do and improve it or remove these commented lines
|
||||
// Class<?> targetType = member.getDeclaredType().getTypeParameters().getFirst().getErasedType();
|
||||
// if (!String.class.isAssignableFrom(targetType) && String.class.isAssignableFrom(member.getType().getErasedType())) {
|
||||
// memberAttributes.put("pattern", ".*{{.*}}.*");
|
||||
// }
|
||||
} else if (member.getDeclaredType().isInstanceOf(Data.class)) {
|
||||
memberAttributes.put("$dynamic", false);
|
||||
}
|
||||
@@ -603,6 +627,7 @@ public class JsonSchemaGenerator {
|
||||
ObjectNode objectNode = generator.generateSchema(cls);
|
||||
replaceAnyOfWithOneOf(objectNode);
|
||||
pullOfDefaultFromOneOf(objectNode);
|
||||
removeRequiredOnPropsWithDefaults(objectNode);
|
||||
|
||||
return JacksonMapper.toMap(extractMainRef(objectNode));
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.function.Predicate.not;
|
||||
@@ -40,15 +41,22 @@ public class Plugin {
|
||||
private String subGroup;
|
||||
|
||||
public static Plugin of(RegisteredPlugin registeredPlugin, @Nullable String subgroup) {
|
||||
return Plugin.of(registeredPlugin, subgroup, true);
|
||||
}
|
||||
|
||||
public static Plugin of(RegisteredPlugin registeredPlugin, @Nullable String subgroup, boolean includeDeprecated) {
|
||||
Plugin plugin = new Plugin();
|
||||
plugin.name = registeredPlugin.name();
|
||||
PluginSubGroup subGroupInfos = null;
|
||||
if (subgroup == null) {
|
||||
plugin.title = registeredPlugin.title();
|
||||
} else {
|
||||
subGroupInfos = registeredPlugin.allClass().stream().filter(c -> c.getName().contains(subgroup)).map(clazz -> clazz.getPackage().getDeclaredAnnotation(PluginSubGroup.class)).toList().getFirst();
|
||||
subGroupInfos = registeredPlugin.allClass().stream()
|
||||
.filter(c -> c.getPackageName().contains(subgroup))
|
||||
.min(Comparator.comparingInt(a -> a.getPackageName().length()))
|
||||
.map(clazz -> clazz.getPackage().getDeclaredAnnotation(PluginSubGroup.class))
|
||||
.orElseThrow();
|
||||
plugin.title = !subGroupInfos.title().isEmpty() ? subGroupInfos.title() : subgroup.substring(subgroup.lastIndexOf('.') + 1);;
|
||||
|
||||
}
|
||||
plugin.group = registeredPlugin.group();
|
||||
plugin.description = subGroupInfos != null && !subGroupInfos.description().isEmpty() ? subGroupInfos.description() : registeredPlugin.description();
|
||||
@@ -80,17 +88,18 @@ public class Plugin {
|
||||
|
||||
plugin.subGroup = subgroup;
|
||||
|
||||
plugin.tasks = filterAndGetClassName(registeredPlugin.getTasks()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.triggers = filterAndGetClassName(registeredPlugin.getTriggers()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.conditions = filterAndGetClassName(registeredPlugin.getConditions()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.storages = filterAndGetClassName(registeredPlugin.getStorages()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.secrets = filterAndGetClassName(registeredPlugin.getSecrets()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.taskRunners = filterAndGetClassName(registeredPlugin.getTaskRunners()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.apps = filterAndGetClassName(registeredPlugin.getApps()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.appBlocks = filterAndGetClassName(registeredPlugin.getAppBlocks()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.charts = filterAndGetClassName(registeredPlugin.getCharts()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.dataFilters = filterAndGetClassName(registeredPlugin.getDataFilters()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
plugin.logExporters = filterAndGetClassName(registeredPlugin.getLogExporters()).stream().filter(c -> subgroup == null || c.startsWith(subgroup)).toList();
|
||||
Predicate<Class<?>> packagePredicate = c -> subgroup == null || c.getPackageName().equals(subgroup);
|
||||
plugin.tasks = filterAndGetClassName(registeredPlugin.getTasks(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.triggers = filterAndGetClassName(registeredPlugin.getTriggers(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.conditions = filterAndGetClassName(registeredPlugin.getConditions(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.storages = filterAndGetClassName(registeredPlugin.getStorages(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.secrets = filterAndGetClassName(registeredPlugin.getSecrets(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.taskRunners = filterAndGetClassName(registeredPlugin.getTaskRunners(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.apps = filterAndGetClassName(registeredPlugin.getApps(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.appBlocks = filterAndGetClassName(registeredPlugin.getAppBlocks(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.charts = filterAndGetClassName(registeredPlugin.getCharts(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.dataFilters = filterAndGetClassName(registeredPlugin.getDataFilters(), includeDeprecated, packagePredicate).stream().toList();
|
||||
plugin.logExporters = filterAndGetClassName(registeredPlugin.getLogExporters(), includeDeprecated, packagePredicate).stream().toList();
|
||||
|
||||
return plugin;
|
||||
}
|
||||
@@ -100,12 +109,15 @@ public class Plugin {
|
||||
* Those classes are only filtered from the documentation to ensure backward compatibility.
|
||||
*
|
||||
* @param list The list of classes?
|
||||
* @param includeDeprecated whether to include deprecated plugins or not
|
||||
* @return a filtered streams.
|
||||
*/
|
||||
private static List<String> filterAndGetClassName(final List<? extends Class<?>> list) {
|
||||
private static List<String> filterAndGetClassName(final List<? extends Class<?>> list, boolean includeDeprecated, Predicate<Class<?>> clazzFilter) {
|
||||
return list
|
||||
.stream()
|
||||
.filter(not(io.kestra.core.models.Plugin::isInternal))
|
||||
.filter(p -> includeDeprecated || !io.kestra.core.models.Plugin.isDeprecated(p))
|
||||
.filter(clazzFilter)
|
||||
.map(Class::getName)
|
||||
.filter(c -> !c.startsWith("org.kestra."))
|
||||
.toList();
|
||||
|
||||
@@ -142,12 +142,22 @@ public class HttpRequest {
|
||||
public abstract static class RequestBody {
|
||||
public abstract HttpEntity to() throws IOException;
|
||||
|
||||
public abstract Object getContent() throws IOException;
|
||||
|
||||
public abstract Charset getCharset() throws IOException;
|
||||
|
||||
public abstract String getContentType() throws IOException;
|
||||
|
||||
protected ContentType entityContentType() throws IOException {
|
||||
return this.getCharset() != null ? ContentType.create(this.getContentType(), this.getCharset()) : ContentType.create(this.getContentType());
|
||||
}
|
||||
|
||||
public static RequestBody from(HttpEntity entity) throws IOException {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Charset charset = Charset.forName(entity.getContentEncoding());
|
||||
Charset charset = entity.getContentEncoding() != null ? Charset.forName(entity.getContentEncoding()) : StandardCharsets.UTF_8;
|
||||
|
||||
if (entity.getContentType().equals(ContentType.APPLICATION_OCTET_STREAM.getMimeType())) {
|
||||
return ByteArrayRequestBody.builder()
|
||||
@@ -172,71 +182,80 @@ public class HttpRequest {
|
||||
.build();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported Content-Type: " + entity.getContentType());
|
||||
return ByteArrayRequestBody.builder()
|
||||
.charset(charset)
|
||||
.contentType(entity.getContentType())
|
||||
.content(entity.getContent().readAllBytes())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class InputStreamRequestBody extends RequestBody {
|
||||
@Builder.Default
|
||||
private String contentType = ContentType.APPLICATION_OCTET_STREAM.getMimeType();
|
||||
|
||||
@Builder.Default
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
private Charset charset;
|
||||
|
||||
private InputStream content;
|
||||
|
||||
public HttpEntity to() {
|
||||
return new InputStreamEntity(content, ContentType.create(contentType, charset));
|
||||
public HttpEntity to() throws IOException {
|
||||
return new InputStreamEntity(content, this.entityContentType());
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class StringRequestBody extends RequestBody {
|
||||
@Builder.Default
|
||||
private String contentType = ContentType.TEXT_PLAIN.getMimeType();
|
||||
|
||||
@Builder.Default
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
private Charset charset;
|
||||
|
||||
private String content;
|
||||
|
||||
public HttpEntity to() {
|
||||
return new StringEntity(this.content, ContentType.create(contentType, charset));
|
||||
public HttpEntity to() throws IOException {
|
||||
return new StringEntity(this.content, this.entityContentType());
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class ByteArrayRequestBody extends RequestBody {
|
||||
@Builder.Default
|
||||
private String contentType = ContentType.APPLICATION_OCTET_STREAM.getMimeType();
|
||||
|
||||
@Builder.Default
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
private Charset charset;
|
||||
|
||||
private byte[] content;
|
||||
|
||||
public HttpEntity to() {
|
||||
return new ByteArrayEntity(content, ContentType.create(contentType, charset));
|
||||
public HttpEntity to() throws IOException {
|
||||
return new ByteArrayEntity(content, this.entityContentType());
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class JsonRequestBody extends RequestBody {
|
||||
@Builder.Default
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
private Charset charset;
|
||||
|
||||
private Object content;
|
||||
|
||||
@Override
|
||||
public String getContentType() throws IOException {
|
||||
return ContentType.APPLICATION_JSON.getMimeType();
|
||||
}
|
||||
|
||||
public HttpEntity to() throws IOException {
|
||||
try {
|
||||
return new StringEntity(
|
||||
JacksonMapper.ofJson().writeValueAsString(content),
|
||||
ContentType.APPLICATION_JSON.withCharset(this.charset)
|
||||
this.charset != null ? ContentType.APPLICATION_JSON.withCharset(this.charset) : ContentType.APPLICATION_JSON
|
||||
);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IOException(e);
|
||||
@@ -244,37 +263,49 @@ public class HttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class UrlEncodedRequestBody extends RequestBody {
|
||||
@Builder.Default
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
private Charset charset;
|
||||
|
||||
private Map<String, Object> content;
|
||||
|
||||
@Override
|
||||
public String getContentType() throws IOException {
|
||||
return ContentType.APPLICATION_FORM_URLENCODED.getMimeType();
|
||||
}
|
||||
|
||||
public HttpEntity to() throws IOException {
|
||||
return new UrlEncodedFormEntity(
|
||||
this.content .entrySet()
|
||||
.stream()
|
||||
.map(e -> new BasicNameValuePair(e.getKey(), e.getValue().toString()))
|
||||
.toList(),
|
||||
this.charset
|
||||
);
|
||||
List<BasicNameValuePair> list = this.content.entrySet()
|
||||
.stream()
|
||||
.map(e -> new BasicNameValuePair(e.getKey(), e.getValue().toString()))
|
||||
.toList();
|
||||
|
||||
return this.charset != null ? new UrlEncodedFormEntity(list, this.charset) : new UrlEncodedFormEntity(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class MultipartRequestBody extends RequestBody {
|
||||
@Builder.Default
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
private Charset charset;
|
||||
|
||||
private Map<String, Object> content;
|
||||
|
||||
@Override
|
||||
public String getContentType() throws IOException {
|
||||
return ContentType.MULTIPART_MIXED.getMimeType();
|
||||
}
|
||||
|
||||
public HttpEntity to() throws IOException {
|
||||
MultipartEntityBuilder builder = MultipartEntityBuilder
|
||||
.create()
|
||||
.setCharset(this.charset);
|
||||
.create();
|
||||
|
||||
if (this.charset != null) {
|
||||
builder.setCharset(this.charset);
|
||||
}
|
||||
|
||||
content.forEach((key, value) -> {
|
||||
switch (value) {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class HttpClient implements Closeable {
|
||||
}
|
||||
|
||||
// proxy
|
||||
if (this.configuration.getProxy() != null) {
|
||||
if (this.configuration.getProxy() != null && configuration.getProxy().getAddress() != null) {
|
||||
SocketAddress proxyAddr = new InetSocketAddress(
|
||||
runContext.render(configuration.getProxy().getAddress()).as(String.class).orElse(null),
|
||||
runContext.render(configuration.getProxy().getPort()).as(Integer.class).orElse(null)
|
||||
|
||||
@@ -3,8 +3,8 @@ package io.kestra.core.http.client.configurations;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
|
||||
@@ -14,8 +14,9 @@ import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
@JsonSubTypes.Type(value = BearerAuthConfiguration.class, name = "BEARER")
|
||||
})
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor
|
||||
public abstract class AbstractAuthConfiguration {
|
||||
public abstract Property<AuthType> getType();
|
||||
public abstract AuthType getType();
|
||||
|
||||
public abstract void configure(HttpClientBuilder builder, RunContext runContext) throws IllegalVariableEvaluationException;
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
@@ -16,19 +15,20 @@ import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
@Getter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class BasicAuthConfiguration extends AbstractAuthConfiguration {
|
||||
@NotNull
|
||||
@JsonInclude
|
||||
@Builder.Default
|
||||
protected Property<AuthType> type = Property.of(AuthType.BASIC);
|
||||
protected AuthType type = AuthType.BASIC;
|
||||
|
||||
@Schema(title = "The username for HTTP basic authentication.")
|
||||
private final Property<String> username;
|
||||
private Property<String> username;
|
||||
|
||||
@Schema(title = "The password for HTTP basic authentication.")
|
||||
private final Property<String> password;
|
||||
private Property<String> password;
|
||||
|
||||
@Override
|
||||
public void configure(HttpClientBuilder builder, RunContext runContext) throws IllegalVariableEvaluationException {
|
||||
|
||||
@@ -8,21 +8,23 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
|
||||
@Getter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class BearerAuthConfiguration extends AbstractAuthConfiguration {
|
||||
@NotNull
|
||||
@JsonInclude
|
||||
@Builder.Default
|
||||
protected Property<AuthType> type = Property.of(AuthType.BEARER);
|
||||
protected AuthType type = AuthType.BEARER;
|
||||
|
||||
@Schema(title = "The token for bearer token authentication.")
|
||||
private final Property<String> token;
|
||||
private Property<String> token;
|
||||
|
||||
@Override
|
||||
public void configure(HttpClientBuilder builder, RunContext runContext) throws IllegalVariableEvaluationException {
|
||||
|
||||
@@ -2,20 +2,21 @@ package io.kestra.core.http.client.configurations;
|
||||
|
||||
import io.kestra.core.models.annotations.PluginProperty;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.micronaut.http.client.HttpClientConfiguration;
|
||||
import io.micronaut.logging.LogLevel;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
@Builder(toBuilder = true)
|
||||
@Getter
|
||||
@Jacksonized
|
||||
public class HttpConfiguration {
|
||||
@Schema(title = "The timeout configuration.")
|
||||
@PluginProperty
|
||||
@@ -28,6 +29,7 @@ public class HttpConfiguration {
|
||||
@Schema(title = "The authentification to use.")
|
||||
private AbstractAuthConfiguration auth;
|
||||
|
||||
@Setter
|
||||
@Schema(title = "The SSL request options")
|
||||
private SslOptions ssl;
|
||||
|
||||
@@ -35,6 +37,7 @@ public class HttpConfiguration {
|
||||
@Builder.Default
|
||||
private Property<Boolean> followRedirects = Property.of(true);
|
||||
|
||||
@Setter
|
||||
@Schema(title = "If true, allow a failed response code (response code >= 400)")
|
||||
@Builder.Default
|
||||
private Property<Boolean> allowFailed = Property.of(false);
|
||||
@@ -55,261 +58,212 @@ public class HttpConfiguration {
|
||||
}
|
||||
|
||||
// Deprecated properties
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The time allowed to establish a connection to the server before failing.")
|
||||
@Deprecated
|
||||
private final Property<Duration> connectTimeout;
|
||||
private final Duration connectTimeout;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setConnectTimeout(Property<Duration> connectTimeout) {
|
||||
if (this.timeout == null) {
|
||||
this.timeout = TimeoutConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.timeout = this.timeout.toBuilder()
|
||||
.connectTimeout(connectTimeout)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The maximum time allowed for reading data from the server before failing.")
|
||||
@Builder.Default
|
||||
@Deprecated
|
||||
private final Property<Duration> readTimeout = Property.of(Duration.ofSeconds(HttpClientConfiguration.DEFAULT_READ_TIMEOUT_SECONDS));
|
||||
private final Duration readTimeout;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setReadTimeout(Property<Duration> readTimeout) {
|
||||
if (this.timeout == null) {
|
||||
this.timeout = TimeoutConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.timeout = this.timeout.toBuilder()
|
||||
.readIdleTimeout(readTimeout)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The type of proxy to use.")
|
||||
@Builder.Default
|
||||
@Deprecated
|
||||
private final Property<Proxy.Type> proxyType = Property.of(Proxy.Type.DIRECT);
|
||||
private final Proxy.Type proxyType;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setProxyType(Property<Proxy.Type> proxyType) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.type(proxyType)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The address of the proxy server.")
|
||||
@Deprecated
|
||||
private final Property<String> proxyAddress;
|
||||
private final String proxyAddress;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setProxyAddress(Property<String> proxyAddress) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.address(proxyAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The port of the proxy server.")
|
||||
@Deprecated
|
||||
private final Property<Integer> proxyPort;
|
||||
private final Integer proxyPort;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setProxyPort(Property<Integer> proxyPort) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.port(proxyPort)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The username for proxy authentication.")
|
||||
@Deprecated
|
||||
private final Property<String> proxyUsername;
|
||||
private final String proxyUsername;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setProxyUsername(Property<String> proxyUsername) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.username(proxyUsername)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The password for proxy authentication.")
|
||||
@Deprecated
|
||||
private final Property<String> proxyPassword;
|
||||
private final String proxyPassword;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setProxyPassword(Property<String> proxyPassword) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.password(proxyPassword)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The username for HTTP basic authentication.")
|
||||
@Deprecated
|
||||
private final Property<String> basicAuthUser;
|
||||
private final String basicAuthUser;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setBasicAuthUser(Property<String> basicAuthUser) {
|
||||
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
|
||||
this.auth = BasicAuthConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
|
||||
.username(basicAuthUser)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The password for HTTP basic authentication.")
|
||||
@Deprecated
|
||||
private final Property<String> basicAuthPassword;
|
||||
private final String basicAuthPassword;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
private void setBasicAuthPassword(Property<String> basicAuthPassword) {
|
||||
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
|
||||
this.auth = BasicAuthConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
|
||||
.password(basicAuthPassword)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The log level for the HTTP client.")
|
||||
@PluginProperty
|
||||
@Deprecated
|
||||
private final LogLevel logLevel;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
// Deprecated properties with no equivalent value to be kept, silently ignore
|
||||
@Schema(title = "The time allowed for a read connection to remain idle before closing it.")
|
||||
@Deprecated
|
||||
private void setLogLevel(LogLevel logLevel) {
|
||||
if (logLevel == LogLevel.TRACE) {
|
||||
this.logs = new LoggingType[]{
|
||||
LoggingType.REQUEST_HEADERS,
|
||||
LoggingType.REQUEST_BODY,
|
||||
LoggingType.RESPONSE_HEADERS,
|
||||
LoggingType.RESPONSE_BODY
|
||||
};
|
||||
} else if (logLevel == LogLevel.DEBUG) {
|
||||
this.logs = new LoggingType[]{
|
||||
LoggingType.REQUEST_HEADERS,
|
||||
LoggingType.RESPONSE_HEADERS,
|
||||
};
|
||||
} else if (logLevel == LogLevel.INFO) {
|
||||
this.logs = new LoggingType[]{
|
||||
LoggingType.RESPONSE_HEADERS,
|
||||
};
|
||||
private final Duration readIdleTimeout;
|
||||
|
||||
@Schema(title = "The time an idle connection can remain in the client's connection pool before being closed.")
|
||||
@Deprecated
|
||||
private final Duration connectionPoolIdleTimeout;
|
||||
|
||||
@Schema(title = "The maximum content length of the response.")
|
||||
@Deprecated
|
||||
private final Integer maxContentLength;
|
||||
|
||||
public static class HttpConfigurationBuilder {
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder connectTimeout(Duration connectTimeout) {
|
||||
if (this.timeout == null) {
|
||||
this.timeout = TimeoutConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.timeout = this.timeout.toBuilder()
|
||||
.connectTimeout(Property.of(connectTimeout))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder readTimeout(Duration readTimeout) {
|
||||
if (this.timeout == null) {
|
||||
this.timeout = TimeoutConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.timeout = this.timeout.toBuilder()
|
||||
.readIdleTimeout(Property.of(readTimeout))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder proxyType(Proxy.Type proxyType) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.type(Property.of(proxyType))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder proxyAddress(String proxyAddress) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.address(Property.of(proxyAddress))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder proxyPort(Integer proxyPort) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.port(Property.of(proxyPort))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder proxyUsername(String proxyUsername) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.username(Property.of(proxyUsername))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder proxyPassword(String proxyPassword) {
|
||||
if (this.proxy == null) {
|
||||
this.proxy = ProxyConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.proxy = this.proxy.toBuilder()
|
||||
.password(Property.of(proxyPassword))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder basicAuthUser(String basicAuthUser) {
|
||||
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
|
||||
this.auth = BasicAuthConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
|
||||
.username(Property.of(basicAuthUser))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder basicAuthPassword(String basicAuthPassword) {
|
||||
if (this.auth == null || !(this.auth instanceof BasicAuthConfiguration)) {
|
||||
this.auth = BasicAuthConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.auth = ((BasicAuthConfiguration) this.auth).toBuilder()
|
||||
.password(Property.of(basicAuthPassword))
|
||||
.build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public HttpConfigurationBuilder logLevel(LogLevel logLevel) {
|
||||
if (logLevel == LogLevel.TRACE) {
|
||||
this.logs = new LoggingType[]{
|
||||
LoggingType.REQUEST_HEADERS,
|
||||
LoggingType.REQUEST_BODY,
|
||||
LoggingType.RESPONSE_HEADERS,
|
||||
LoggingType.RESPONSE_BODY
|
||||
};
|
||||
} else if (logLevel == LogLevel.DEBUG) {
|
||||
this.logs = new LoggingType[]{
|
||||
LoggingType.REQUEST_HEADERS,
|
||||
LoggingType.RESPONSE_HEADERS,
|
||||
};
|
||||
} else if (logLevel == LogLevel.INFO) {
|
||||
this.logs = new LoggingType[]{
|
||||
LoggingType.RESPONSE_HEADERS,
|
||||
};
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated properties with no real value to be kept, silently ignore
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The time allowed for a read connection to remain idle before closing it.")
|
||||
@Builder.Default
|
||||
@Deprecated
|
||||
private final Property<Duration> readIdleTimeout = Property.of(Duration.of(HttpClientConfiguration.DEFAULT_READ_IDLE_TIMEOUT_MINUTES, ChronoUnit.MINUTES));
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The time an idle connection can remain in the client's connection pool before being closed.")
|
||||
@Builder.Default
|
||||
@Deprecated
|
||||
private final Property<Duration> connectionPoolIdleTimeout = Property.of(Duration.ofSeconds(HttpClientConfiguration.DEFAULT_CONNECTION_POOL_IDLE_TIMEOUT_SECONDS));
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Schema(title = "The maximum content length of the response.")
|
||||
@Builder.Default
|
||||
@Deprecated
|
||||
private final Property<Integer> maxContentLength = Property.of(HttpClientConfiguration.DEFAULT_MAX_CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import io.kestra.core.models.property.Property;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
@Getter
|
||||
@Builder(toBuilder = true)
|
||||
@Jacksonized
|
||||
public class ProxyConfiguration {
|
||||
@Schema(title = "The type of proxy to use.")
|
||||
@Builder.Default
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.kestra.core.models.tasks.FlowableTask;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.tasks.retrys.AbstractRetry;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.core.models.validations.ManualConstraintViolation;
|
||||
import io.kestra.core.serializers.JacksonMapper;
|
||||
import io.kestra.core.serializers.ListOrMapOfLabelDeserializer;
|
||||
@@ -176,6 +177,14 @@ public class Flow extends AbstractFlow implements HasUID {
|
||||
);
|
||||
}
|
||||
|
||||
public static String uid(Trigger trigger) {
|
||||
return IdUtils.fromParts(
|
||||
trigger.getTenantId(),
|
||||
trigger.getNamespace(),
|
||||
trigger.getFlowId()
|
||||
);
|
||||
}
|
||||
|
||||
public static String uidWithoutRevision(Execution execution) {
|
||||
return IdUtils.fromParts(
|
||||
execution.getTenantId(),
|
||||
@@ -278,6 +287,14 @@ public class Flow extends AbstractFlow implements HasUID {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public AbstractTrigger findTriggerByTriggerId(String triggerId) {
|
||||
return this.triggers
|
||||
.stream()
|
||||
.filter(trigger -> trigger.getId().equals(triggerId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated should not be used
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static io.kestra.core.utils.Rethrow.throwFunction;
|
||||
|
||||
/**
|
||||
* Define a plugin properties that will be rendered and converted to a target type at use time.
|
||||
*
|
||||
@@ -136,12 +138,31 @@ public class Property<T> {
|
||||
*
|
||||
* @see io.kestra.core.runners.RunContextProperty#asList(Class, Map)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, I> T asList(Property<T> property, RunContext runContext, Class<I> itemClazz, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
if (property.value == null) {
|
||||
String rendered = runContext.render(property.expression, variables);
|
||||
JavaType type = MAPPER.getTypeFactory().constructCollectionLikeType(List.class, itemClazz);
|
||||
try {
|
||||
property.value = MAPPER.readValue(rendered, type);
|
||||
String trimmedExpression = property.expression.trim();
|
||||
// We need to detect if the expression is already a list or if it's a pebble expression (for eg. referencing a variable containing a list).
|
||||
// Doing that allows us to, if it's an expression, first render then read it as a list.
|
||||
if (trimmedExpression.startsWith("{{") && trimmedExpression.endsWith("}}")) {
|
||||
property.value = MAPPER.readValue(runContext.render(property.expression, variables), type);
|
||||
}
|
||||
// Otherwise, if it's already a list, we read it as a list first then render it from run context which handle list rendering by rendering each item of the list
|
||||
else {
|
||||
List<?> asRawList = MAPPER.readValue(property.expression, List.class);
|
||||
property.value = (T) asRawList.stream()
|
||||
.map(throwFunction(item -> {
|
||||
if (item instanceof String str) {
|
||||
return MAPPER.convertValue(runContext.render(str, variables), itemClazz);
|
||||
} else if (item instanceof Map map) {
|
||||
return MAPPER.convertValue(runContext.render(map, variables), itemClazz);
|
||||
}
|
||||
return item;
|
||||
}))
|
||||
.toList();
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalVariableEvaluationException(e);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.kestra.core.models.tasks.runners;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import io.kestra.core.utils.Slugify;
|
||||
@@ -93,6 +94,18 @@ public final class ScriptService {
|
||||
|
||||
}
|
||||
|
||||
public static List<String> replaceInternalStorage(
|
||||
RunContext runContext,
|
||||
Map<String, Object> additionalVars,
|
||||
Property<List<String>> commands,
|
||||
boolean replaceWithRelativePath
|
||||
) throws IOException, IllegalVariableEvaluationException {
|
||||
return commands == null ? Collections.emptyList() :
|
||||
runContext.render(commands).asList(String.class, additionalVars).stream()
|
||||
.map(throwFunction(c -> ScriptService.replaceInternalStorage(runContext, c, replaceWithRelativePath)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static List<String> replaceInternalStorage(
|
||||
RunContext runContext,
|
||||
List<String> commands
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package io.kestra.core.models.tasks.runners;
|
||||
|
||||
import io.kestra.core.models.property.Property;
|
||||
import lombok.With;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -19,7 +22,11 @@ public interface TaskCommands {
|
||||
|
||||
AbstractLogConsumer getLogConsumer();
|
||||
|
||||
List<String> getCommands();
|
||||
Property<List<String>> getInterpreter();
|
||||
|
||||
Property<List<String>> getBeforeCommands();
|
||||
|
||||
Property<List<String>> getCommands();
|
||||
|
||||
Map<String, Object> getAdditionalVars();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.Plugin;
|
||||
import io.kestra.core.models.WorkerJobLifecycle;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import io.kestra.plugin.core.runner.Process;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -16,11 +17,11 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.kestra.core.utils.WindowsUtils.windowsToUnixPath;
|
||||
|
||||
|
||||
@@ -2,23 +2,26 @@ package io.kestra.core.models.tasks.runners;
|
||||
|
||||
import io.kestra.core.models.tasks.Output;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Builder
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
public class TaskRunnerResult<T extends TaskRunnerDetailResult> implements Output {
|
||||
private int exitCode;
|
||||
|
||||
private AbstractLogConsumer logConsumer;
|
||||
|
||||
@Nullable
|
||||
private T details;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TaskRunnerResult(int exitCode, AbstractLogConsumer logConsumer) {
|
||||
this.exitCode = exitCode;
|
||||
this.logConsumer = logConsumer;
|
||||
this.details = (T) TaskRunnerDetailResult.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.kestra.core.models.triggers;
|
||||
import io.kestra.core.models.annotations.PluginProperty;
|
||||
import io.kestra.core.validations.TimeWindowValidation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.With;
|
||||
@@ -19,7 +18,6 @@ public class TimeWindow {
|
||||
title = "The type of the SLA",
|
||||
description = "The default SLA is a sliding window (`DURATION_WINDOW`) with a window of 24 hours."
|
||||
)
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
@PluginProperty
|
||||
private TimeWindow.Type type = TimeWindow.Type.DURATION_WINDOW;
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.plugin.core.trigger.Schedule;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
@@ -210,6 +211,32 @@ public class Trigger extends TriggerContext implements HasUID {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Trigger resetExecution(Flow flow, Execution execution, ConditionContext conditionContext) {
|
||||
boolean disabled = this.getStopAfter() != null ? this.getStopAfter().contains(execution.getState().getCurrent()) : this.getDisabled();
|
||||
if (!disabled) {
|
||||
AbstractTrigger abstractTrigger = flow.findTriggerByTriggerId(this.getTriggerId());
|
||||
if (abstractTrigger == null) {
|
||||
throw new IllegalArgumentException("Unable to find trigger with id '" + this.getTriggerId() + "'");
|
||||
}
|
||||
// If trigger is a schedule and execution ended after the next execution date
|
||||
else if (abstractTrigger instanceof Schedule schedule &&
|
||||
execution.getState().getEndDate().get().isAfter(this.getNextExecutionDate().toInstant())
|
||||
) {
|
||||
RecoverMissedSchedules recoverMissedSchedules = Optional.ofNullable(schedule.getRecoverMissedSchedules())
|
||||
.orElseGet(() -> schedule.defaultRecoverMissedSchedules(conditionContext.getRunContext()));
|
||||
|
||||
ZonedDateTime previousDate = schedule.previousEvaluationDate(conditionContext);
|
||||
|
||||
if (recoverMissedSchedules.equals(RecoverMissedSchedules.LAST)) {
|
||||
return resetExecution(execution.getState().getCurrent(), previousDate);
|
||||
} else if (recoverMissedSchedules.equals(RecoverMissedSchedules.NONE)) {
|
||||
return resetExecution(execution.getState().getCurrent(), schedule.nextEvaluationDate(conditionContext, Optional.empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return resetExecution(execution.getState().getCurrent());
|
||||
}
|
||||
|
||||
public Trigger resetExecution(State.Type executionEndState) {
|
||||
return resetExecution(executionEndState, this.getNextExecutionDate());
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.kestra.core.models.triggers.TimeWindow.Type.DURATION_WINDOW;
|
||||
|
||||
public interface MultipleConditionStorageInterface {
|
||||
Optional<MultipleConditionWindow> get(Flow flow, String conditionId);
|
||||
|
||||
@@ -20,7 +22,8 @@ public interface MultipleConditionStorageInterface {
|
||||
ZonedDateTime now = ZonedDateTime.now().withNano(0);
|
||||
TimeWindow timeWindow = multipleCondition.getTimeWindow() != null ? multipleCondition.getTimeWindow() : TimeWindow.builder().build();
|
||||
|
||||
var startAndEnd = switch (timeWindow.getType()) {
|
||||
TimeWindow.Type type = timeWindow.getType() != null ? timeWindow.getType() : DURATION_WINDOW;
|
||||
var startAndEnd = switch (type) {
|
||||
case DURATION_WINDOW -> {
|
||||
Duration window = timeWindow.getWindow() == null ? Duration.ofDays(1) : timeWindow.getWindow();
|
||||
if (window.toDays() > 0) {
|
||||
|
||||
@@ -43,6 +43,7 @@ public class PluginClassLoader extends URLClassLoader {
|
||||
+ "|com.fasterxml.jackson.dataformat.xml"
|
||||
+ "|org.reactivestreams"
|
||||
+ "|dev.failsafe"
|
||||
+ "|reactor"
|
||||
+ ")\\..*$");
|
||||
|
||||
private final ClassLoader parent;
|
||||
|
||||
@@ -154,7 +154,7 @@ public class RegisteredPlugin {
|
||||
result.put("secrets", Arrays.asList(this.getSecrets().toArray(Class[]::new)));
|
||||
result.put("task-runners", Arrays.asList(this.getTaskRunners().toArray(Class[]::new)));
|
||||
result.put("apps", Arrays.asList(this.getApps().toArray(Class[]::new)));
|
||||
result.put("appBlocks", Arrays.asList(this.getAppBlocks().toArray(Class[]::new)));
|
||||
result.put("app-blocks", Arrays.asList(this.getAppBlocks().toArray(Class[]::new)));
|
||||
result.put("charts", Arrays.asList(this.getCharts().toArray(Class[]::new)));
|
||||
result.put("data-filters", Arrays.asList(this.getDataFilters().toArray(Class[]::new)));
|
||||
result.put("log-exporters", Arrays.asList(this.getLogExporters().toArray(Class[]::new)));
|
||||
@@ -177,11 +177,11 @@ public class RegisteredPlugin {
|
||||
pluginSubGroup = null;
|
||||
}
|
||||
|
||||
if (pluginSubGroup != null && clazz.getPackageName().startsWith(this.group()) ) {
|
||||
return this.group() + "." + clazz.getPackageName().substring(this.group().length() + 1);
|
||||
} else {
|
||||
if (pluginSubGroup == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return clazz.getPackageName();
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -328,6 +328,18 @@ public class RegisteredPlugin {
|
||||
b.append("] ");
|
||||
}
|
||||
|
||||
if (!this.getApps().isEmpty()) {
|
||||
b.append("[Apps: ");
|
||||
b.append(this.getApps().stream().map(Class::getName).collect(Collectors.joining(", ")));
|
||||
b.append("] ");
|
||||
}
|
||||
|
||||
if (!this.getAppBlocks().isEmpty()) {
|
||||
b.append("[AppBlocks: ");
|
||||
b.append(this.getAppBlocks().stream().map(Class::getName).collect(Collectors.joining(", ")));
|
||||
b.append("] ");
|
||||
}
|
||||
|
||||
if (!this.getCharts().isEmpty()) {
|
||||
b.append("[Charts: ");
|
||||
b.append(this.getCharts().stream().map(Class::getName).collect(Collectors.joining(", ")));
|
||||
|
||||
@@ -475,7 +475,9 @@ public class DefaultRunContext extends RunContext {
|
||||
logger().warn("Unable to cleanup worker task", ex);
|
||||
}
|
||||
|
||||
logger.resetMDC();
|
||||
if (logger != null){
|
||||
logger.resetMDC();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.kestra.core.repositories.ExecutionRepositoryInterface;
|
||||
import io.kestra.core.services.ExecutionService;
|
||||
import io.kestra.core.storages.Storage;
|
||||
import io.kestra.core.trace.TracerFactory;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import io.kestra.core.utils.MapUtils;
|
||||
import io.kestra.core.trace.propagation.ExecutionTextMapSetter;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
@@ -153,7 +154,7 @@ public final class ExecutableUtils {
|
||||
throw new IllegalStateException("Cannot execute an invalid flow: " + fwe.getException());
|
||||
}
|
||||
|
||||
List<Label> newLabels = inheritLabels ? new ArrayList<>(currentExecution.getLabels()) : new ArrayList<>(systemLabels(currentExecution));
|
||||
List<Label> newLabels = inheritLabels ? new ArrayList<>(filterLabels(currentExecution.getLabels(), flow)) : new ArrayList<>(systemLabels(currentExecution));
|
||||
if (labels != null) {
|
||||
labels.forEach(throwConsumer(label -> newLabels.add(new Label(runContext.render(label.key()), runContext.render(label.value())))));
|
||||
}
|
||||
@@ -201,6 +202,16 @@ public final class ExecutableUtils {
|
||||
}));
|
||||
}
|
||||
|
||||
private static List<Label> filterLabels(List<Label> labels, Flow flow) {
|
||||
if (ListUtils.isEmpty(flow.getLabels())) {
|
||||
return labels;
|
||||
}
|
||||
|
||||
return labels.stream()
|
||||
.filter(label -> flow.getLabels().stream().noneMatch(flowLabel -> flowLabel.key().equals(label.key())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Label> systemLabels(Execution execution) {
|
||||
return Streams.of(execution.getLabels())
|
||||
.filter(label -> label.key().startsWith(Label.SYSTEM_PREFIX))
|
||||
@@ -302,7 +313,10 @@ public final class ExecutableUtils {
|
||||
public static boolean isSubflow(Execution execution) {
|
||||
return execution.getTrigger() != null && (
|
||||
"io.kestra.plugin.core.flow.Subflow".equals(execution.getTrigger().getType()) ||
|
||||
"io.kestra.plugin.core.flow.ForEachItem$ForEachItemExecutable".equals(execution.getTrigger().getType())
|
||||
"io.kestra.plugin.core.flow.ForEachItem$ForEachItemExecutable".equals(execution.getTrigger().getType()) ||
|
||||
"io.kestra.core.tasks.flows.Subflow".equals(execution.getTrigger().getType()) ||
|
||||
"io.kestra.core.tasks.flows.Flow".equals(execution.getTrigger().getType()) ||
|
||||
"io.kestra.core.tasks.flows.ForEachItem$ForEachItemExecutable".equals(execution.getTrigger().getType())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ import static io.kestra.core.utils.Rethrow.throwFunction;
|
||||
public class FlowInputOutput {
|
||||
private static final Pattern URI_PATTERN = Pattern.compile("^[a-z]+:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$");
|
||||
private static final ObjectMapper YAML_MAPPER = JacksonMapper.ofYaml();
|
||||
private static final ObjectMapper JSON_MAPPER = JacksonMapper.ofJson();
|
||||
|
||||
private final StorageInterface storageInterface;
|
||||
private final Optional<String> secretKey;
|
||||
@@ -95,11 +94,12 @@ public class FlowInputOutput {
|
||||
* @return The list of {@link InputAndValue}.
|
||||
*/
|
||||
public Mono<List<InputAndValue>> validateExecutionInputs(final List<Input<?>> inputs,
|
||||
final Execution execution,
|
||||
final Publisher<CompletedPart> data) {
|
||||
final Flow flow,
|
||||
final Execution execution,
|
||||
final Publisher<CompletedPart> data) {
|
||||
if (ListUtils.isEmpty(inputs)) return Mono.just(Collections.emptyList());
|
||||
|
||||
return readData(inputs, execution, data, false).map(inputData -> resolveInputs(inputs, execution, inputData));
|
||||
return readData(inputs, execution, data, false).map(inputData -> resolveInputs(inputs, flow, execution, inputData));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,9 +111,9 @@ public class FlowInputOutput {
|
||||
* @return The Map of typed inputs.
|
||||
*/
|
||||
public Mono<Map<String, Object>> readExecutionInputs(final Flow flow,
|
||||
final Execution execution,
|
||||
final Publisher<CompletedPart> data) {
|
||||
return this.readExecutionInputs(flow.getInputs(), execution, data);
|
||||
final Execution execution,
|
||||
final Publisher<CompletedPart> data) {
|
||||
return this.readExecutionInputs(flow.getInputs(), flow, execution, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,9 +125,10 @@ public class FlowInputOutput {
|
||||
* @return The Map of typed inputs.
|
||||
*/
|
||||
public Mono<Map<String, Object>> readExecutionInputs(final List<Input<?>> inputs,
|
||||
final Flow flow,
|
||||
final Execution execution,
|
||||
final Publisher<CompletedPart> data) {
|
||||
return readData(inputs, execution, data, true).map(inputData -> this.readExecutionInputs(inputs, execution, inputData));
|
||||
return readData(inputs, execution, data, true).map(inputData -> this.readExecutionInputs(inputs, flow, execution, inputData));
|
||||
}
|
||||
|
||||
private Mono<Map<String, Object>> readData(List<Input<?>> inputs, Execution execution, Publisher<CompletedPart> data, boolean uploadFiles) {
|
||||
@@ -192,15 +193,16 @@ public class FlowInputOutput {
|
||||
final Execution execution,
|
||||
final Map<String, ?> data
|
||||
) {
|
||||
return readExecutionInputs(flow.getInputs(), execution, data);
|
||||
return readExecutionInputs(flow.getInputs(), flow, execution, data);
|
||||
}
|
||||
|
||||
private Map<String, Object> readExecutionInputs(
|
||||
final List<Input<?>> inputs,
|
||||
final Flow flow,
|
||||
final Execution execution,
|
||||
final Map<String, ?> data
|
||||
) {
|
||||
Map<String, Object> resolved = this.resolveInputs(inputs, execution, data)
|
||||
Map<String, Object> resolved = this.resolveInputs(inputs, flow, execution, data)
|
||||
.stream()
|
||||
.filter(InputAndValue::enabled)
|
||||
.map(it -> {
|
||||
@@ -225,6 +227,7 @@ public class FlowInputOutput {
|
||||
@VisibleForTesting
|
||||
public List<InputAndValue> resolveInputs(
|
||||
final List<Input<?>> inputs,
|
||||
final Flow flow,
|
||||
final Execution execution,
|
||||
final Map<String, ?> data
|
||||
) {
|
||||
@@ -240,7 +243,7 @@ public class FlowInputOutput {
|
||||
})
|
||||
.collect(Collectors.toMap(it -> it.get().input().getId(), Function.identity(), (o1, o2) -> o1, LinkedHashMap::new)));
|
||||
|
||||
resolvableInputMap.values().forEach(input -> resolveInputValue(input, execution, resolvableInputMap));
|
||||
resolvableInputMap.values().forEach(input -> resolveInputValue(input, flow, execution, resolvableInputMap));
|
||||
|
||||
return resolvableInputMap.values().stream().map(ResolvableInput::get).toList();
|
||||
}
|
||||
@@ -248,6 +251,7 @@ public class FlowInputOutput {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private InputAndValue resolveInputValue(
|
||||
final @NotNull ResolvableInput resolvable,
|
||||
final Flow flow,
|
||||
final @NotNull Execution execution,
|
||||
final @NotNull Map<String, ResolvableInput> inputs) {
|
||||
|
||||
@@ -258,8 +262,8 @@ public class FlowInputOutput {
|
||||
|
||||
try {
|
||||
// resolve all input dependencies and check whether input is enabled
|
||||
final Map<String, InputAndValue> dependencies = resolveAllDependentInputs(input, execution, inputs);
|
||||
final RunContext runContext = buildRunContextForExecutionAndInputs(execution, dependencies);
|
||||
final Map<String, InputAndValue> dependencies = resolveAllDependentInputs(input, flow, execution, inputs);
|
||||
final RunContext runContext = buildRunContextForExecutionAndInputs(flow, execution, dependencies);
|
||||
|
||||
boolean isInputEnabled = dependencies.isEmpty() || dependencies.values().stream().allMatch(InputAndValue::enabled);
|
||||
|
||||
@@ -325,15 +329,15 @@ public class FlowInputOutput {
|
||||
return resolvable.get();
|
||||
}
|
||||
|
||||
private RunContext buildRunContextForExecutionAndInputs(Execution execution, Map<String, InputAndValue> dependencies) {
|
||||
private RunContext buildRunContextForExecutionAndInputs(final Flow flow, final Execution execution, Map<String, InputAndValue> dependencies) {
|
||||
Map<String, Object> flattenInputs = MapUtils.flattenToNestedMap(dependencies.entrySet()
|
||||
.stream()
|
||||
.collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue().value()), HashMap::putAll)
|
||||
);
|
||||
return runContextFactory.of(null, execution, vars -> vars.withInputs(flattenInputs));
|
||||
return runContextFactory.of(flow, execution, vars -> vars.withInputs(flattenInputs));
|
||||
}
|
||||
|
||||
private Map<String, InputAndValue> resolveAllDependentInputs(final Input<?> input, final Execution execution, final Map<String, ResolvableInput> inputs) {
|
||||
private Map<String, InputAndValue> resolveAllDependentInputs(final Input<?> input, final Flow flow, final Execution execution, final Map<String, ResolvableInput> inputs) {
|
||||
return Optional.ofNullable(input.getDependsOn())
|
||||
.map(DependsOn::inputs)
|
||||
.stream()
|
||||
@@ -341,7 +345,7 @@ public class FlowInputOutput {
|
||||
.filter(id -> !id.equals(input.getId()))
|
||||
.map(inputs::get)
|
||||
.filter(Objects::nonNull) // input may declare unknown or non-necessary dependencies. Let's ignore.
|
||||
.map(it -> resolveInputValue(it, execution, inputs))
|
||||
.map(it -> resolveInputValue(it, flow, execution, inputs))
|
||||
.collect(Collectors.toMap(it -> it.input().getId(), Function.identity()));
|
||||
}
|
||||
|
||||
@@ -401,34 +405,34 @@ public class FlowInputOutput {
|
||||
private Object parseType(Execution execution, Type type, String id, Type elementType, Object current) throws Exception {
|
||||
try {
|
||||
return switch (type) {
|
||||
case SELECT, ENUM, STRING, EMAIL -> current;
|
||||
case SELECT, ENUM, STRING, EMAIL -> current.toString();
|
||||
case SECRET -> {
|
||||
if (secretKey.isEmpty()) {
|
||||
throw new Exception("Unable to use a `SECRET` input/output as encryption is not configured");
|
||||
}
|
||||
yield EncryptionService.encrypt(secretKey.get(), (String) current);
|
||||
yield EncryptionService.encrypt(secretKey.get(), current.toString());
|
||||
}
|
||||
case INT -> current instanceof Integer ? current : Integer.valueOf((String) current);
|
||||
case INT -> current instanceof Integer ? current : Integer.valueOf(current.toString());
|
||||
// Assuming that after the render we must have a double/int, so we can safely use its toString representation
|
||||
case FLOAT -> current instanceof Float ? current : Float.valueOf(current.toString());
|
||||
case BOOLEAN -> current instanceof Boolean ? current : Boolean.valueOf((String) current);
|
||||
case DATETIME -> current instanceof Instant ? current : Instant.parse(((String) current));
|
||||
case DATE -> current instanceof LocalDate ? current : LocalDate.parse(((String) current));
|
||||
case TIME -> current instanceof LocalTime ? current : LocalTime.parse(((String) current));
|
||||
case DURATION -> current instanceof Duration ? current : Duration.parse(((String) current));
|
||||
case BOOLEAN -> current instanceof Boolean ? current : Boolean.valueOf(current.toString());
|
||||
case DATETIME -> current instanceof Instant ? current : Instant.parse(current.toString());
|
||||
case DATE -> current instanceof LocalDate ? current : LocalDate.parse(current.toString());
|
||||
case TIME -> current instanceof LocalTime ? current : LocalTime.parse(current.toString());
|
||||
case DURATION -> current instanceof Duration ? current : Duration.parse(current.toString());
|
||||
case FILE -> {
|
||||
URI uri = URI.create(((String) current).replace(File.separator, "/"));
|
||||
URI uri = URI.create(current.toString().replace(File.separator, "/"));
|
||||
|
||||
if (uri.getScheme() != null && uri.getScheme().equals("kestra")) {
|
||||
yield uri;
|
||||
} else {
|
||||
yield storageInterface.from(execution, id, new File(((String) current)));
|
||||
yield storageInterface.from(execution, id, new File(current.toString()));
|
||||
}
|
||||
}
|
||||
case JSON -> JacksonMapper.toObject(((String) current));
|
||||
case YAML -> YAML_MAPPER.readValue((String) current, JacksonMapper.OBJECT_TYPE_REFERENCE);
|
||||
case JSON -> JacksonMapper.toObject(current.toString());
|
||||
case YAML -> YAML_MAPPER.readValue(current.toString(), JacksonMapper.OBJECT_TYPE_REFERENCE);
|
||||
case URI -> {
|
||||
Matcher matcher = URI_PATTERN.matcher((String) current);
|
||||
Matcher matcher = URI_PATTERN.matcher(current.toString());
|
||||
if (matcher.matches()) {
|
||||
yield current;
|
||||
} else {
|
||||
|
||||
@@ -838,6 +838,8 @@ public class Worker implements Service, Runnable, AutoCloseable {
|
||||
);
|
||||
} catch(Exception e) {
|
||||
// should only occur if it fails in the tracing code which should be unexpected
|
||||
// we add the exception to have some log in that case
|
||||
workerJobCallable.exception = e;
|
||||
return State.Type.FAILED;
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
@@ -1016,6 +1018,17 @@ public class Worker implements Service, Runnable, AutoCloseable {
|
||||
|
||||
@VisibleForTesting
|
||||
public void shutdown() {
|
||||
// initiate shutdown
|
||||
shutdown.compareAndSet(false, true);
|
||||
|
||||
try {
|
||||
// close the WorkerJob queue to stop receiving new JobTask execution.
|
||||
workerJobQueue.close();
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to close the WorkerJobQueue");
|
||||
}
|
||||
|
||||
// close all queues and shutdown now
|
||||
this.receiveCancellations.forEach(Runnable::run);
|
||||
this.executorService.shutdownNow();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package io.kestra.core.runners.pebble.functions;
|
||||
|
||||
import io.kestra.core.services.FlowService;
|
||||
import io.kestra.core.utils.Slugify;
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.extension.Function;
|
||||
import io.pebbletemplates.pebble.template.EvaluationContext;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
abstract class AbstractFileFunction implements Function {
|
||||
static final String KESTRA_SCHEME = "kestra:///";
|
||||
static final String TRIGGER = "trigger";
|
||||
static final String NAMESPACE = "namespace";
|
||||
static final String TENANT_ID = "tenantId";
|
||||
static final String ID = "id";
|
||||
|
||||
private static final Pattern EXECUTION_FILE = Pattern.compile(".*/.*/executions/.*/tasks/.*/.*");
|
||||
|
||||
@Inject
|
||||
private FlowService flowService;
|
||||
|
||||
URI getUriFromThePath(Object path, int lineNumber, PebbleTemplate self) {
|
||||
if (path instanceof URI u) {
|
||||
return u;
|
||||
} else if (path instanceof String str && str.startsWith(KESTRA_SCHEME)) {
|
||||
return URI.create(str);
|
||||
} else {
|
||||
throw new PebbleException(null, "Unable to create the URI from the path " + path, lineNumber, self.getName());
|
||||
}
|
||||
}
|
||||
|
||||
boolean isFileUriValid(String namespace, String flowId, String executionId, URI path) {
|
||||
// Internal storage URI should be: kestra:///$namespace/$flowId/executions/$executionId/tasks/$taskName/$taskRunId/$random.ion or kestra:///$namespace/$flowId/executions/$executionId/trigger/$triggerName/$random.ion
|
||||
// We check that the file is for the given flow execution
|
||||
if (namespace == null || flowId == null || executionId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String authorizedBasePath = KESTRA_SCHEME + namespace.replace(".", "/") + "/" + Slugify.of(flowId) + "/executions/" + executionId + "/";
|
||||
return path.toString().startsWith(authorizedBasePath);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
String checkAllowedFileAndReturnNamespace(EvaluationContext context, URI path) {
|
||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||
Map<String, String> execution = (Map<String, String>) context.getVariable("execution");
|
||||
|
||||
// check if the file is from the current execution, the parent execution or an allowed namespaces
|
||||
boolean isFileFromCurrentExecution = isFileUriValid(flow.get(NAMESPACE), flow.get(ID), execution.get(ID), path);
|
||||
if (isFileFromCurrentExecution) {
|
||||
return flow.get(NAMESPACE);
|
||||
} else {
|
||||
if (isFileFromParentExecution(context, path)) {
|
||||
Map<String, String> trigger = (Map<String, String>) context.getVariable(TRIGGER);
|
||||
return trigger.get(NAMESPACE);
|
||||
}
|
||||
else {
|
||||
return checkIfFileFromAllowedNamespaceAndReturnIt(context, path, flow.get(TENANT_ID), flow.get(NAMESPACE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isFileFromParentExecution(EvaluationContext context, URI path) {
|
||||
if (context.getVariable(TRIGGER) != null) {
|
||||
// if there is a trigger of type execution, we also allow accessing a file from the parent execution
|
||||
Map<String, String> trigger = (Map<String, String>) context.getVariable(TRIGGER);
|
||||
|
||||
if (!isFileUriValid(trigger.get(NAMESPACE), trigger.get("flowId"), trigger.get("executionId"), path)) {
|
||||
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the parent execution");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String checkIfFileFromAllowedNamespaceAndReturnIt(EvaluationContext context, URI path, String tenantId, String fromNamespace) {
|
||||
// Extract namespace from the path, it should be of the form: kestra:///({tenantId}/){namespace}/{flowId}/executions/{executionId}/tasks/{taskId}/{taskRunId}/{fileName}'
|
||||
// To extract the namespace, we must do it step by step as tenantId, namespace and taskId can contain the words 'executions' and 'tasks'
|
||||
String namespace = path.toString().substring(KESTRA_SCHEME.length());
|
||||
if (!EXECUTION_FILE.matcher(namespace).matches()) {
|
||||
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it is not an execution file");
|
||||
}
|
||||
|
||||
// 1. remove the tenantId if existing
|
||||
if (tenantId != null) {
|
||||
namespace = namespace.substring(tenantId.length() + 1);
|
||||
}
|
||||
// 2. remove everything after tasks
|
||||
namespace = namespace.substring(0, namespace.lastIndexOf("/tasks/"));
|
||||
// 3. remove everything after executions
|
||||
namespace = namespace.substring(0, namespace.lastIndexOf("/executions/"));
|
||||
// 4. remove the flowId
|
||||
namespace = namespace.substring(0, namespace.lastIndexOf('/'));
|
||||
// 5. replace '/' with '.'
|
||||
namespace = namespace.replace("/", ".");
|
||||
|
||||
flowService.checkAllowedNamespace(tenantId, namespace, tenantId, fromNamespace);
|
||||
|
||||
return namespace;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,11 @@ package io.kestra.core.runners.pebble.functions;
|
||||
|
||||
import io.kestra.core.storages.FileAttributes;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.utils.Slugify;
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.template.EvaluationContext;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import io.pebbletemplates.pebble.extension.Function;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -16,12 +14,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class FileSizeFunction implements Function {
|
||||
public class FileSizeFunction extends AbstractFileFunction {
|
||||
private static final String ERROR_MESSAGE = "The 'fileSize' function expects an argument 'path' that is a path to the internal storage URI.";
|
||||
private static final String KESTRA_SCHEME = "kestra:///";
|
||||
private static final String TRIGGER = "trigger";
|
||||
private static final String NAMESPACE = "namespace";
|
||||
private static final String ID = "id";
|
||||
|
||||
@Inject
|
||||
private StorageInterface storageInterface;
|
||||
@@ -49,53 +43,13 @@ public class FileSizeFunction implements Function {
|
||||
|
||||
}
|
||||
|
||||
private URI getUriFromThePath(Object path, int lineNumber, PebbleTemplate self) {
|
||||
if (path instanceof URI u) {
|
||||
return u;
|
||||
} else if (path instanceof String str && str.startsWith(KESTRA_SCHEME)) {
|
||||
return URI.create(str);
|
||||
} else {
|
||||
throw new PebbleException(null, "Unable to create the URI from the path " + path, lineNumber, self.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private long getFileSizeFromInternalStorageUri(EvaluationContext context, URI path) throws IOException {
|
||||
// check if the file is from the current execution, the parent execution, or an allowed namespace
|
||||
String namespace = checkAllowedFileAndReturnNamespace(context, path);
|
||||
|
||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||
Map<String, String> execution = (Map<String, String>) context.getVariable("execution");
|
||||
|
||||
boolean isFileFromCurrentExecution = isFileUriValid(flow.get(NAMESPACE), flow.get(ID), execution.get(ID), path);
|
||||
|
||||
if (!isFileFromCurrentExecution) {
|
||||
checkIfFileFromParentExecution(context, path);
|
||||
}
|
||||
|
||||
FileAttributes fileAttributes = storageInterface.getAttributes(flow.get("tenantId"), flow.get("namespace"), path);
|
||||
FileAttributes fileAttributes = storageInterface.getAttributes(flow.get(TENANT_ID), namespace, path);
|
||||
return fileAttributes.getSize();
|
||||
}
|
||||
|
||||
private void checkIfFileFromParentExecution(EvaluationContext context, URI path) {
|
||||
if (context.getVariable(TRIGGER) != null) {
|
||||
// if there is a trigger of type execution, we also allow accessing a file from the parent execution
|
||||
Map<String, String> trigger = (Map<String, String>) context.getVariable(TRIGGER);
|
||||
|
||||
if (!isFileUriValid(trigger.get(NAMESPACE), trigger.get("flowId"), trigger.get("executionId"), path)) {
|
||||
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFileUriValid(String namespace, String flowId, String executionId, URI path) {
|
||||
// Internal storage URI should be: kestra:///$namespace/$flowId/executions/$executionId/tasks/$taskName/$taskRunId/$random.ion or kestra:///$namespace/$flowId/executions/$executionId/trigger/$triggerName/$random.ion
|
||||
// We check that the file is for the given flow execution
|
||||
if (namespace == null || flowId == null || executionId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String authorizedBasePath = KESTRA_SCHEME + namespace.replace(".", "/") + "/" + Slugify.of(flowId) + "/executions/" + executionId + "/";
|
||||
return path.toString().startsWith(authorizedBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@ package io.kestra.core.runners.pebble.functions;
|
||||
|
||||
import io.kestra.core.storages.StorageContext;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.utils.Slugify;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.extension.Function;
|
||||
import io.pebbletemplates.pebble.template.EvaluationContext;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -19,15 +16,15 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class ReadFileFunction implements Function {
|
||||
public class ReadFileFunction extends AbstractFileFunction {
|
||||
private static final String ERROR_MESSAGE = "The 'read' function expects an argument 'path' that is a path to a namespace file or an internal storage URI.";
|
||||
private static final String KESTRA_SCHEME = "kestra:///";
|
||||
|
||||
@Inject
|
||||
private StorageInterface storageInterface;
|
||||
|
||||
@Value("${kestra.server-type:}") // default to empty as tests didn't set this property
|
||||
private String serverType;
|
||||
// @Value("${kestra.server-type:}") // default to empty as tests didn't set this property
|
||||
// private String serverType;
|
||||
|
||||
@Override
|
||||
public List<String> getArgumentNames() {
|
||||
@@ -70,45 +67,20 @@ public class ReadFileFunction implements Function {
|
||||
@SuppressWarnings("unchecked")
|
||||
private String readFromNamespaceFile(EvaluationContext context, String path) throws IOException {
|
||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||
URI namespaceFile = URI.create(StorageContext.namespaceFilePrefix(flow.get("namespace")) + "/" + path);
|
||||
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), flow.get("namespace"), namespaceFile)) {
|
||||
URI namespaceFile = URI.create(StorageContext.namespaceFilePrefix(flow.get(NAMESPACE)) + "/" + path);
|
||||
try (InputStream inputStream = storageInterface.get(flow.get(TENANT_ID), flow.get(NAMESPACE), namespaceFile)) {
|
||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String readFromInternalStorageUri(EvaluationContext context, URI path) throws IOException {
|
||||
// check if the file is from the current execution, the parent execution, or an allowed namespace
|
||||
String namespace = checkAllowedFileAndReturnNamespace(context, path);
|
||||
|
||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||
Map<String, String> execution = (Map<String, String>) context.getVariable("execution");
|
||||
|
||||
// check if the file is from the current execution
|
||||
if (!validateFileUri(flow.get("namespace"), flow.get("id"), execution.get("id"), path)) {
|
||||
// if not, it can be from the parent execution, so we check if there is a trigger of type execution
|
||||
if (context.getVariable("trigger") != null) {
|
||||
// if there is a trigger of type execution, we also allow accessing a file from the parent execution
|
||||
Map<String, String> trigger = (Map<String, String>) context.getVariable("trigger");
|
||||
if (!validateFileUri(trigger.get("namespace"), trigger.get("flowId"), trigger.get("executionId"), path)) {
|
||||
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unable to read the file '" + path + "' as it didn't belong to the current execution");
|
||||
}
|
||||
}
|
||||
|
||||
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), flow.get("namespace"), path)) {
|
||||
try (InputStream inputStream = storageInterface.get(flow.get(TENANT_ID), namespace, path)) {
|
||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateFileUri(String namespace, String flowId, String executionId, URI path) {
|
||||
// Internal storage URI should be: kestra:///$namespace/$flowId/executions/$executionId/tasks/$taskName/$taskRunId/$random.ion or kestra:///$namespace/$flowId/executions/$executionId/trigger/$triggerName/$random.ion
|
||||
// We check that the file is for the given flow execution
|
||||
if (namespace == null || flowId == null || executionId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String authorizedBasePath = KESTRA_SCHEME + namespace.replace(".", "/") + "/" + Slugify.of(flowId) + "/executions/" + executionId + "/";
|
||||
return path.toString().startsWith(authorizedBasePath);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import io.kestra.core.utils.Await;
|
||||
import io.kestra.core.utils.Either;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import io.kestra.core.models.triggers.RecoverMissedSchedules;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.event.ApplicationEventPublisher;
|
||||
import io.micronaut.inject.qualifiers.Qualifiers;
|
||||
@@ -70,7 +70,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
private final QueueInterface<WorkerJob> workerTaskQueue;
|
||||
private final WorkerTriggerResultQueueInterface workerTriggerResultQueue;
|
||||
private final QueueInterface<ExecutionKilled> executionKilledQueue;
|
||||
@SuppressWarnings("rawtypes") private final Optional<QueueInterface> clusterEventQueue;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final Optional<QueueInterface> clusterEventQueue;
|
||||
protected final FlowListenersInterface flowListeners;
|
||||
private final RunContextFactory runContextFactory;
|
||||
private final RunContextInitializer runContextInitializer;
|
||||
@@ -268,6 +269,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
|
||||
flows
|
||||
.stream()
|
||||
.map(flow -> pluginDefaultService.injectDefaults(flow, log))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(flow -> flow.getTriggers() != null && !flow.getTriggers().isEmpty())
|
||||
.flatMap(flow -> flow.getTriggers().stream().filter(trigger -> trigger instanceof WorkerTriggerInterface).map(trigger -> new FlowAndTrigger(flow, trigger)))
|
||||
.forEach(flowAndTrigger -> {
|
||||
@@ -408,8 +411,20 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
private List<FlowWithTriggers> computeSchedulable(List<FlowWithSource> flows, List<Trigger> triggerContextsToEvaluate, ScheduleContextInterface scheduleContext) {
|
||||
List<String> flowToKeep = triggerContextsToEvaluate.stream().map(Trigger::getFlowId).toList();
|
||||
|
||||
triggerContextsToEvaluate.stream()
|
||||
.filter(trigger -> !flows.stream().map(FlowWithSource::uidWithoutRevision).toList().contains(Flow.uid(trigger)))
|
||||
.forEach(trigger -> {
|
||||
try {
|
||||
this.triggerState.delete(trigger);
|
||||
} catch (QueueException e) {
|
||||
log.error("Unable to delete the trigger: {}.{}.{}", trigger.getNamespace(), trigger.getFlowId(), trigger.getTriggerId(), e);
|
||||
}
|
||||
});
|
||||
|
||||
return flows
|
||||
.stream()
|
||||
.map(flow -> pluginDefaultService.injectDefaults(flow, log))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(flow -> flowToKeep.contains(flow.getId()))
|
||||
.filter(flow -> flow.getTriggers() != null && !flow.getTriggers().isEmpty())
|
||||
.filter(flow -> !flow.isDisabled() && !(flow instanceof FlowWithException))
|
||||
@@ -438,7 +453,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
logError(conditionContext, flow, abstractTrigger, e);
|
||||
return null;
|
||||
}
|
||||
this.triggerState.save(triggerContext, scheduleContext);
|
||||
this.triggerState.save(triggerContext, scheduleContext, "/kestra/services/scheduler/compute-schedulable/save/lastTrigger-nextDate-null");
|
||||
} else {
|
||||
triggerContext = lastTrigger;
|
||||
}
|
||||
@@ -565,7 +580,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
Trigger triggerRunning = Trigger.of(f.getTriggerContext(), now);
|
||||
var flowWithTrigger = f.toBuilder().triggerContext(triggerRunning).build();
|
||||
try {
|
||||
this.triggerState.save(triggerRunning, scheduleContext);
|
||||
this.triggerState.save(triggerRunning, scheduleContext, "/kestra/services/scheduler/handle/save/on-eval-true/polling");
|
||||
this.sendWorkerTriggerToWorker(flowWithTrigger);
|
||||
} catch (InternalException e) {
|
||||
logService.logTrigger(
|
||||
@@ -590,7 +605,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
schedule.nextEvaluationDate(f.getConditionContext(), Optional.of(f.getTriggerContext()))
|
||||
);
|
||||
trigger = trigger.checkBackfill();
|
||||
this.triggerState.save(trigger, scheduleContext);
|
||||
this.triggerState.save(trigger, scheduleContext, "/kestra/services/scheduler/handle/save/on-eval-true/schedule");
|
||||
}
|
||||
} else {
|
||||
logService.logTrigger(
|
||||
@@ -608,7 +623,7 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
logError(f, e);
|
||||
}
|
||||
var trigger = f.getTriggerContext().toBuilder().nextExecutionDate(nextExecutionDate).build().checkBackfill();
|
||||
this.triggerState.save(trigger, scheduleContext);
|
||||
this.triggerState.save(trigger, scheduleContext, "/kestra/services/scheduler/handle/save/on-eval-false");
|
||||
}
|
||||
} catch (Exception ie) {
|
||||
// validate schedule condition can fail to render variables
|
||||
@@ -625,13 +640,14 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
.build();
|
||||
ZonedDateTime nextExecutionDate = this.nextEvaluationDate(f.getAbstractTrigger());
|
||||
var trigger = f.getTriggerContext().resetExecution(State.Type.FAILED, nextExecutionDate);
|
||||
this.saveLastTriggerAndEmitExecution(execution, trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext));
|
||||
this.saveLastTriggerAndEmitExecution(execution, trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext, "/kestra/services/scheduler/handle/save/on-error"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleEvaluateWorkerTriggerResult(SchedulerExecutionWithTrigger result, ZonedDateTime nextExecutionDate) {
|
||||
private void handleEvaluateWorkerTriggerResult(SchedulerExecutionWithTrigger result, ZonedDateTime
|
||||
nextExecutionDate) {
|
||||
Optional.ofNullable(result)
|
||||
.ifPresent(executionWithTrigger -> {
|
||||
log(executionWithTrigger);
|
||||
@@ -649,7 +665,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
);
|
||||
}
|
||||
|
||||
private void handleEvaluateSchedulingTriggerResult(Schedulable schedule, SchedulerExecutionWithTrigger result, ConditionContext conditionContext, ScheduleContextInterface scheduleContext) throws Exception {
|
||||
private void handleEvaluateSchedulingTriggerResult(Schedulable schedule, SchedulerExecutionWithTrigger
|
||||
result, ConditionContext conditionContext, ScheduleContextInterface scheduleContext) throws Exception {
|
||||
log(result);
|
||||
Trigger trigger = Trigger.of(
|
||||
result.getTriggerContext(),
|
||||
@@ -665,10 +682,11 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
|
||||
// Schedule triggers are being executed directly from the handle method within the context where triggers are locked.
|
||||
// So we must save them by passing the scheduleContext.
|
||||
this.saveLastTriggerAndEmitExecution(result.getExecution(), trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext));
|
||||
this.saveLastTriggerAndEmitExecution(result.getExecution(), trigger, triggerToSave -> this.triggerState.save(triggerToSave, scheduleContext, "/kestra/services/scheduler/handleEvaluateSchedulingTriggerResult/save"));
|
||||
}
|
||||
|
||||
protected void saveLastTriggerAndEmitExecution(Execution execution, Trigger trigger, Consumer<Trigger> saveAction) {
|
||||
protected void saveLastTriggerAndEmitExecution(Execution execution, Trigger
|
||||
trigger, Consumer<Trigger> saveAction) {
|
||||
saveAction.accept(trigger);
|
||||
this.emitExecution(execution, trigger);
|
||||
}
|
||||
@@ -844,7 +862,8 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void logError(ConditionContext conditionContext, FlowWithSource flow, AbstractTrigger trigger, Throwable e) {
|
||||
private void logError(ConditionContext conditionContext, FlowWithSource flow, AbstractTrigger
|
||||
trigger, Throwable e) {
|
||||
Logger logger = conditionContext.getRunContext().logger();
|
||||
|
||||
logService.logFlow(
|
||||
@@ -1017,4 +1036,12 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
public ServiceState getState() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
protected Trigger resetExecution(FlowWithSource flow, Execution execution, Trigger trigger) {
|
||||
Flow flowWithDefaults = pluginDefaultService.injectDefaults(flow, execution);
|
||||
RunContext runContext = runContextFactory.of(flowWithDefaults, flowWithDefaults.findTriggerByTriggerId(trigger.getTriggerId()));
|
||||
ConditionContext conditionContext = conditionService.conditionContext(runContext, flowWithDefaults, null);
|
||||
|
||||
return trigger.resetExecution(flowWithDefaults, execution, conditionContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.core.models.triggers.TriggerContext;
|
||||
import io.kestra.core.queues.QueueException;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -21,11 +22,18 @@ public interface SchedulerTriggerStateInterface {
|
||||
|
||||
Trigger create(Trigger trigger) throws ConstraintViolationException;
|
||||
|
||||
Trigger save(Trigger trigger, ScheduleContextInterface scheduleContext, String headerContent) throws ConstraintViolationException;
|
||||
|
||||
Trigger create(Trigger trigger, String headerContent) throws ConstraintViolationException;
|
||||
|
||||
Trigger update(Trigger trigger);
|
||||
|
||||
Trigger update(Flow flow, AbstractTrigger abstractTrigger, ConditionContext conditionContext) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* QueueException required for Kafka implementation
|
||||
*/
|
||||
void delete(Trigger trigger) throws QueueException;
|
||||
/**
|
||||
* Used by the JDBC implementation: find triggers in all tenants.
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.kestra.core.storages.StorageContext;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.utils.GraphUtils;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import io.kestra.plugin.core.flow.Pause;
|
||||
import io.kestra.plugin.core.flow.WorkingDirectory;
|
||||
import io.micronaut.context.event.ApplicationEventPublisher;
|
||||
@@ -214,7 +215,7 @@ public class ExecutionService {
|
||||
execution.withState(State.Type.RESTARTED).getState()
|
||||
);
|
||||
|
||||
List<Label> newLabels = new ArrayList<>(execution.getLabels());
|
||||
List<Label> newLabels = new ArrayList<>(ListUtils.emptyOnNull(execution.getLabels()));
|
||||
if (!newLabels.contains(new Label(Label.RESTARTED, "true"))) {
|
||||
newLabels.add(new Label(Label.RESTARTED, "true"));
|
||||
}
|
||||
@@ -297,7 +298,7 @@ public class ExecutionService {
|
||||
taskRunId == null ? new State() : execution.withState(State.Type.RESTARTED).getState()
|
||||
);
|
||||
|
||||
List<Label> newLabels = new ArrayList<>(execution.getLabels());
|
||||
List<Label> newLabels = new ArrayList<>(ListUtils.emptyOnNull(execution.getLabels()));
|
||||
if (!newLabels.contains(new Label(Label.REPLAY, "true"))) {
|
||||
newLabels.add(new Label(Label.REPLAY, "true"));
|
||||
}
|
||||
@@ -486,7 +487,7 @@ public class ExecutionService {
|
||||
return getFirstPausedTaskOr(execution, flow)
|
||||
.flatMap(task -> {
|
||||
if (task.isPresent() && task.get() instanceof Pause pauseTask) {
|
||||
return Mono.just(flowInputOutput.resolveInputs(pauseTask.getOnResume(), execution, Map.of()));
|
||||
return Mono.just(flowInputOutput.resolveInputs(pauseTask.getOnResume(), flow, execution, Map.of()));
|
||||
} else {
|
||||
return Mono.just(Collections.emptyList());
|
||||
}
|
||||
@@ -507,7 +508,7 @@ public class ExecutionService {
|
||||
return getFirstPausedTaskOr(execution, flow)
|
||||
.flatMap(task -> {
|
||||
if (task.isPresent() && task.get() instanceof Pause pauseTask) {
|
||||
return flowInputOutput.validateExecutionInputs(pauseTask.getOnResume(), execution, inputs);
|
||||
return flowInputOutput.validateExecutionInputs(pauseTask.getOnResume(), flow, execution, inputs);
|
||||
} else {
|
||||
return Mono.just(Collections.emptyList());
|
||||
}
|
||||
@@ -528,7 +529,7 @@ public class ExecutionService {
|
||||
return getFirstPausedTaskOr(execution, flow)
|
||||
.flatMap(task -> {
|
||||
if (task.isPresent() && task.get() instanceof Pause pauseTask) {
|
||||
return flowInputOutput.readExecutionInputs(pauseTask.getOnResume(), execution, inputs);
|
||||
return flowInputOutput.readExecutionInputs(pauseTask.getOnResume(), flow, execution, inputs);
|
||||
} else {
|
||||
return Mono.just(Collections.<String, Object>emptyMap());
|
||||
}
|
||||
|
||||
@@ -128,21 +128,13 @@ public class FlowService {
|
||||
return deprecationTraversal("", flow).toList();
|
||||
}
|
||||
|
||||
public List<String> warnings(Flow flow) {
|
||||
|
||||
public List<String> warnings(Flow flow, String tenantId) {
|
||||
if (flow == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> warnings = new ArrayList<>();
|
||||
List<io.kestra.plugin.core.trigger.Flow> flowTriggers = ListUtils.emptyOnNull(flow.getTriggers()).stream()
|
||||
.filter(io.kestra.plugin.core.trigger.Flow.class::isInstance)
|
||||
.map(io.kestra.plugin.core.trigger.Flow.class::cast)
|
||||
.toList();
|
||||
flowTriggers.forEach(flowTrigger -> {
|
||||
if (ListUtils.emptyOnNull(flowTrigger.getConditions()).isEmpty() && flowTrigger.getPreconditions() == null) {
|
||||
warnings.add("This flow will be triggered for EVERY execution of EVERY flow on your instance. We recommend adding the preconditions property to the Flow trigger '" + flowTrigger.getId() + "'.");
|
||||
}
|
||||
});
|
||||
List<String> warnings = new ArrayList<>(checkValidSubflows(flow, tenantId));
|
||||
|
||||
return warnings;
|
||||
}
|
||||
@@ -161,29 +153,31 @@ public class FlowService {
|
||||
}
|
||||
|
||||
// check if subflow is present in given namespace
|
||||
public void checkValidSubflows(Flow flow) {
|
||||
public List<String> checkValidSubflows(Flow flow, String tenantId) {
|
||||
List<io.kestra.plugin.core.flow.Subflow> subFlows = ListUtils.emptyOnNull(flow.getTasks()).stream()
|
||||
.filter(io.kestra.plugin.core.flow.Subflow.class::isInstance)
|
||||
.map(io.kestra.plugin.core.flow.Subflow.class::cast)
|
||||
.toList();
|
||||
|
||||
Set<ConstraintViolation<?>> violations = new HashSet<>();
|
||||
List<String> violations = new ArrayList<>();
|
||||
|
||||
subFlows.forEach(subflow -> {
|
||||
Optional<Flow> optional = findById(flow.getTenantId(), subflow.getNamespace(), subflow.getFlowId());
|
||||
String regex = ".*\\{\\{.+}}.*"; // regex to check if string contains pebble
|
||||
String subflowId = subflow.getFlowId();
|
||||
String namespace = subflow.getNamespace();
|
||||
if (subflowId.matches(regex) || namespace.matches(regex)) {
|
||||
return;
|
||||
}
|
||||
Optional<Flow> optional = findById(tenantId, subflow.getNamespace(), subflow.getFlowId());
|
||||
|
||||
violations.add(ManualConstraintViolation.of(
|
||||
"The subflow '" + subflow.getFlowId() + "' not found in namespace '" + subflow.getNamespace() + "'.",
|
||||
flow,
|
||||
Flow.class,
|
||||
"flow.tasks",
|
||||
flow.getNamespace()
|
||||
));
|
||||
if (optional.isEmpty()) {
|
||||
violations.add("The subflow '" + subflow.getFlowId() + "' not found in namespace '" + subflow.getNamespace() + "'.");
|
||||
} else if (optional.get().isDisabled()) {
|
||||
violations.add("The subflow '" + subflow.getFlowId() + "' is disabled in namespace '" + subflow.getNamespace() + "'.");
|
||||
}
|
||||
});
|
||||
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
public record Relocation(String from, String to) {}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -54,9 +55,9 @@ public final class LabelService {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsAll(List<Label> labelsContainer, List<Label> labelsThatMustBeIncluded) {
|
||||
Map<String, String> labelsContainerMap = labelsContainer.stream().collect(HashMap::new, (m, label)-> m.put(label.key(), label.value()), HashMap::putAll);
|
||||
public static boolean containsAll(@Nullable List<Label> labelsContainer, @Nullable List<Label> labelsThatMustBeIncluded) {
|
||||
Map<String, String> labelsContainerMap = ListUtils.emptyOnNull(labelsContainer).stream().collect(HashMap::new, (m, label)-> m.put(label.key(), label.value()), HashMap::putAll);
|
||||
|
||||
return labelsThatMustBeIncluded.stream().allMatch(label -> Objects.equals(labelsContainerMap.get(label.key()), label.value()));
|
||||
return ListUtils.emptyOnNull(labelsThatMustBeIncluded).stream().allMatch(label -> Objects.equals(labelsContainerMap.get(label.key()), label.value()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import io.micronaut.validation.validator.constraints.ConstraintValidator;
|
||||
import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import static io.kestra.core.models.triggers.TimeWindow.Type.DURATION_WINDOW;
|
||||
|
||||
@Singleton
|
||||
@Introspected
|
||||
public class TimeWindowValidator implements ConstraintValidator<TimeWindowValidation, TimeWindow> {
|
||||
@@ -23,7 +25,8 @@ public class TimeWindowValidator implements ConstraintValidator<TimeWindowValida
|
||||
return true;
|
||||
}
|
||||
|
||||
return switch (value.getType()) {
|
||||
TimeWindow.Type type = value.getType() != null ? value.getType() : DURATION_WINDOW;
|
||||
return switch (type) {
|
||||
case DAILY_TIME_DEADLINE -> {
|
||||
if (value.getWindow() != null || value.getWindowAdvance() != null || value.getStartTime() != null || value.getEndTime() != null) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
|
||||
@@ -28,9 +28,9 @@ import java.util.*;
|
||||
@Schema(
|
||||
title = "Run a flow if the list of preconditions are met in a time window.",
|
||||
description = """
|
||||
**This task is deprecated**, use io.kestra.plugin.core.condition.ExecutionsWindow or io.kestra.plugin.core.condition.FilteredExecutionsWindow instead.
|
||||
**This task is deprecated**, use the `preconditions` property of the `io.kestra.plugin.core.trigger.Flow` trigger instead.
|
||||
Will trigger an executions when all the flows defined by the preconditions are successfully executed in a specific period of time.
|
||||
The period is defined by the `timeSLA` property and is by default a duration window of 24 hours."""
|
||||
The period is defined by the `timeWindow` property and is by default a duration window of 24 hours."""
|
||||
)
|
||||
@Plugin(
|
||||
examples = {
|
||||
@@ -47,7 +47,7 @@ import java.util.*;
|
||||
" - SUCCESS",
|
||||
" - id: multiple",
|
||||
" type: io.kestra.plugin.core.condition.MultipleCondition",
|
||||
" sla:",
|
||||
" timeWindow:",
|
||||
" window: PT12H",
|
||||
" conditions:",
|
||||
" flow-a:",
|
||||
@@ -102,7 +102,7 @@ public class MultipleCondition extends Condition implements io.kestra.core.model
|
||||
|
||||
@Schema(
|
||||
title = "The duration of the window",
|
||||
description = "Deprecated, use `timeSLA.window` instead.")
|
||||
description = "Deprecated, use `timeWindow.window` instead.")
|
||||
@PluginProperty
|
||||
@Deprecated
|
||||
private Duration window;
|
||||
@@ -114,7 +114,7 @@ public class MultipleCondition extends Condition implements io.kestra.core.model
|
||||
|
||||
@Schema(
|
||||
title = "The window advance duration",
|
||||
description = "Deprecated, use `timeSLA.windowAdvance` instead.")
|
||||
description = "Deprecated, use `timeWindow.windowAdvance` instead.")
|
||||
@PluginProperty
|
||||
@Deprecated
|
||||
private Duration windowAdvance;
|
||||
|
||||
@@ -37,8 +37,8 @@ import static io.kestra.core.utils.Rethrow.throwPredicate;
|
||||
"- conditions:",
|
||||
" - type: io.kestra.plugin.core.condition.Not",
|
||||
" conditions:",
|
||||
" - type: io.kestra.plugin.core.condition.DateBetween",
|
||||
" after: \"2013-09-08T16:19:12\"",
|
||||
" - type: io.kestra.plugin.core.condition.DateTimeBetween",
|
||||
" after: \"2013-09-08T16:19:12Z\"",
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@PluginSubGroup(categories = PluginSubGroup.PluginCategory.CORE)
|
||||
package io.kestra.plugin.core.dashboard.chart;
|
||||
|
||||
import io.kestra.core.models.annotations.PluginSubGroup;
|
||||
@@ -0,0 +1,4 @@
|
||||
@PluginSubGroup(title = "Data Filter", categories = PluginSubGroup.PluginCategory.CORE)
|
||||
package io.kestra.plugin.core.dashboard.data;
|
||||
|
||||
import io.kestra.core.models.annotations.PluginSubGroup;
|
||||
@@ -68,8 +68,8 @@ public class Return extends Task implements RunnableTask<Return.Output> {
|
||||
long end = System.nanoTime();
|
||||
|
||||
runContext
|
||||
.metric(Counter.of("length", Optional.ofNullable(render).map(String::length).orElse(0), "format", render))
|
||||
.metric(Timer.of("duration", Duration.ofNanos(end - start), "format", render));
|
||||
.metric(Counter.of("length", Optional.ofNullable(render).map(String::length).orElse(0)))
|
||||
.metric(Timer.of("duration", Duration.ofNanos(end - start)));
|
||||
|
||||
return Output.builder()
|
||||
.value(render)
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
@@ -57,7 +56,8 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
|
||||
|
||||
protected Property<Map<CharSequence, CharSequence>> headers;
|
||||
|
||||
protected HttpConfiguration options;
|
||||
@Builder.Default
|
||||
protected HttpConfiguration options = HttpConfiguration.builder().build();
|
||||
|
||||
@Deprecated
|
||||
@Schema(
|
||||
@@ -72,10 +72,7 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
|
||||
this.options = HttpConfiguration.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
this.options = this.options.toBuilder()
|
||||
.allowFailed(allowFailed)
|
||||
.build();
|
||||
this.options.setAllowFailed(allowFailed);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -89,9 +86,7 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
|
||||
}
|
||||
|
||||
this.sslOptions = sslOptions;
|
||||
this.options = this.options.toBuilder()
|
||||
.ssl(sslOptions)
|
||||
.build();
|
||||
this.options.setSsl(sslOptions);
|
||||
}
|
||||
|
||||
protected HttpClient client(RunContext runContext) throws IllegalVariableEvaluationException, MalformedURLException, URISyntaxException {
|
||||
@@ -103,9 +98,11 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected HttpRequest request(RunContext runContext) throws IllegalVariableEvaluationException, URISyntaxException, IOException {
|
||||
// ideally we should URLEncode the path of the UI, but as we cannot URLEncode everything, we handle the common case of space in the URI.
|
||||
String renderedUri = runContext.render(this.uri).as(String.class).map(s -> s.replace(" ", "%20")).orElseThrow();
|
||||
HttpRequest.HttpRequestBuilder request = HttpRequest.builder()
|
||||
.method(runContext.render(this.method).as(String.class).orElse(null))
|
||||
.uri(new URI(runContext.render(this.uri).as(String.class).orElseThrow()));
|
||||
.uri(new URI(renderedUri));
|
||||
|
||||
var renderedFormData = runContext.render(this.formData).asMap(String.class, Object.class);
|
||||
if (!renderedFormData.isEmpty()) {
|
||||
@@ -157,9 +154,11 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
|
||||
request.body(HttpRequest.StringRequestBody.builder()
|
||||
.content(runContext.render(body).as(String.class).orElseThrow())
|
||||
.contentType(runContext.render(this.contentType).as(String.class).orElse(null))
|
||||
.charset(this.options != null ? runContext.render(this.options.getDefaultCharset()).as(Charset.class).orElse(null) : StandardCharsets.UTF_8)
|
||||
.charset(this.options != null && this.options.getDefaultCharset() != null ? runContext.render(this.options.getDefaultCharset()).as(Charset.class).orElse(null) : null)
|
||||
.build()
|
||||
);
|
||||
} else if (this.contentType != null) {
|
||||
request.addHeader("Content-Type", runContext.render(this.contentType).as(String.class).orElse(null));
|
||||
}
|
||||
|
||||
var renderedHeader = runContext.render(this.headers).asMap(CharSequence.class, CharSequence.class);
|
||||
@@ -178,6 +177,7 @@ public abstract class AbstractHttp extends Task implements HttpInterface {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return request.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,13 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
|
||||
RunContext runContext = conditionContext.getRunContext();
|
||||
Logger logger = runContext.logger();
|
||||
|
||||
if (this.options == null){
|
||||
this.options = HttpConfiguration.builder().build();
|
||||
}
|
||||
// we allow failed status code as it is the condition that must determine whether we trigger the flow
|
||||
options.setAllowFailed(Property.of(true));
|
||||
options.setSsl(this.options.getSsl() != null ? this.options.getSsl() : this.sslOptions);
|
||||
|
||||
var request = Request.builder()
|
||||
.uri(this.uri)
|
||||
.method(this.method)
|
||||
@@ -142,12 +149,7 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
|
||||
.formData(this.formData)
|
||||
.contentType(this.contentType)
|
||||
.headers(this.headers)
|
||||
.options((this.options == null ? HttpConfiguration.builder() : this.options.toBuilder())
|
||||
// we allow failed status code as it is the condition that must determine whether we trigger the flow
|
||||
.allowFailed(Property.of(true))
|
||||
.ssl(this.options != null && this.options.getSsl() != null ? this.options.getSsl() : this.sslOptions)
|
||||
.build()
|
||||
)
|
||||
.options(this.options)
|
||||
.encryptBody(this.encryptBody)
|
||||
.build();
|
||||
var output = request.run(runContext);
|
||||
@@ -185,8 +187,6 @@ public class Trigger extends AbstractTrigger implements PollingTriggerInterface,
|
||||
}
|
||||
|
||||
this.sslOptions = sslOptions;
|
||||
this.options = this.options.toBuilder()
|
||||
.ssl(sslOptions)
|
||||
.build();
|
||||
this.options.setSsl(sslOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class PurgeLogs extends Task implements RunnableTask<PurgeLogs.Output> {
|
||||
flowService.checkAllowedNamespace(flowInfo.tenantId(), runContext.render(namespace).as(String.class).orElse(null), flowInfo.tenantId(), flowInfo.namespace());
|
||||
}
|
||||
|
||||
var logLevelsRendered = runContext.render(this.logLevels).asList(String.class);
|
||||
var logLevelsRendered = runContext.render(this.logLevels).asList(Level.class);
|
||||
var renderedDate = runContext.render(startDate).as(String.class).orElse(null);
|
||||
int deleted = logService.purge(
|
||||
flowInfo.tenantId(),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.kestra.plugin.core.runner;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.annotations.Example;
|
||||
import io.kestra.core.models.annotations.Plugin;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.models.tasks.runners.*;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import io.micronaut.core.annotation.Introspected;
|
||||
@@ -14,6 +16,7 @@ import lombok.experimental.SuperBuilder;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -133,11 +136,14 @@ public class Process extends TaskRunner<TaskRunnerDetailResult> {
|
||||
environment.putAll(this.env(runContext, taskCommands));
|
||||
|
||||
processBuilder.directory(taskCommands.getWorkingDirectory().toFile());
|
||||
processBuilder.command(taskCommands.getCommands());
|
||||
|
||||
List<String> renderedCommands = runContext.render(taskCommands.getCommands()).asList(String.class);
|
||||
|
||||
processBuilder.command(renderedCommands);
|
||||
|
||||
java.lang.Process process = processBuilder.start();
|
||||
long pid = process.pid();
|
||||
logger.debug("Starting command with pid {} [{}]", pid, String.join(" ", taskCommands.getCommands()));
|
||||
logger.debug("Starting command with pid {} [{}]", pid, String.join(" ", renderedCommands));
|
||||
|
||||
LogRunnable stdOutRunnable = new LogRunnable(process.getInputStream(), defaultLogConsumer, false);
|
||||
LogRunnable stdErrRunnable = new LogRunnable(process.getErrorStream(), defaultLogConsumer, true);
|
||||
|
||||
@@ -149,7 +149,7 @@ class ClassPluginDocumentationTest {
|
||||
assertThat(oneOf.getFirst().get("type"), is("integer"));
|
||||
assertThat(oneOf.getFirst().get("$dynamic"), is(true));
|
||||
assertThat(oneOf.get(1).get("type"), is("string"));
|
||||
assertThat(oneOf.get(1).get("pattern"), is(".*{{.*}}.*"));
|
||||
// assertThat(oneOf.get(1).get("pattern"), is(".*{{.*}}.*"));
|
||||
|
||||
Map<String, Object> withDefault = (Map<String, Object>) properties.get("withDefault");
|
||||
assertThat(withDefault.get("type"), is("string"));
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.kestra.plugin.core.flow.Dag;
|
||||
import io.kestra.plugin.core.log.Log;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.hamcrest.Matchers;
|
||||
@@ -238,6 +239,15 @@ class JsonSchemaGeneratorTest {
|
||||
assertThat(((Map<String, Map<String, Object>>) generate.get("properties")).get("beta").get("$beta"), is(true));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void requiredAreRemovedIfThereIsADefault() {
|
||||
Map<String, Object> generate = jsonSchemaGenerator.properties(Task.class, RequiredWithDefault.class);
|
||||
assertThat(generate, is(not(nullValue())));
|
||||
assertThat((List<String>) generate.get("required"), not(containsInAnyOrder("requiredWithDefault")));
|
||||
assertThat((List<String>) generate.get("required"), containsInAnyOrder("requiredWithNoDefault"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void dashboard() throws URISyntaxException {
|
||||
@@ -324,6 +334,7 @@ class JsonSchemaGeneratorTest {
|
||||
}
|
||||
|
||||
@Schema(title = "Test class")
|
||||
@Builder
|
||||
private static class TestClass {
|
||||
@Schema(title = "Test property")
|
||||
public String testProperty;
|
||||
@@ -360,4 +371,21 @@ class JsonSchemaGeneratorTest {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuperBuilder
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@Plugin
|
||||
public static class RequiredWithDefault extends Task {
|
||||
@PluginProperty
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
private Property<TaskWithEnum.TestClass> requiredWithDefault = Property.of(TaskWithEnum.TestClass.builder().testProperty("test").build());
|
||||
|
||||
@PluginProperty
|
||||
@NotNull
|
||||
private Property<TaskWithEnum.TestClass> requiredWithNoDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +351,21 @@ class HttpClientTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void specialContentType() throws IllegalVariableEvaluationException, HttpClientException, IOException {
|
||||
try (HttpClient client = client()) {
|
||||
HttpResponse<String> response = client.request(
|
||||
HttpRequest.of(URI.create(embeddedServerUri + "/http/content-type"), Map.of(
|
||||
"Content-Type", List.of("application/vnd.campaignsexport.v1+json")
|
||||
)),
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(response.getStatus().getCode(), is(200));
|
||||
assertThat(response.getBody(), is("application/vnd.campaignsexport.v1+json"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProxy() throws IllegalVariableEvaluationException, HttpClientException, IOException {
|
||||
try (HttpClient client = client(b -> b
|
||||
@@ -383,6 +398,12 @@ class HttpClientTest {
|
||||
return io.micronaut.http.HttpResponse.ok("pong");
|
||||
}
|
||||
|
||||
@Get("content-type")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public io.micronaut.http.HttpResponse<String> contentType(io.micronaut.http.HttpRequest<?> request) {
|
||||
return io.micronaut.http.HttpResponse.ok(request.getContentType().orElseThrow().toString());
|
||||
}
|
||||
|
||||
@Get("json")
|
||||
public io.micronaut.http.HttpResponse<Object> json(@QueryValue(defaultValue = "false") Boolean array) {
|
||||
return io.micronaut.http.HttpResponse.ok(array ? List.of(1, 2, 3) : Map.of("ping", "pong"));
|
||||
|
||||
@@ -298,6 +298,30 @@ class PropertyTest {
|
||||
assertThat(output.getMap().get("mapKey2"), is("mapValue2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aListToRender() throws Exception {
|
||||
var task = DynamicPropertyExampleTask.builder()
|
||||
.items(new Property<>("""
|
||||
["python test.py --input1 \\"{{ item1 }}\\" --input2 \\"{{ item2 }}\\"", "'gs://{{ renderOnce(\\"bucket\\") }}/{{ 'table' }}/{{ 'file' }}_*.csv.gz'"]"""))
|
||||
.properties(new Property<>("""
|
||||
{
|
||||
"key1": "{{value1}}",
|
||||
"key2": "{{value2}}"
|
||||
}"""))
|
||||
.build();
|
||||
var runContext = runContextFactory.of(Map.ofEntries(
|
||||
entry("item1", "item1"),
|
||||
entry("item2", "item2"),
|
||||
entry("value1", "value1"),
|
||||
entry("value2", "value2")
|
||||
));
|
||||
|
||||
var output = task.run(runContext);
|
||||
|
||||
assertThat(output, notNullValue());
|
||||
assertThat(output.getList(), containsInAnyOrder("python test.py --input1 \"item1\" --input2 \"item2\"", "'gs://bucket/table/file_*.csv.gz'"));
|
||||
}
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
private static class TestObj {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.kestra.core.models.tasks.runners;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.runners.RunContext;
|
||||
import io.kestra.core.runners.RunContextFactory;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
@@ -149,7 +150,17 @@ public class TaskRunnerTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCommands() {
|
||||
public Property<List<String>> getInterpreter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property<List<String>> getBeforeCommands() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property<List<String>> getCommands() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class FlowInputOutputTest {
|
||||
Map<String, Object> data = Map.of("input1", "value1", "input2", "value2");
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(
|
||||
@@ -98,7 +98,7 @@ class FlowInputOutputTest {
|
||||
Map<String, Object> data = Map.of("input1", "v1", "input2", "v2", "input3", "v3");
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(
|
||||
@@ -132,7 +132,7 @@ class FlowInputOutputTest {
|
||||
Map<String, Object> data = Map.of("input1", "v1", "input2", "v2", "input3", "v3");
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(
|
||||
@@ -162,7 +162,7 @@ class FlowInputOutputTest {
|
||||
Map<String, Object> data = Map.of("input1", "value1", "input2", "value2");
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(
|
||||
@@ -191,7 +191,7 @@ class FlowInputOutputTest {
|
||||
Map<String, Object> data = Map.of("input1", "value1", "input2", "value2");
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(2, values.size());
|
||||
@@ -211,7 +211,7 @@ class FlowInputOutputTest {
|
||||
Publisher<CompletedPart> data = Mono.just(new MemoryCompletedFileUpload("input", "input", "???".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.validateExecutionInputs(List.of(input), DEFAULT_TEST_EXECUTION, data).block();
|
||||
List<InputAndValue> values = flowInputOutput.validateExecutionInputs(List.of(input), null, DEFAULT_TEST_EXECUTION, data).block();
|
||||
|
||||
// Then
|
||||
Assertions.assertNull(values.getFirst().exception());
|
||||
@@ -238,7 +238,7 @@ class FlowInputOutputTest {
|
||||
Map<String, Object> data = Map.of("input42", "foo");
|
||||
|
||||
// When
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, DEFAULT_TEST_EXECUTION, data);
|
||||
List<InputAndValue> values = flowInputOutput.resolveInputs(inputs, null, DEFAULT_TEST_EXECUTION, data);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(
|
||||
|
||||
@@ -73,7 +73,7 @@ public class FileSizeFunctionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentException_givenMissingTrigger_andParentExecution() throws IOException {
|
||||
void shouldReadFromAnotherExecution() throws IOException, IllegalVariableEvaluationException {
|
||||
String executionId = IdUtils.create();
|
||||
URI internalStorageURI = getInternalStorageURI(executionId);
|
||||
URI internalStorageFile = getInternalStorageFile(internalStorageURI);
|
||||
@@ -85,12 +85,8 @@ public class FileSizeFunctionTest {
|
||||
"execution", Map.of("id", IdUtils.create())
|
||||
);
|
||||
|
||||
Exception ex = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> variableRenderer.render("{{ fileSize('" + internalStorageFile + "') }}", variables)
|
||||
);
|
||||
|
||||
assertTrue(ex.getMessage().startsWith("Unable to read the file"), "Exception message doesn't match expected one");
|
||||
String size = variableRenderer.render("{{ fileSize('" + internalStorageFile + "') }}", variables);
|
||||
assertThat(size, is(FILE_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -125,14 +125,13 @@ class ReadFileFunctionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void readUnauthorizedInternalStorageFile() throws IOException {
|
||||
void readInternalStorageFileFromAnotherExecution() throws IOException, IllegalVariableEvaluationException {
|
||||
String namespace = "my.namespace";
|
||||
String flowId = "flow";
|
||||
String executionId = IdUtils.create();
|
||||
URI internalStorageURI = URI.create("/" + namespace.replace(".", "/") + "/" + flowId + "/executions/" + executionId + "/tasks/task/" + IdUtils.create() + "/123456.ion");
|
||||
URI internalStorageFile = storageInterface.put(null, namespace, internalStorageURI, new ByteArrayInputStream("Hello from a task output".getBytes()));
|
||||
|
||||
// test for an un-authorized execution with no trigger
|
||||
Map<String, Object> variables = Map.of(
|
||||
"flow", Map.of(
|
||||
"id", "notme",
|
||||
@@ -140,39 +139,8 @@ class ReadFileFunctionTest {
|
||||
"execution", Map.of("id", "notme")
|
||||
);
|
||||
|
||||
var exception = assertThrows(IllegalArgumentException.class, () -> variableRenderer.render("{{ read('" + internalStorageFile + "') }}", variables));
|
||||
assertThat(exception.getMessage(), is("Unable to read the file '" + internalStorageFile + "' as it didn't belong to the current execution"));
|
||||
|
||||
// test for an un-authorized execution with a trigger of type execution
|
||||
Map<String, Object> executionTriggerVariables = Map.of(
|
||||
"flow", Map.of(
|
||||
"id", "notme",
|
||||
"namespace", "notme"),
|
||||
"execution", Map.of("id", "notme"),
|
||||
"trigger", Map.of(
|
||||
"flowId", "notme",
|
||||
"namespace", "notme",
|
||||
"executionId", "notme"
|
||||
)
|
||||
);
|
||||
|
||||
exception = assertThrows(IllegalArgumentException.class, () -> variableRenderer.render("{{ read('" + internalStorageFile + "') }}", executionTriggerVariables));
|
||||
assertThat(exception.getMessage(), is("Unable to read the file '" + internalStorageFile + "' as it didn't belong to the current execution"));
|
||||
|
||||
// test for an un-authorized execution with a trigger of another type
|
||||
Map<String, Object> triggerVariables = Map.of(
|
||||
"flow", Map.of(
|
||||
"id", "notme",
|
||||
"namespace", "notme"),
|
||||
"execution", Map.of("id", "notme"),
|
||||
"trigger", Map.of(
|
||||
"date", "somedate",
|
||||
"row", "somerow"
|
||||
)
|
||||
);
|
||||
|
||||
exception = assertThrows(IllegalArgumentException.class, () -> variableRenderer.render("{{ read('" + internalStorageFile + "') }}", triggerVariables));
|
||||
assertThat(exception.getMessage(), is("Unable to read the file '" + internalStorageFile + "' as it didn't belong to the current execution"));
|
||||
String render = variableRenderer.render("{{ read('" + internalStorageFile + "') }}", variables);
|
||||
assertThat(render, is("Hello from a task output"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,6 +16,7 @@ import io.kestra.core.queues.QueueFactoryInterface;
|
||||
import io.kestra.core.queues.QueueInterface;
|
||||
import io.kestra.plugin.core.debug.Return;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.plugin.core.flow.Sleep;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -101,6 +102,17 @@ abstract public class AbstractSchedulerTest {
|
||||
return FlowWithSource.of(flow, flow.generateSource());
|
||||
}
|
||||
|
||||
protected static FlowWithSource createLongRunningFlow(List<AbstractTrigger> triggers, List<PluginDefault> list) {
|
||||
return createFlow(triggers, list)
|
||||
.toBuilder()
|
||||
.tasks(
|
||||
Collections.singletonList(
|
||||
Sleep.builder().id("sleep").type(Sleep.class.getName()).duration(Duration.ofSeconds(125)).build()
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected static int COUNTER = 0;
|
||||
|
||||
@SuperBuilder
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package io.kestra.core.schedulers;
|
||||
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.condition.DayWeekInMonth;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.plugin.core.trigger.Schedule;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.runners.FlowListeners;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.condition.DayWeekInMonth;
|
||||
import io.kestra.plugin.core.trigger.Schedule;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -37,6 +38,9 @@ class SchedulerConditionTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
protected SchedulerExecutionStateInterface executionState;
|
||||
|
||||
@Inject
|
||||
protected FlowRepositoryInterface flowRepository;
|
||||
|
||||
private static Flow createScheduleFlow() {
|
||||
Schedule schedule = Schedule.builder()
|
||||
.id("hourly")
|
||||
@@ -66,6 +70,7 @@ class SchedulerConditionTest extends AbstractSchedulerTest {
|
||||
CountDownLatch queueCount = new CountDownLatch(4);
|
||||
|
||||
Flow flow = createScheduleFlow();
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
|
||||
triggerState.create(Trigger.builder()
|
||||
.namespace(flow.getNamespace())
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.kestra.core.schedulers;
|
||||
|
||||
import io.kestra.core.models.Label;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.condition.Expression;
|
||||
@@ -46,6 +48,8 @@ public class SchedulerPollingTriggerTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
private FlowListeners flowListenersService;
|
||||
|
||||
@Inject
|
||||
protected FlowRepositoryInterface flowRepository;
|
||||
|
||||
@Test
|
||||
void pollingTrigger() throws Exception {
|
||||
@@ -92,6 +96,7 @@ public class SchedulerPollingTriggerTest extends AbstractSchedulerTest {
|
||||
.toBuilder()
|
||||
.tasks(List.of(Fail.builder().id("fail").type(Fail.class.getName()).build()))
|
||||
.build();
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
doReturn(List.of(flow))
|
||||
.when(flowListenersServiceSpy)
|
||||
.flows();
|
||||
|
||||
@@ -6,13 +6,13 @@ import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.models.triggers.RecoverMissedSchedules;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.runners.FlowListeners;
|
||||
import io.kestra.core.utils.Await;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.trigger.ScheduleOnDates;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -25,7 +25,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.kestra.core.utils.Rethrow.throwConsumer;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.oneOf;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
@@ -36,6 +37,9 @@ public class SchedulerScheduleOnDatesTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
protected SchedulerTriggerStateInterface triggerState;
|
||||
|
||||
@Inject
|
||||
protected FlowRepositoryInterface flowRepository;
|
||||
|
||||
private ScheduleOnDates.ScheduleOnDatesBuilder<?, ?> createScheduleOnDatesTrigger(String zone, List<ZonedDateTime> dates, String triggerId) {
|
||||
return ScheduleOnDates.builder()
|
||||
.id(triggerId)
|
||||
@@ -75,6 +79,7 @@ public class SchedulerScheduleOnDatesTest extends AbstractSchedulerTest {
|
||||
|
||||
// then flow should be executed 4 times
|
||||
Flow flow = createScheduleFlow("Europe/Paris", "schedule");
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
|
||||
doReturn(List.of(flow))
|
||||
.when(flowListenersServiceSpy)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package io.kestra.core.schedulers;
|
||||
|
||||
import io.kestra.core.models.executions.TaskRun;
|
||||
import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.models.flows.PluginDefault;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.condition.Expression;
|
||||
@@ -30,8 +33,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import static io.kestra.core.utils.Rethrow.throwConsumer;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class SchedulerScheduleTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
@@ -43,6 +45,9 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
protected SchedulerExecutionStateInterface executionState;
|
||||
|
||||
@Inject
|
||||
protected FlowRepositoryInterface flowRepository;
|
||||
|
||||
@Inject
|
||||
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
|
||||
protected QueueInterface<LogEntry> logQueue;
|
||||
@@ -93,6 +98,7 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
|
||||
FlowWithSource invalid = createScheduleFlow("Asia/Delhi", "schedule", true);
|
||||
FlowWithSource flow = createScheduleFlow("Europe/Paris", "schedule", false);
|
||||
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
doReturn(List.of(invalid, flow))
|
||||
.when(flowListenersServiceSpy)
|
||||
.flows();
|
||||
@@ -421,6 +427,7 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
|
||||
.when(flowListenersServiceSpy)
|
||||
.flows();
|
||||
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
// to avoid waiting too much before a trigger execution, we add a last trigger with a date now - 1m.
|
||||
Trigger lastTrigger = Trigger
|
||||
.builder()
|
||||
@@ -515,4 +522,146 @@ public class SchedulerScheduleTest extends AbstractSchedulerTest {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void recoverLASTLongRunningExecution() throws Exception {
|
||||
// mock flow listeners
|
||||
FlowListeners flowListenersServiceSpy = spy(this.flowListenersService);
|
||||
String triggerId = "recoverLASTLongRunningExecution";
|
||||
Schedule schedule = Schedule.builder().id(triggerId).type(Schedule.class.getName()).cron("*/5 * * * * *").withSeconds(true).build();
|
||||
FlowWithSource flow = createLongRunningFlow(
|
||||
Collections.singletonList(schedule),
|
||||
List.of(
|
||||
PluginDefault.builder()
|
||||
.type(Schedule.class.getName())
|
||||
.values(Map.of("recoverMissedSchedules", "LAST"))
|
||||
.build()
|
||||
)
|
||||
);
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
doReturn(List.of(flow))
|
||||
.when(flowListenersServiceSpy)
|
||||
.flows();
|
||||
|
||||
// to avoid waiting too much before a trigger execution, we add a last trigger with a date now - 1m.
|
||||
Trigger lastTrigger = Trigger
|
||||
.builder()
|
||||
.triggerId(triggerId)
|
||||
.flowId(flow.getId())
|
||||
.namespace(flow.getNamespace())
|
||||
.date(ZonedDateTime.now().minusMinutes(1L))
|
||||
.nextExecutionDate(ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES))
|
||||
.build();
|
||||
triggerState.create(lastTrigger);
|
||||
|
||||
CountDownLatch queueCount = new CountDownLatch(1);
|
||||
|
||||
// scheduler
|
||||
try (AbstractScheduler scheduler = scheduler(flowListenersServiceSpy, executionState)) {
|
||||
// wait for execution
|
||||
Flux<Execution> receive = TestsUtils.receive(executionQueue, throwConsumer(either -> {
|
||||
Execution execution = either.getLeft();
|
||||
assertThat(execution.getFlowId(), is(flow.getId()));
|
||||
|
||||
queueCount.countDown();
|
||||
if (execution.getState().getCurrent() == State.Type.CREATED) {
|
||||
Thread.sleep(11000);
|
||||
executionQueue.emit(execution.withState(State.Type.SUCCESS)
|
||||
.toBuilder()
|
||||
.taskRunList(List.of(TaskRun.builder()
|
||||
.id("test")
|
||||
.executionId(execution.getId())
|
||||
.state(State.of(State.Type.SUCCESS,
|
||||
List.of(new State.History(
|
||||
State.Type.SUCCESS,
|
||||
lastTrigger.getNextExecutionDate().plusMinutes(3).toInstant()
|
||||
))))
|
||||
.build()))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
scheduler.run();
|
||||
|
||||
queueCount.await(3, TimeUnit.MINUTES);
|
||||
receive.blockLast();
|
||||
|
||||
assertThat(queueCount.getCount(), is(0L));
|
||||
|
||||
Trigger trigger = Trigger.of(flow, schedule);
|
||||
Await.until(() -> this.triggerState.findLast(trigger).map(t -> t.getNextExecutionDate().isAfter(lastTrigger.getNextExecutionDate().plusSeconds(10))).orElse(false).booleanValue(), Duration.ofMillis(100), Duration.ofSeconds(20));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void recoverNONELongRunningExecution() throws Exception {
|
||||
// mock flow listeners
|
||||
FlowListeners flowListenersServiceSpy = spy(this.flowListenersService);
|
||||
String triggerId = "recoverNONELongRunningExecution";
|
||||
Schedule schedule = Schedule.builder().id(triggerId).type(Schedule.class.getName()).cron("*/5 * * * * *").withSeconds(true).build();
|
||||
FlowWithSource flow = createLongRunningFlow(
|
||||
Collections.singletonList(schedule),
|
||||
List.of(
|
||||
PluginDefault.builder()
|
||||
.type(Schedule.class.getName())
|
||||
.values(Map.of("recoverMissedSchedules", "LAST"))
|
||||
.build()
|
||||
)
|
||||
);
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
doReturn(List.of(flow))
|
||||
.when(flowListenersServiceSpy)
|
||||
.flows();
|
||||
|
||||
// to avoid waiting too much before a trigger execution, we add a last trigger with a date now - 1m.
|
||||
Trigger lastTrigger = Trigger
|
||||
.builder()
|
||||
.triggerId(triggerId)
|
||||
.flowId(flow.getId())
|
||||
.namespace(flow.getNamespace())
|
||||
.date(ZonedDateTime.now().minusMinutes(1L))
|
||||
.nextExecutionDate(ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES))
|
||||
.build();
|
||||
triggerState.create(lastTrigger);
|
||||
|
||||
CountDownLatch queueCount = new CountDownLatch(1);
|
||||
|
||||
// scheduler
|
||||
try (AbstractScheduler scheduler = scheduler(flowListenersServiceSpy, executionState)) {
|
||||
// wait for execution
|
||||
Flux<Execution> receive = TestsUtils.receive(executionQueue, throwConsumer(either -> {
|
||||
Execution execution = either.getLeft();
|
||||
assertThat(execution.getFlowId(), is(flow.getId()));
|
||||
|
||||
queueCount.countDown();
|
||||
if (execution.getState().getCurrent() == State.Type.CREATED) {
|
||||
Thread.sleep(10000);
|
||||
executionQueue.emit(execution.withState(State.Type.SUCCESS)
|
||||
.toBuilder()
|
||||
.taskRunList(List.of(TaskRun.builder()
|
||||
.id("test")
|
||||
.executionId(execution.getId())
|
||||
.state(State.of(State.Type.SUCCESS,
|
||||
List.of(new State.History(
|
||||
State.Type.SUCCESS,
|
||||
lastTrigger.getNextExecutionDate().plusMinutes(3).toInstant()
|
||||
))))
|
||||
.build()))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
scheduler.run();
|
||||
|
||||
queueCount.await(3, TimeUnit.MINUTES);
|
||||
receive.blockLast();
|
||||
|
||||
assertThat(queueCount.getCount(), is(0L));
|
||||
|
||||
Trigger trigger = Trigger.of(flow, schedule);
|
||||
Await.until(() -> this.triggerState.findLast(trigger).map(t -> t.getNextExecutionDate().isAfter(lastTrigger.getNextExecutionDate().plusSeconds(10))).orElse(false).booleanValue(), Duration.ofMillis(100), Duration.ofSeconds(20));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@ package io.kestra.core.schedulers;
|
||||
import io.kestra.core.models.Label;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.PluginDefault;
|
||||
import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.tasks.WorkerGroup;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.runners.FlowListeners;
|
||||
import io.kestra.core.runners.TestMethodScopedWorker;
|
||||
import io.kestra.core.runners.Worker;
|
||||
@@ -17,8 +16,6 @@ import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -27,6 +24,7 @@ import static io.kestra.core.utils.Rethrow.throwConsumer;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
|
||||
public class SchedulerThreadTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
@@ -35,9 +33,13 @@ public class SchedulerThreadTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
protected SchedulerExecutionStateInterface executionState;
|
||||
|
||||
@Inject
|
||||
protected FlowRepositoryInterface flowRepository;
|
||||
|
||||
@Test
|
||||
void thread() throws Exception {
|
||||
Flow flow = createThreadFlow();
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
CountDownLatch queueCount = new CountDownLatch(2);
|
||||
|
||||
// wait for execution
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.executions.ExecutionKilled;
|
||||
import io.kestra.core.models.executions.ExecutionKilledTrigger;
|
||||
import io.kestra.core.models.executions.LogEntry;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
@@ -13,12 +14,16 @@ import io.kestra.core.models.triggers.TriggerContext;
|
||||
import io.kestra.core.models.triggers.TriggerService;
|
||||
import io.kestra.core.queues.QueueFactoryInterface;
|
||||
import io.kestra.core.queues.QueueInterface;
|
||||
import io.kestra.core.runners.*;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.debug.Return;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.runners.FlowListeners;
|
||||
import io.kestra.core.runners.TestMethodScopedWorker;
|
||||
import io.kestra.core.runners.Worker;
|
||||
import io.kestra.core.runners.WorkerTrigger;
|
||||
import io.kestra.core.utils.Await;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.kestra.jdbc.runner.JdbcScheduler;
|
||||
import io.kestra.plugin.core.debug.Return;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import lombok.*;
|
||||
@@ -27,14 +32,16 @@ import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
|
||||
|
||||
public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
|
||||
@Inject
|
||||
@@ -52,6 +59,10 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
|
||||
@Named(QueueFactoryInterface.KILL_NAMED)
|
||||
protected QueueInterface<ExecutionKilled> killedQueue;
|
||||
|
||||
@Inject
|
||||
protected FlowRepositoryInterface flowRepository;
|
||||
|
||||
|
||||
public static FlowWithSource createFlow(Duration sleep) {
|
||||
SleepTriggerTest schedule = SleepTriggerTest.builder()
|
||||
.id("sleep")
|
||||
@@ -59,7 +70,7 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
|
||||
.sleep(sleep)
|
||||
.build();
|
||||
|
||||
return FlowWithSource.builder()
|
||||
Flow flow = Flow.builder()
|
||||
.id(SchedulerTriggerChangeTest.class.getSimpleName())
|
||||
.namespace("io.kestra.unittest")
|
||||
.revision(1)
|
||||
@@ -71,6 +82,8 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
|
||||
.build())
|
||||
)
|
||||
.build();
|
||||
|
||||
return FlowWithSource.of(flow, flow.generateSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,6 +119,7 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
|
||||
|
||||
// emit a flow trigger to be started
|
||||
FlowWithSource flow = createFlow(Duration.ofSeconds(10));
|
||||
flowRepository.create(flow, flow.generateSource(), flow);
|
||||
flowQueue.emit(flow);
|
||||
|
||||
Await.until(() -> STARTED_COUNT == 1, Duration.ofMillis(100), Duration.ofSeconds(30));
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@KestraTest
|
||||
@@ -204,26 +205,6 @@ class FlowServiceTest {
|
||||
assertThat(collect.stream().filter(flow -> flow.getId().equals("test3")).findFirst().orElseThrow().getRevision(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void warnings() {
|
||||
Flow flow = create("test", "test", 1).toBuilder()
|
||||
.namespace("system")
|
||||
.triggers(List.of(
|
||||
io.kestra.plugin.core.trigger.Flow.builder()
|
||||
.id("flow-trigger")
|
||||
.type(io.kestra.plugin.core.trigger.Flow.class.getName())
|
||||
.build()
|
||||
))
|
||||
.build();
|
||||
|
||||
List<String> warnings = flowService.warnings(flow);
|
||||
|
||||
assertThat(warnings.size(), is(1));
|
||||
assertThat(warnings, containsInAnyOrder(
|
||||
"This flow will be triggered for EVERY execution of EVERY flow on your instance. We recommend adding the preconditions property to the Flow trigger 'flow-trigger'."
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aliases() {
|
||||
List<FlowService.Relocation> warnings = flowService.relocations("""
|
||||
@@ -323,7 +304,7 @@ class FlowServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkValidSubflowsNotFound() {
|
||||
void checkSubflowNotFound() {
|
||||
Flow flow = create("mainFlow", "task", 1).toBuilder()
|
||||
.tasks(List.of(
|
||||
io.kestra.plugin.core.flow.Subflow.builder()
|
||||
@@ -335,11 +316,30 @@ class FlowServiceTest {
|
||||
))
|
||||
.build();
|
||||
|
||||
ConstraintViolationException exception = assertThrows(ConstraintViolationException.class, () -> {
|
||||
flowService.checkValidSubflows(flow);
|
||||
});
|
||||
List<String> exceptions = flowService.checkValidSubflows(flow, null);
|
||||
|
||||
assertThat(exception.getConstraintViolations().size(), is(1));
|
||||
assertThat(exception.getConstraintViolations().iterator().next().getMessage(), is("The subflow 'nonExistentSubflow' not found in namespace 'io.kestra.unittest'."));
|
||||
assertThat(exceptions.size(), is(1));
|
||||
assertThat(exceptions.iterator().next(), is("The subflow 'nonExistentSubflow' not found in namespace 'io.kestra.unittest'."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkValidSubflow() {
|
||||
Flow subflow = create("existingSubflow", "task", 1);
|
||||
flowRepository.create(subflow, subflow.generateSource(), subflow);
|
||||
|
||||
Flow flow = create("mainFlow", "task", 1).toBuilder()
|
||||
.tasks(List.of(
|
||||
io.kestra.plugin.core.flow.Subflow.builder()
|
||||
.id("subflowTask")
|
||||
.type(io.kestra.plugin.core.flow.Subflow.class.getName())
|
||||
.namespace("io.kestra.unittest")
|
||||
.flowId("existingSubflow")
|
||||
.build()
|
||||
))
|
||||
.build();
|
||||
|
||||
List<String> exceptions = flowService.checkValidSubflows(flow, null);
|
||||
|
||||
assertThat(exceptions.size(), is(0));
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,14 @@ import io.kestra.plugin.core.trigger.Schedule;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@KestraTest
|
||||
class LabelServiceTest {
|
||||
@@ -65,4 +68,15 @@ class LabelServiceTest {
|
||||
assertThat(labels, hasSize(2));
|
||||
assertThat(labels, hasItems(new Label("key", "value"), new Label("scheduleLabel", "scheduleValue")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsAll() {
|
||||
assertFalse(LabelService.containsAll(null, List.of(new Label("key", "value"))));
|
||||
assertFalse(LabelService.containsAll(Collections.emptyList(), List.of(new Label("key", "value"))));
|
||||
assertFalse(LabelService.containsAll(List.of(new Label("key1", "value1")), List.of(new Label("key2", "value2"))));
|
||||
assertTrue(LabelService.containsAll(List.of(new Label("key", "value")), null));
|
||||
assertTrue(LabelService.containsAll(List.of(new Label("key", "value")), Collections.emptyList()));
|
||||
assertTrue(LabelService.containsAll(List.of(new Label("key1", "value1")), List.of(new Label("key1", "value1"))));
|
||||
assertTrue(LabelService.containsAll(List.of(new Label("key1", "value1"), new Label("key2", "value2")), List.of(new Label("key1", "value1"))));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package io.kestra.core.storages;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import io.kestra.core.exceptions.KestraRuntimeException;
|
||||
import io.kestra.core.plugins.DefaultPluginRegistry;
|
||||
import io.kestra.storage.local.LocalStorage;
|
||||
@@ -24,21 +28,28 @@ class StorageInterfaceFactoryTest {
|
||||
void shouldReturnStorageGivenValidId() {
|
||||
StorageInterface storage = StorageInterfaceFactory.make(registry, "local", Map.of("basePath", "/tmp/kestra"), validator);
|
||||
Assertions.assertNotNull(storage);
|
||||
Assertions.assertEquals(LocalStorage.class.getName(), storage.getType());
|
||||
assertEquals(LocalStorage.class.getName(), storage.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailedGivenInvalidId() {
|
||||
Assertions.assertThrows(KestraRuntimeException.class,
|
||||
assertThrows(KestraRuntimeException.class,
|
||||
() -> StorageInterfaceFactory.make(registry, "invalid", Map.of(), validator));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailedGivenInvalidConfig() {
|
||||
KestraRuntimeException e = Assertions.assertThrows(KestraRuntimeException.class,
|
||||
KestraRuntimeException e = assertThrows(KestraRuntimeException.class,
|
||||
() -> StorageInterfaceFactory.make(registry, "local", Map.of(), validator));
|
||||
|
||||
Assertions.assertTrue(e.getCause() instanceof ConstraintViolationException);
|
||||
Assertions.assertEquals("basePath: must not be null", e.getCause().getMessage());
|
||||
assertTrue(e.getCause() instanceof ConstraintViolationException);
|
||||
assertEquals("basePath: must not be null", e.getCause().getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_found_unknown_storage(){
|
||||
KestraRuntimeException e = assertThrows(KestraRuntimeException.class,
|
||||
() -> StorageInterfaceFactory.make(registry, "unknown", Map.of(), validator));
|
||||
assertEquals("No storage interface can be found for 'kestra.storage.type=unknown'. Supported types are: [local]", e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,36 @@ public class FlowCaseTest {
|
||||
this.run("OK", State.Type.SUCCESS, State.Type.SUCCESS, 2, "default > amazing", false);
|
||||
}
|
||||
|
||||
public void oldTaskName() throws Exception {
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
AtomicReference<Execution> triggered = new AtomicReference<>();
|
||||
|
||||
Flux<Execution> receive = TestsUtils.receive(executionQueue, either -> {
|
||||
Execution execution = either.getLeft();
|
||||
if (execution.getFlowId().equals("minimal") && execution.getState().getCurrent().isTerminated()) {
|
||||
triggered.set(execution);
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
Execution execution = runnerUtils.runOne(
|
||||
null,
|
||||
"io.kestra.tests",
|
||||
"subflow-old-task-name"
|
||||
);
|
||||
|
||||
countDownLatch.await(1, TimeUnit.MINUTES);
|
||||
receive.blockLast();
|
||||
|
||||
assertThat(execution.getTaskRunList(), hasSize(1));
|
||||
assertThat(execution.getState().getCurrent(), is(State.Type.SUCCESS));
|
||||
assertThat(execution.getTaskRunList().getFirst().getOutputs().get("executionId"), is(triggered.get().getId()));
|
||||
assertThat(triggered.get().getTrigger().getType(), is("io.kestra.core.tasks.flows.Subflow"));
|
||||
assertThat(triggered.get().getTrigger().getVariables().get("executionId"), is(execution.getId()));
|
||||
assertThat(triggered.get().getTrigger().getVariables().get("flowId"), is(execution.getFlowId()));
|
||||
assertThat(triggered.get().getTrigger().getVariables().get("namespace"), is(execution.getNamespace()));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"})
|
||||
void run(String input, State.Type fromState, State.Type triggerState, int count, String outputs, boolean testInherited) throws Exception {
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@@ -99,21 +129,24 @@ public class FlowCaseTest {
|
||||
assertThat(triggered.get().getState().getCurrent(), is(triggerState));
|
||||
|
||||
if (testInherited) {
|
||||
assertThat(triggered.get().getLabels().size(), is(5));
|
||||
assertThat(triggered.get().getLabels().size(), is(6));
|
||||
assertThat(triggered.get().getLabels(), hasItems(
|
||||
new Label(Label.CORRELATION_ID, execution.getId()),
|
||||
new Label("mainFlowExecutionLabel", "execFoo"),
|
||||
new Label("mainFlowLabel", "flowFoo"),
|
||||
new Label("launchTaskLabel", "launchFoo"),
|
||||
new Label("switchFlowLabel", "switchFoo")
|
||||
new Label("switchFlowLabel", "switchFoo"),
|
||||
new Label("overriding", "child")
|
||||
));
|
||||
} else {
|
||||
assertThat(triggered.get().getLabels().size(), is(3));
|
||||
assertThat(triggered.get().getLabels().size(), is(4));
|
||||
assertThat(triggered.get().getLabels(), hasItems(
|
||||
new Label(Label.CORRELATION_ID, execution.getId()),
|
||||
new Label("launchTaskLabel", "launchFoo"),
|
||||
new Label("switchFlowLabel", "switchFoo")
|
||||
new Label("switchFlowLabel", "switchFoo"),
|
||||
new Label("overriding", "child")
|
||||
));
|
||||
assertThat(triggered.get().getLabels(), not(hasItems(new Label("inherited", "label"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@KestraTest(startRunner = true)
|
||||
public class FlowTest {
|
||||
class FlowTest {
|
||||
@Inject
|
||||
FlowCaseTest flowCaseTest;
|
||||
|
||||
@@ -15,7 +15,7 @@ public class FlowTest {
|
||||
@LoadFlows({"flows/valids/task-flow.yaml",
|
||||
"flows/valids/task-flow-inherited-labels.yaml",
|
||||
"flows/valids/switch.yaml"})
|
||||
public void waitSuccess() throws Exception {
|
||||
void waitSuccess() throws Exception {
|
||||
flowCaseTest.waitSuccess();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class FlowTest {
|
||||
@LoadFlows({"flows/valids/task-flow.yaml",
|
||||
"flows/valids/task-flow-inherited-labels.yaml",
|
||||
"flows/valids/switch.yaml"})
|
||||
public void waitFailed() throws Exception {
|
||||
void waitFailed() throws Exception {
|
||||
flowCaseTest.waitFailed();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class FlowTest {
|
||||
@LoadFlows({"flows/valids/task-flow.yaml",
|
||||
"flows/valids/task-flow-inherited-labels.yaml",
|
||||
"flows/valids/switch.yaml"})
|
||||
public void invalidOutputs() throws Exception {
|
||||
void invalidOutputs() throws Exception {
|
||||
flowCaseTest.invalidOutputs();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,14 @@ public class FlowTest {
|
||||
@LoadFlows({"flows/valids/task-flow.yaml",
|
||||
"flows/valids/task-flow-inherited-labels.yaml",
|
||||
"flows/valids/switch.yaml"})
|
||||
public void noLabels() throws Exception {
|
||||
void noLabels() throws Exception {
|
||||
flowCaseTest.noLabels();
|
||||
}
|
||||
|
||||
@Test
|
||||
@LoadFlows({"flows/valids/subflow-old-task-name.yaml",
|
||||
"flows/valids/minimal.yaml"})
|
||||
void oldTaskName() throws Exception {
|
||||
flowCaseTest.oldTaskName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ public class ForEachItemCaseTest {
|
||||
}
|
||||
|
||||
public void restartForEachItem() throws Exception {
|
||||
CountDownLatch countDownLatch = new CountDownLatch(26);
|
||||
CountDownLatch countDownLatch = new CountDownLatch(6);
|
||||
Flux<Execution> receiveSubflows = TestsUtils.receive(executionQueue, either -> {
|
||||
Execution subflowExecution = either.getLeft();
|
||||
if (subflowExecution.getFlowId().equals("restart-child") && subflowExecution.getState().getCurrent().isFailed()) {
|
||||
@@ -285,7 +285,7 @@ public class ForEachItemCaseTest {
|
||||
});
|
||||
|
||||
URI file = storageUpload();
|
||||
Map<String, Object> inputs = Map.of("file", file.toString(), "batch", 4);
|
||||
Map<String, Object> inputs = Map.of("file", file.toString(), "batch", 20);
|
||||
Execution execution = runnerUtils.runOne(null, TEST_NAMESPACE, "restart-for-each-item", null,
|
||||
(flow, execution1) -> flowIO.readExecutionInputs(flow, execution1, inputs),
|
||||
Duration.ofSeconds(30));
|
||||
@@ -296,7 +296,7 @@ public class ForEachItemCaseTest {
|
||||
assertTrue(countDownLatch.await(1, TimeUnit.MINUTES));
|
||||
receiveSubflows.blockLast();
|
||||
|
||||
CountDownLatch successLatch = new CountDownLatch(26);
|
||||
CountDownLatch successLatch = new CountDownLatch(6);
|
||||
receiveSubflows = TestsUtils.receive(executionQueue, either -> {
|
||||
Execution subflowExecution = either.getLeft();
|
||||
if (subflowExecution.getFlowId().equals("restart-child") && subflowExecution.getState().getCurrent().isSuccess()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junitpioneer.jupiter.RetryingTest;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -43,7 +43,7 @@ class TimeoutTest {
|
||||
@Inject
|
||||
private RunnerUtils runnerUtils;
|
||||
|
||||
@Test
|
||||
@RetryingTest(5) // Flaky on CI but never locally even with 100 repetitions
|
||||
void timeout() throws TimeoutException, QueueException {
|
||||
List<LogEntry> logs = new CopyOnWriteArrayList<>();
|
||||
Flux<LogEntry> receive = TestsUtils.receive(workerTaskLogQueue, either -> logs.add(either.getLeft()));
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.micronaut.http.*;
|
||||
import io.micronaut.http.annotation.*;
|
||||
import io.micronaut.http.multipart.StreamingFileUpload;
|
||||
import io.micronaut.runtime.server.EmbeddedServer;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -188,6 +189,37 @@ class RequestTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedPost() throws Exception {
|
||||
try (
|
||||
ApplicationContext applicationContext = ApplicationContext.run();
|
||||
EmbeddedServer server = applicationContext.getBean(EmbeddedServer.class).start();
|
||||
|
||||
) {
|
||||
Request task = Request.builder()
|
||||
.id(RequestTest.class.getSimpleName())
|
||||
.type(RequestTest.class.getName())
|
||||
.uri(Property.of(server.getURL().toString() + "/markdown"))
|
||||
.method(Property.of("POST"))
|
||||
.body(Property.of("# hello web!"))
|
||||
.contentType(Property.of("text/markdown"))
|
||||
.options(HttpConfiguration.builder().defaultCharset(Property.of(null)).build())
|
||||
.build();
|
||||
|
||||
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
|
||||
|
||||
HttpClientResponseException exception = assertThrows(
|
||||
HttpClientResponseException.class,
|
||||
() -> task.run(runContext)
|
||||
);
|
||||
|
||||
assertThat(exception.getResponse().getStatus().getCode(), is(417));
|
||||
assertThat(exception.getMessage(), containsString("hello world"));
|
||||
byte[] content = ((io.kestra.core.http.HttpRequest.ByteArrayRequestBody) exception.getRequest().getBody()).getContent();
|
||||
assertThat(new String(content) , containsString("hello web"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfSigned() throws Exception {
|
||||
try (
|
||||
@@ -437,6 +469,33 @@ class RequestTest {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
void basicAuthOld() throws Exception {
|
||||
try (
|
||||
ApplicationContext applicationContext = ApplicationContext.run();
|
||||
EmbeddedServer server = applicationContext.getBean(EmbeddedServer.class).start();
|
||||
) {
|
||||
Request task = Request.builder()
|
||||
.id(RequestTest.class.getSimpleName())
|
||||
.type(RequestTest.class.getName())
|
||||
.uri(Property.of(server.getURL().toString() + "/auth/basic"))
|
||||
.options(HttpConfiguration.builder()
|
||||
.basicAuthUser("John")
|
||||
.basicAuthPassword("p4ss")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, Map.of());
|
||||
|
||||
Request.Output output = task.run(runContext);
|
||||
|
||||
assertThat(output.getBody(), is("{\"hello\":\"John\"}"));
|
||||
assertThat(output.getCode(), is(200));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bearerAuth() throws Exception {
|
||||
try (
|
||||
@@ -464,6 +523,54 @@ class RequestTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void specialContentType() throws Exception {
|
||||
try (
|
||||
ApplicationContext applicationContext = ApplicationContext.run();
|
||||
EmbeddedServer server = applicationContext.getBean(EmbeddedServer.class).start();
|
||||
|
||||
) {
|
||||
Request task = Request.builder()
|
||||
.id(RequestTest.class.getSimpleName())
|
||||
.type(RequestTest.class.getName())
|
||||
.uri(Property.of(server.getURL().toString() + "/content-type"))
|
||||
.method(Property.of("POST"))
|
||||
.body(Property.of("{}"))
|
||||
.contentType(Property.of("application/vnd.campaignsexport.v1+json"))
|
||||
.options(HttpConfiguration.builder().logs(HttpConfiguration.LoggingType.values()).defaultCharset(null).build())
|
||||
.build();
|
||||
|
||||
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
|
||||
|
||||
Request.Output output = task.run(runContext);
|
||||
|
||||
assertThat(output.getBody(), is("application/vnd.campaignsexport.v1+json"));
|
||||
assertThat(output.getCode(), is(200));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void spaceInURI() throws Exception {
|
||||
try (
|
||||
ApplicationContext applicationContext = ApplicationContext.run();
|
||||
EmbeddedServer server = applicationContext.getBean(EmbeddedServer.class).start();
|
||||
|
||||
) {
|
||||
Request task = Request.builder()
|
||||
.id(RequestTest.class.getSimpleName())
|
||||
.type(RequestTest.class.getName())
|
||||
.uri(Property.of(server.getURL().toString() + "/uri with space"))
|
||||
.build();
|
||||
|
||||
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
|
||||
|
||||
Request.Output output = task.run(runContext);
|
||||
|
||||
assertThat(output.getBody(), is("Hello World"));
|
||||
assertThat(output.getCode(), is(200));
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class MockController {
|
||||
@Get("/hello")
|
||||
@@ -471,6 +578,13 @@ class RequestTest {
|
||||
return HttpResponse.ok("{ \"hello\": \"world\" }");
|
||||
}
|
||||
|
||||
@Post("content-type")
|
||||
@Consumes("application/vnd.campaignsexport.v1+json")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public io.micronaut.http.HttpResponse<String> contentType(io.micronaut.http.HttpRequest<?> request, @Nullable @Body Map<String, String> body) {
|
||||
return io.micronaut.http.HttpResponse.ok(request.getContentType().orElseThrow().toString());
|
||||
}
|
||||
|
||||
@Head("/hello")
|
||||
HttpResponse<String> head() {
|
||||
return HttpResponse.ok();
|
||||
@@ -481,6 +595,13 @@ class RequestTest {
|
||||
return HttpResponse.status(HttpStatus.EXPECTATION_FAILED).body("{ \"hello\": \"world\" }");
|
||||
}
|
||||
|
||||
@Post("/markdown")
|
||||
@Consumes(MediaType.TEXT_MARKDOWN)
|
||||
@Produces(MediaType.TEXT_MARKDOWN)
|
||||
HttpResponse<String> postMarkdown() {
|
||||
return HttpResponse.status(HttpStatus.EXPECTATION_FAILED).body("# hello world");
|
||||
}
|
||||
|
||||
@Get("/redirect")
|
||||
HttpResponse<String> redirect() {
|
||||
return HttpResponse.redirect(URI.create("/hello"));
|
||||
@@ -537,5 +658,10 @@ class RequestTest {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Get("/uri%20with%20space")
|
||||
HttpResponse<String> uriWithSpace() {
|
||||
return HttpResponse.ok("Hello World");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
package io.kestra.plugin.core.log;
|
||||
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import io.kestra.core.junit.annotations.LoadFlows;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.executions.LogEntry;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.repositories.LogRepositoryInterface;
|
||||
import io.kestra.core.runners.RunContextFactory;
|
||||
import io.kestra.core.runners.RunnerUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@KestraTest
|
||||
@KestraTest(startRunner = true)
|
||||
class PurgeLogsTest {
|
||||
@Inject
|
||||
private RunContextFactory runContextFactory;
|
||||
@@ -25,8 +30,12 @@ class PurgeLogsTest {
|
||||
@Inject
|
||||
private LogRepositoryInterface logRepository;
|
||||
|
||||
@Inject
|
||||
protected RunnerUtils runnerUtils;
|
||||
|
||||
@Test
|
||||
void run() throws Exception {
|
||||
@LoadFlows("flows/valids/purge_logs_no_arguments.yaml")
|
||||
void run_with_no_arguments() throws Exception {
|
||||
// create an execution to delete
|
||||
var logEntry = LogEntry.builder()
|
||||
.namespace("namespace")
|
||||
@@ -37,12 +46,71 @@ class PurgeLogsTest {
|
||||
.build();
|
||||
logRepository.save(logEntry);
|
||||
|
||||
var purge = PurgeLogs.builder()
|
||||
.endDate(Property.of(ZonedDateTime.now().plusMinutes(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)))
|
||||
.build();
|
||||
var runContext = runContextFactory.of(Map.of("flow", Map.of("namespace", "namespace", "id", "flowId")));
|
||||
var output = purge.run(runContext);
|
||||
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "purge_logs_no_arguments");
|
||||
|
||||
assertThat(output.getCount(), is(1));
|
||||
assertTrue(execution.getState().isSuccess());
|
||||
assertThat(execution.getTaskRunList().size(), is(1));
|
||||
assertThat(execution.getTaskRunList().getFirst().getOutputs().get("count"), is(1));
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("buildArguments")
|
||||
@LoadFlows("flows/valids/purge_logs_full_arguments.yaml")
|
||||
void run_with_full_arguments(LogEntry logEntry, int resultCount, String failingReason) throws Exception {
|
||||
logRepository.save(logEntry);
|
||||
|
||||
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "purge_logs_full_arguments");
|
||||
|
||||
assertTrue(execution.getState().isSuccess());
|
||||
assertThat(execution.getTaskRunList().size(), is(1));
|
||||
assertThat(failingReason, execution.getTaskRunList().getFirst().getOutputs().get("count"), is(resultCount));
|
||||
}
|
||||
|
||||
static Stream<Arguments> buildArguments() {
|
||||
return Stream.of(
|
||||
Arguments.of(LogEntry.builder()
|
||||
.namespace("purge.namespace")
|
||||
.flowId("purgeFlowId")
|
||||
.timestamp(Instant.now().plus(5, ChronoUnit.HOURS))
|
||||
.level(Level.INFO)
|
||||
.message("Hello World")
|
||||
.build(), 0, "The log is too recent to be found"),
|
||||
Arguments.of(LogEntry.builder()
|
||||
.namespace("purge.namespace")
|
||||
.flowId("purgeFlowId")
|
||||
.timestamp(Instant.now().minus(5, ChronoUnit.HOURS))
|
||||
.level(Level.INFO)
|
||||
.message("Hello World")
|
||||
.build(), 0, "The log is too old to be found"),
|
||||
Arguments.of(LogEntry.builder()
|
||||
.namespace("uncorrect.namespace")
|
||||
.flowId("purgeFlowId")
|
||||
.timestamp(Instant.now().minusSeconds(10))
|
||||
.level(Level.INFO)
|
||||
.message("Hello World")
|
||||
.build(), 0, "The log has an incorrect namespace"),
|
||||
Arguments.of(LogEntry.builder()
|
||||
.namespace("purge.namespace")
|
||||
.flowId("wrongFlowId")
|
||||
.timestamp(Instant.now().minusSeconds(10))
|
||||
.level(Level.INFO)
|
||||
.message("Hello World")
|
||||
.build(), 0, "The log has an incorrect flow id"),
|
||||
Arguments.of(LogEntry.builder()
|
||||
.namespace("purge.namespace")
|
||||
.flowId("purgeFlowId")
|
||||
.timestamp(Instant.now().minusSeconds(10))
|
||||
.level(Level.WARN)
|
||||
.message("Hello World")
|
||||
.build(), 0, "The log has an incorrect LogLevel"),
|
||||
Arguments.of(LogEntry.builder()
|
||||
.namespace("purge.namespace")
|
||||
.flowId("purgeFlowId")
|
||||
.timestamp(Instant.now().minusSeconds(10))
|
||||
.level(Level.INFO)
|
||||
.message("Hello World")
|
||||
.build(), 1, "The log should be deleted")
|
||||
);
|
||||
}
|
||||
}
|
||||
7
core/src/test/resources/flows/runners/sleep_medium.yml
Normal file
7
core/src/test/resources/flows/runners/sleep_medium.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
id: sleep_medium
|
||||
namespace: io.kestra.forcerun.tests
|
||||
|
||||
tasks:
|
||||
- id: sleep
|
||||
type: io.kestra.plugin.core.flow.Sleep
|
||||
duration: PT10S
|
||||
@@ -0,0 +1,13 @@
|
||||
id: purge_logs_full_arguments
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: purge_logs
|
||||
type: io.kestra.plugin.core.log.PurgeLogs
|
||||
endDate: "{{ now() | dateAdd(2, 'HOURS') }}"
|
||||
startDate: "{{ now() | dateAdd(-2, 'HOURS') }}"
|
||||
namespace: purge.namespace
|
||||
flowId: purgeFlowId
|
||||
logLevels:
|
||||
- INFO
|
||||
- ERROR
|
||||
@@ -0,0 +1,7 @@
|
||||
id: purge_logs_no_arguments
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: purge_logs
|
||||
type: io.kestra.plugin.core.log.PurgeLogs
|
||||
endDate: "{{ now() | dateAdd(2, 'HOURS') }}"
|
||||
7
core/src/test/resources/flows/valids/sleep-long.yml
Normal file
7
core/src/test/resources/flows/valids/sleep-long.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
id: sleep-long
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: sleep-long
|
||||
type: io.kestra.plugin.core.flow.Sleep
|
||||
duration: PT300S
|
||||
@@ -0,0 +1,8 @@
|
||||
id: subflow-old-task-name
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: subflow
|
||||
type: io.kestra.core.tasks.flows.Subflow
|
||||
namespace: io.kestra.tests
|
||||
flowId: minimal
|
||||
@@ -10,6 +10,7 @@ inputs:
|
||||
|
||||
labels:
|
||||
switchFlowLabel: switchFoo
|
||||
overriding: child
|
||||
|
||||
tasks:
|
||||
- id: parent-seq
|
||||
|
||||
@@ -7,6 +7,7 @@ inputs:
|
||||
|
||||
labels:
|
||||
mainFlowLabel: flowFoo
|
||||
overriding: parent
|
||||
|
||||
tasks:
|
||||
- id: launch
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
id: request-basicauth-deprecated
|
||||
namespace: sanitycheck.plugin.core.http
|
||||
|
||||
tasks:
|
||||
- id: request
|
||||
type: io.kestra.plugin.core.http.Request
|
||||
uri: https://testpages.eviltester.com/styled/auth/basic-auth-results.html
|
||||
method: GET
|
||||
options:
|
||||
basicAuthUser: authorized
|
||||
basicAuthPassword: password001
|
||||
|
||||
- id: assert
|
||||
type: io.kestra.plugin.core.execution.Assert
|
||||
errorMessage: "Invalid response code {{ outputs.request.code }}"
|
||||
conditions:
|
||||
- "{{ outputs.request.code == 200 }}"
|
||||
19
core/src/test/resources/sanity-checks/request-basicauth.yaml
Normal file
19
core/src/test/resources/sanity-checks/request-basicauth.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
id: request-basicauth
|
||||
namespace: sanitycheck.plugin.core.http
|
||||
|
||||
tasks:
|
||||
- id: request
|
||||
type: io.kestra.plugin.core.http.Request
|
||||
uri: https://testpages.eviltester.com/styled/auth/basic-auth-results.html
|
||||
method: GET
|
||||
options:
|
||||
auth:
|
||||
type: BASIC
|
||||
username: authorized
|
||||
password: password001
|
||||
|
||||
- id: assert
|
||||
type: io.kestra.plugin.core.execution.Assert
|
||||
errorMessage: "Invalid response code {{ outputs.request.code }}"
|
||||
conditions:
|
||||
- "{{ outputs.request.code == 200 }}"
|
||||
@@ -0,0 +1,14 @@
|
||||
id: request_no_options
|
||||
namespace: sanitycheck.plugin.core.http
|
||||
|
||||
tasks:
|
||||
- id: request
|
||||
type: io.kestra.plugin.core.http.Request
|
||||
uri: https://www.google.com
|
||||
method: GET
|
||||
|
||||
- id: assert
|
||||
type: io.kestra.plugin.core.execution.Assert
|
||||
errorMessage: "Invalid response code {{ outputs.request.code }}"
|
||||
conditions:
|
||||
- "{{ outputs.request.code == 200 }}"
|
||||
@@ -19,7 +19,8 @@
|
||||
# ./release-plugins.sh --release-version=0.20.0 --next-version=0.21.0-SNAPSHOT
|
||||
# To release a specific plugin:
|
||||
# ./release-plugins.sh --release-version=0.20.0 --next-version=0.21.0-SNAPSHOT plugin-kubernetes
|
||||
|
||||
# To release specific plugins from file:
|
||||
# ./release-plugins.sh --release-version=0.20.0 --plugin-file .plugins
|
||||
#===============================================================================
|
||||
|
||||
set -e;
|
||||
@@ -29,7 +30,7 @@ set -e;
|
||||
###############################################################
|
||||
BASEDIR=$(dirname "$(readlink -f $0)")
|
||||
WORKING_DIR=/tmp/kestra-release-plugins-$(date +%s);
|
||||
PLUGIN_FILE="$BASEDIR/.plugins"
|
||||
PLUGIN_FILE="$BASEDIR/../.plugins"
|
||||
GIT_BRANCH=master
|
||||
|
||||
###############################################################
|
||||
@@ -43,6 +44,7 @@ usage() {
|
||||
echo "Options:"
|
||||
echo " --release-version <version> Specify the release version (required)."
|
||||
echo " --next-version <version> Specify the next version (required)."
|
||||
echo " --plugin-file File containing the plugin list (default: .plugins)"
|
||||
echo " --dry-run Specify to run in DRY_RUN."
|
||||
echo " -y, --yes Automatically confirm prompts (non-interactive)."
|
||||
echo " -h, --help Show this help message and exit."
|
||||
@@ -81,6 +83,14 @@ while [[ "$#" -gt 0 ]]; do
|
||||
NEXT_VERSION="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--plugin-file)
|
||||
PLUGIN_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--plugin-file=*)
|
||||
PLUGIN_FILE="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# SCRIPT: tag-release-plugins.sh
|
||||
# SCRIPT: setversion-tag-plugins.sh
|
||||
#
|
||||
# DESCRIPTION:
|
||||
# This script can be used to update and tag plugins from a release branch .e.g., releases/v0.21.x.
|
||||
# By default, if no `GITHUB_PAT` environment variable exist, the script will attempt to clone GitHub repositories using SSH_KEY.
|
||||
#
|
||||
# USAGE: ./tag-release-plugins.sh [options]
|
||||
# USAGE: ./setversion-tag-plugins.sh [options]
|
||||
# OPTIONS:
|
||||
# --release-version <version> Specify the release version (required)
|
||||
# --dry-run Specify to run in DRY_RUN.
|
||||
@@ -15,10 +15,11 @@
|
||||
|
||||
# EXAMPLES:
|
||||
# To release all plugins:
|
||||
# ./tag-release-plugins.sh --release-version=0.20.0
|
||||
# ./setversion-tag-plugins.sh --release-version=0.20.0
|
||||
# To release a specific plugin:
|
||||
# ./tag-release-plugins.sh --release-version=0.20.0 plugin-kubernetes
|
||||
|
||||
# ./setversion-tag-plugins.sh --release-version=0.20.0 plugin-kubernetes
|
||||
# To release specific plugins from file:
|
||||
# ./setversion-tag-plugins.sh --release-version=0.20.0 --plugin-file .plugins
|
||||
#===============================================================================
|
||||
|
||||
set -e;
|
||||
@@ -28,7 +29,7 @@ set -e;
|
||||
###############################################################
|
||||
BASEDIR=$(dirname "$(readlink -f $0)")
|
||||
WORKING_DIR=/tmp/kestra-release-plugins-$(date +%s);
|
||||
PLUGIN_FILE="$BASEDIR/.plugins"
|
||||
PLUGIN_FILE="$BASEDIR/../.plugins"
|
||||
|
||||
###############################################################
|
||||
# Functions
|
||||
@@ -40,6 +41,7 @@ usage() {
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --release-version <version> Specify the release version (required)."
|
||||
echo " --plugin-file File containing the plugin list (default: .plugins)"
|
||||
echo " --dry-run Specify to run in DRY_RUN."
|
||||
echo " -y, --yes Automatically confirm prompts (non-interactive)."
|
||||
echo " -h, --help Show this help message and exit."
|
||||
@@ -70,6 +72,14 @@ while [[ "$#" -gt 0 ]]; do
|
||||
RELEASE_VERSION="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--plugin-file)
|
||||
PLUGIN_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--plugin-file=*)
|
||||
PLUGIN_FILE="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
@@ -163,7 +173,7 @@ do
|
||||
git checkout "$RELEASE_BRANCH";
|
||||
|
||||
# Update version
|
||||
sed -i.bak "s/^version=.*/version=$RELEASE_VERSION/" ./gradle.properties
|
||||
sed -i "s/^version=.*/version=$RELEASE_VERSION/" ./gradle.properties
|
||||
git add ./gradle.properties
|
||||
git commit -m"chore(version): update to version 'v$RELEASE_VERSION'."
|
||||
git push
|
||||
@@ -1,6 +1,6 @@
|
||||
version=0.21.0-rc0-SNAPSHOT
|
||||
version=0.21.7
|
||||
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.priority=low
|
||||
org.gradle.priority=low
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS dashboards (
|
||||
"key" VARCHAR(250) NOT NULL PRIMARY KEY,
|
||||
"key" VARCHAR(250) NOT NULL PRIMARY KEY,
|
||||
"value" TEXT NOT NULL,
|
||||
"deleted" BOOL NOT NULL GENERATED ALWAYS AS (JQ_BOOLEAN("value", '.deleted')),
|
||||
"tenant_id" VARCHAR(250) NOT NULL GENERATED ALWAYS AS (JQ_STRING("value", '.tenantId')),
|
||||
|
||||
@@ -16,4 +16,5 @@ dependencies {
|
||||
testImplementation project(':jdbc').sourceSets.test.output
|
||||
testImplementation project(':storage-local')
|
||||
testImplementation project(':tests')
|
||||
testImplementation("io.micronaut.validation:micronaut-validation") // MysqlServiceLivenessCoordinatorTest fail to init without that
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ dependencies {
|
||||
testImplementation project(':jdbc').sourceSets.test.output
|
||||
testImplementation project(':storage-local')
|
||||
testImplementation project(':tests')
|
||||
testImplementation("io.micronaut.validation:micronaut-validation") // PostgresServiceLivenessCoordinatorTest fail to init without that
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user