diff --git a/README.md b/README.md index 9af78d4f2a6..8eb1f047b91 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This repository contains tooling applications and libraries for KIE projects. > **šŸ’” RECOMMENDED** > -> **Nix development environment**: A _devbox_ configuration is provided to automatically setup all the tools below. Read more in [here](./NIX_DEV_ENV.md). +> **Nix development environment**: A _devbox_ configuration is provided to automatically setup all the tools below. Read more in [here](./repo/NIX_DEV_ENV.md). To build and test all packages of the Apache KIE Tools project, you're going to need: diff --git a/devbox.lock b/devbox.lock index 309462aaba7..a0d26a58bd0 100644 --- a/devbox.lock +++ b/devbox.lock @@ -103,6 +103,7 @@ }, "nodejs@20.12.2": { "last_modified": "2024-06-12T20:55:33Z", + "plugin_version": "0.0.2", "resolved": "github:NixOS/nixpkgs/a9858885e197f984d92d7fe64e9fff6b2e488d40#nodejs_20", "source": "devbox-search", "version": "20.12.2", @@ -123,6 +124,7 @@ }, "python@3.12.2": { "last_modified": "2024-04-02T02:53:36Z", + "plugin_version": "0.0.4", "resolved": "github:NixOS/nixpkgs/080a4a27f206d07724b88da096e27ef63401a504#python312", "source": "devbox-search", "version": "3.12.2", diff --git a/packages/jbpm-quarkus-devui/jbpm-quarkus-devui-runtime/src/main/resources/application.properties b/packages/jbpm-quarkus-devui/jbpm-quarkus-devui-runtime/src/main/resources/application.properties deleted file mode 100644 index 4cc7054e5fe..00000000000 --- a/packages/jbpm-quarkus-devui/jbpm-quarkus-devui-runtime/src/main/resources/application.properties +++ /dev/null @@ -1,20 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -jbpm.devui.users.admin.groups=admin \ No newline at end of file diff --git a/packages/kn-plugin-workflow/go.mod b/packages/kn-plugin-workflow/go.mod index b8dfab167a6..eeb1dcaeb2e 100644 --- a/packages/kn-plugin-workflow/go.mod +++ b/packages/kn-plugin-workflow/go.mod @@ -15,12 +15,14 @@ require ( github.com/docker/distribution v2.8.2+incompatible github.com/docker/docker v24.0.9+incompatible github.com/docker/go-connections v0.4.0 + github.com/getkin/kin-openapi v0.128.0 github.com/jstemmer/go-junit-report/v2 v2.0.0 github.com/ory/viper v1.7.5 github.com/spf13/afero v1.9.5 github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/apiextensions-apiserver v0.28.1 k8s.io/apimachinery v0.28.1 k8s.io/client-go v0.28.1 @@ -41,9 +43,9 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.15.4 // indirect @@ -58,6 +60,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.4 // indirect @@ -68,12 +71,14 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pb33f/libopenapi v0.10.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.17.0 // indirect @@ -88,7 +93,7 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect golang.org/x/crypto v0.21.0 // indirect @@ -107,7 +112,6 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.3.0 // indirect k8s.io/api v0.28.1 // indirect k8s.io/component-base v0.28.1 // indirect diff --git a/packages/kn-plugin-workflow/go.sum b/packages/kn-plugin-workflow/go.sum index 7c2d83ca084..81238cb336f 100644 --- a/packages/kn-plugin-workflow/go.sum +++ b/packages/kn-plugin-workflow/go.sum @@ -104,6 +104,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= +github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -113,13 +115,13 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -131,6 +133,8 @@ github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -222,6 +226,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -262,6 +268,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -296,6 +304,8 @@ github.com/pb33f/libopenapi v0.10.1/go.mod h1:s8uj6S0DjWrwZVj20ianJBz+MMjHAbeeRY github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -314,8 +324,8 @@ github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/ github.com/relvacode/iso8601 v1.3.0 h1:HguUjsGpIMh/zsTczGN3DVJFxTU/GX+MMmzcKoMO7ko= github.com/relvacode/iso8601 v1.3.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= @@ -347,8 +357,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -358,11 +369,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/packages/kn-plugin-workflow/pkg/command/deploy.go b/packages/kn-plugin-workflow/pkg/command/deploy.go index 4a3098960b4..6db7f23f6ce 100644 --- a/packages/kn-plugin-workflow/pkg/command/deploy.go +++ b/packages/kn-plugin-workflow/pkg/command/deploy.go @@ -81,6 +81,11 @@ func NewDeployCommand() *cobra.Command { cmd.Flags().StringP("specs-dir", "p", "", "Specify a custom specs files directory") cmd.Flags().StringP("subflows-dir", "s", "", "Specify a custom subflows files directory") cmd.Flags().StringP("schemas-dir", "t", "", "Specify a custom schemas files directory") + cmd.Flags().BoolP("minify", "f", true, "Minify the OpenAPI specs files before deploying") + + if err := viper.BindPFlag("minify", cmd.Flags().Lookup("minify")); err != nil { + fmt.Println("āŒ ERROR: failed to bind minify flag") + } cmd.SetHelpFunc(common.DefaultTemplatedHelp) @@ -159,6 +164,7 @@ func runDeployCmdConfig(cmd *cobra.Command) (cfg DeployUndeployCmdConfig, err er SpecsDir: viper.GetString("specs-dir"), SchemasDir: viper.GetString("schemas-dir"), SubflowsDir: viper.GetString("subflows-dir"), + Minify: viper.GetBool("minify"), } if len(cfg.SubflowsDir) == 0 { diff --git a/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go b/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go index 2f3f32afdc9..3519c8e99fc 100644 --- a/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go +++ b/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go @@ -26,6 +26,7 @@ import ( "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/metadata" + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/specs" apimetadata "github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/metadata" "github.com/apache/incubator-kie-tools/packages/sonataflow-operator/workflowproj" ) @@ -46,9 +47,10 @@ type DeployUndeployCmdConfig struct { Profile string Image string SchemasFilesPath []string - SpecsFilesPath []string + SpecsFilesPath map[string]string SubFlowsFilesPath []string DashboardsPath []string + Minify bool } func checkEnvironment(cfg *DeployUndeployCmdConfig) error { @@ -87,7 +89,7 @@ func generateManifests(cfg *DeployUndeployCmdConfig) error { fmt.Println("\nšŸ› ļø Generating your manifests...") fmt.Println("šŸ” Looking for your SonataFlow files...") - if file, err := findSonataFlowFile(workflowExtensionsType); err != nil { + if file, err := common.FindSonataFlowFile(workflowExtensionsType); err != nil { return err } else { cfg.SonataFlowFile = file @@ -122,13 +124,25 @@ func generateManifests(cfg *DeployUndeployCmdConfig) error { supportFileExtensions := []string{metadata.JSONExtension, metadata.YAMLExtension, metadata.YMLExtension} fmt.Println("šŸ” Looking for specs files...") - files, err = common.FindFilesWithExtensions(cfg.SpecsDir, supportFileExtensions) - if err != nil { - return fmt.Errorf("āŒ ERROR: failed to get supportFiles directory: %w", err) - } - cfg.SpecsFilesPath = files - for _, file := range cfg.SpecsFilesPath { - fmt.Printf(" - āœ… Specs file found: %s\n", file) + if cfg.Minify { + minifiedfiles, err := specs.NewMinifier(&specs.OpenApiMinifierOpts{ + SpecsDir: cfg.SpecsDir, + SubflowsDir: cfg.SubflowsDir, + }).Minify() + if err != nil { + return fmt.Errorf("āŒ ERROR: failed to minify specs files: %w", err) + } + cfg.SpecsFilesPath = minifiedfiles + } else { + files, err = common.FindFilesWithExtensions(cfg.SpecsDir, supportFileExtensions) + if err != nil { + return fmt.Errorf("āŒ ERROR: failed to get supportFiles directory: %w", err) + } + cfg.SpecsFilesPath = map[string]string{} + for _, file := range files { + cfg.SpecsFilesPath[file] = file + fmt.Printf(" - āœ… Specs file found: %s\n", file) + } } fmt.Println("šŸ” Looking for schema files...") @@ -186,8 +200,8 @@ func generateManifests(cfg *DeployUndeployCmdConfig) error { handler.AddResourceAt(filepath.Base(supportFile), filepath.Base(cfg.SchemasDir), specIO) } - for _, supportFile := range cfg.SpecsFilesPath { - specIO, err := common.MustGetFile(supportFile) + for supportFile, minifiedFile := range cfg.SpecsFilesPath { + specIO, err := common.MustGetFile(minifiedFile) if err != nil { return err } @@ -234,29 +248,6 @@ func findApplicationPropertiesPath(directoryPath string) string { return filePath } -func findSonataFlowFile(extensions []string) (string, error) { - - dir, err := os.Getwd() - if err != nil { - return "", fmt.Errorf("āŒ ERROR: failed to get current directory: %w", err) - } - - var matchingFiles []string - for _, ext := range extensions { - files, _ := filepath.Glob(filepath.Join(dir, "*."+ext)) - matchingFiles = append(matchingFiles, files...) - } - - switch len(matchingFiles) { - case 0: - return "", fmt.Errorf("āŒ ERROR: no matching files found") - case 1: - return matchingFiles[0], nil - default: - return "", fmt.Errorf("āŒ ERROR: multiple SonataFlow definition files found") - } -} - func setupConfigManifestPath(cfg *DeployUndeployCmdConfig) error { if len(cfg.CustomGeneratedManifestDir) == 0 { diff --git a/packages/kn-plugin-workflow/pkg/command/specs/minify.go b/packages/kn-plugin-workflow/pkg/command/specs/minify.go new file mode 100644 index 00000000000..ddebb040dd9 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/command/specs/minify.go @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" + "github.com/spf13/cobra" +) + +func minifyCommand() *cobra.Command { + // add command minify here + + var cmd = &cobra.Command{ + Use: "minify", + Short: "Minification of OpenAPI specs", + Long: ` + Minification of OpenAPI specs: + Minification allows us to reduce the size of an OpenAPI spec file, which is essential given the maximum YAML + size supported by Kubernetes is limited to 3,145,728 bytes. + + Note: right now only OpenAPI specs are supported for minification, see examples below. + `, + Example: ` + #Minify the workflow project's OpenAPI spec file located in the current project. + {{.Name}} specs minify openapi + `, + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + cmd.AddCommand(minifyOpenApi()) + + return cmd +} diff --git a/packages/kn-plugin-workflow/pkg/command/specs/minify_test.go b/packages/kn-plugin-workflow/pkg/command/specs/minify_test.go new file mode 100644 index 00000000000..b2c0fd7d7fa --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/command/specs/minify_test.go @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMinifyCommand(t *testing.T) { + cmd := minifyCommand() + + assert.NotNil(t, cmd) + + assert.Equal(t, "minify", cmd.Use) + + subcommands := cmd.Commands() + assert.NotEmpty(t, subcommands) + assert.Equal(t, 1, len(subcommands)) + + found := false + for _, c := range subcommands { + if c.Name() == "openapi" { + found = true + break + } + } + assert.True(t, found, "minify subcommand not found") +} diff --git a/packages/kn-plugin-workflow/pkg/command/specs/openapi.go b/packages/kn-plugin-workflow/pkg/command/specs/openapi.go new file mode 100644 index 00000000000..7518267b05a --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/command/specs/openapi.go @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "fmt" + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/specs" + "github.com/ory/viper" + "github.com/spf13/cobra" + "os" +) + +func minifyOpenApi() *cobra.Command { + + var cmd = &cobra.Command{ + Use: "openapi", + Short: "Minify the openAPI spec files to trim operations only used by the workflows", + Long: ` + Minification of OpenAPI specs: + Minification allows us to reduce the size of an OpenAPI spec file, which is essential given the maximum YAML + size supported by Kubernetes is limited to 3,145,728 bytes.`, + Example: ` + #Minify the workflow project's OpenAPI spec file located in the current project. + {{.Name}} specs minify openapi + + # Specify a custom subflows files directory. (default: ./subflows) + {{.Name}} specs minify openapi --subflows-dir= + + # Specify a custom support specs directory. (default: ./specs) + {{.Name}} specs minify openapi --specs-dir= + `, + PreRunE: common.BindEnv("specs-dir", "subflows-dir"), + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + cmd.Flags().StringP("specs-dir", "p", "", "Specify a custom specs files directory") + cmd.Flags().StringP("subflows-dir", "s", "", "Specify a custom subflows files directory") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runMinifyOpenApi() + } + + return cmd + +} + +func runMinifyOpenApi() error { + + var cfg = &specs.OpenApiMinifierOpts{ + SpecsDir: viper.GetString("specs-dir"), + SubflowsDir: viper.GetString("subflows-dir"), + } + + if len(cfg.SubflowsDir) == 0 { + dir, err := os.Getwd() + cfg.SubflowsDir = dir + "/subflows" + if err != nil { + return fmt.Errorf("āŒ ERROR: failed to get default subflows workflow files folder: %w", err) + } + } + + if len(cfg.SpecsDir) == 0 { + dir, err := os.Getwd() + cfg.SpecsDir = dir + "/specs" + if err != nil { + return fmt.Errorf("āŒ ERROR: failed to get default specs files folder: %w", err) + } + } + + minifier := specs.NewMinifier(cfg) + _, err := minifier.Minify() + return err +} diff --git a/packages/kn-plugin-workflow/pkg/command/specs/openapi_test.go b/packages/kn-plugin-workflow/pkg/command/specs/openapi_test.go new file mode 100644 index 00000000000..1c5887354a7 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/command/specs/openapi_test.go @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestOpenApiCommand(t *testing.T) { + cmd := minifyOpenApi() + + assert.NotNil(t, cmd) + + assert.Equal(t, "openapi", cmd.Use) + + subcommands := cmd.Commands() + assert.Empty(t, subcommands) + + specsDirFlag := cmd.Flags().Lookup("specs-dir") + assert.NotNil(t, specsDirFlag) + assert.Equal(t, "p", specsDirFlag.Shorthand) + + subflowsDirFlag := cmd.Flags().Lookup("subflows-dir") + assert.NotNil(t, subflowsDirFlag) + assert.Equal(t, "s", subflowsDirFlag.Shorthand) +} diff --git a/packages/kn-plugin-workflow/pkg/command/specs/specs.go b/packages/kn-plugin-workflow/pkg/command/specs/specs.go new file mode 100644 index 00000000000..3de855042d4 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/command/specs/specs.go @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" + "github.com/spf13/cobra" +) + +func SpecsCommand() *cobra.Command { + // add command specs here + + var cmd = &cobra.Command{ + Use: "specs", + Short: "Set of utilities to work with specs", + Long: ` + Minification of OpenAPI specs: + Minification allows us to reduce the size of an OpenAPI spec file, which is essential given the maximum YAML + size supported by Kubernetes is limited to 3,145,728 bytes.`, + Example: ` + #Minify the workflow project's OpenAPI spec file located in the current project. + {{.Name}} specs minify openapi + `, + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + cmd.AddCommand(minifyCommand()) + + return cmd +} diff --git a/packages/kn-plugin-workflow/pkg/command/specs/specs_test.go b/packages/kn-plugin-workflow/pkg/command/specs/specs_test.go new file mode 100644 index 00000000000..58f0df1ca9a --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/command/specs/specs_test.go @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSpecsCommand(t *testing.T) { + cmd := SpecsCommand() + + assert.NotNil(t, cmd) + + assert.Equal(t, "specs", cmd.Use) + + subcommands := cmd.Commands() + assert.NotEmpty(t, subcommands) + assert.Equal(t, 1, len(subcommands)) + + found := false + for _, c := range subcommands { + if c.Name() == "minify" { + found = true + break + } + } + assert.True(t, found, "minify subcommand not found") +} diff --git a/packages/kn-plugin-workflow/pkg/common/io.go b/packages/kn-plugin-workflow/pkg/common/io.go index 85c5ca1dbd4..cd329b85553 100644 --- a/packages/kn-plugin-workflow/pkg/common/io.go +++ b/packages/kn-plugin-workflow/pkg/common/io.go @@ -60,6 +60,33 @@ func FindFilesWithExtensions(directoryPath string, extensions []string) ([]strin return filePaths, nil } +func FindSonataFlowFile(extensions []string) (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to get current directory: %w", err) + } + + matchingFiles := FindSonataFlowFiles(dir, extensions) + + switch len(matchingFiles) { + case 0: + return "", fmt.Errorf("āŒ ERROR: no matching files found") + case 1: + return matchingFiles[0], nil + default: + return "", fmt.Errorf("āŒ ERROR: multiple SonataFlow definition files found") + } +} + +func FindSonataFlowFiles(dir string, extensions []string) []string { + var matchingFiles []string + for _, ext := range extensions { + files, _ := filepath.Glob(filepath.Join(dir, "*."+ext)) + matchingFiles = append(matchingFiles, files...) + } + return matchingFiles +} + func MustGetFile(filepath string) (io.Reader, error) { file, err := os.OpenFile(filepath, os.O_RDONLY, os.ModePerm) if err != nil { diff --git a/packages/kn-plugin-workflow/pkg/root/root.go b/packages/kn-plugin-workflow/pkg/root/root.go index cc7fe2e8ea8..343b9ecab6e 100644 --- a/packages/kn-plugin-workflow/pkg/root/root.go +++ b/packages/kn-plugin-workflow/pkg/root/root.go @@ -21,6 +21,7 @@ package root import ( "fmt" + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/specs" "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/quarkus" @@ -76,6 +77,7 @@ func NewRootCommand(cfg RootCmdConfig) *cobra.Command { cmd.AddCommand(command.NewGenManifest()) cmd.AddCommand(quarkus.NewQuarkusCommand()) cmd.AddCommand(command.NewVersionCommand(cfg.Version)) + cmd.AddCommand(specs.SpecsCommand()) cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { runRootHelp(cmd, args) diff --git a/packages/kn-plugin-workflow/pkg/root/root_test.go b/packages/kn-plugin-workflow/pkg/root/root_test.go index 339897ba570..eb901e24f19 100644 --- a/packages/kn-plugin-workflow/pkg/root/root_test.go +++ b/packages/kn-plugin-workflow/pkg/root/root_test.go @@ -46,6 +46,7 @@ func TestNewRootCommand(t *testing.T) { "deploy", "quarkus", "run", + "specs", "undeploy", "gen-manifest", "version", diff --git a/packages/kn-plugin-workflow/pkg/specs/openapi_minifier.go b/packages/kn-plugin-workflow/pkg/specs/openapi_minifier.go new file mode 100644 index 00000000000..4fe7ef0747c --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/openapi_minifier.go @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/metadata" + "github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08" + "github.com/getkin/kin-openapi/openapi3" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/util/sets" + yamlk8s "k8s.io/apimachinery/pkg/util/yaml" +) + +type OpenApiMinifier struct { + workflows []string + params *OpenApiMinifierOpts + operations map[string]sets.Set[string] +} + +type OpenApiMinifierOpts struct { + RootPath string + SpecsDir string + SubflowsDir string +} + +var workflowExtensionsType = []string{metadata.YAMLSWExtension, metadata.YMLSWExtension, metadata.JSONSWExtension} + +var minifiedExtensionsType = []string{metadata.YAMLExtension, metadata.YMLExtension, metadata.JSONExtension} + +// k8sFileSizeLimit defines the maximum file size allowed (e.g., Kubernetes ConfigMap size limit is 1MB) +const k8sFileSizeLimit = 3145728 // 3MB + +func NewMinifier(params *OpenApiMinifierOpts) *OpenApiMinifier { + return &OpenApiMinifier{params: params, operations: make(map[string]sets.Set[string]), workflows: []string{}} +} + +// Minify removes unused operations from OpenAPI specs based on the functions used in workflows. +func (m *OpenApiMinifier) Minify() (map[string]string, error) { + if err := m.findWorkflowFile(); err != nil { + return nil, err + } + + m.findSubflowsFiles(m.params) + + if err := m.fetchSpecFromFunctions(); err != nil { + return nil, err + } + + if err := m.validateSpecsFiles(); err != nil { + return nil, err + } + + minifySpecsFiles, err := m.minifySpecsFiles() + if err != nil { + return nil, err + } + + return minifySpecsFiles, nil +} + +func (m *OpenApiMinifier) fetchSpecFromFunctions() error { + for _, workflowFile := range m.workflows { + err := m.fetchSpecFromFunction(workflowFile) + if err != nil { + return err + } + } + return nil +} + +func (m *OpenApiMinifier) fetchSpecFromFunction(workflowFile string) error { + workflow, err := m.GetWorkflow(workflowFile) + if err != nil { + return err + } + + relativePath := filepath.Base(m.params.SpecsDir) + + if workflow.Functions == nil { + return nil + } + + for _, function := range workflow.Functions { + if strings.HasPrefix(function.Operation, relativePath) { + trimmedPrefix := strings.TrimPrefix(function.Operation, relativePath+"/") + if !strings.Contains(trimmedPrefix, "#") { + return fmt.Errorf("Invalid operation format in function: %s", function.Operation) + } + parts := strings.SplitN(trimmedPrefix, "#", 2) + if len(parts) != 2 { + return fmt.Errorf("āŒ ERROR: Invalid operation format: %s", function.Operation) + } + apiFileName := path.Base(parts[0]) + operation := parts[1] + + if _, ok := m.operations[apiFileName]; !ok { + m.operations[apiFileName] = sets.Set[string]{} + } + m.operations[apiFileName].Insert(operation) + } + } + return nil +} + +func (m *OpenApiMinifier) validateSpecsFiles() error { + for specFile := range m.operations { + specFileName := filepath.Join(m.params.SpecsDir, specFile) + if _, err := os.Stat(specFileName); err != nil { + return fmt.Errorf("āŒ ERROR: file %s not found or can't be open", specFileName) + } + } + return nil +} + +func (m *OpenApiMinifier) minifySpecsFiles() (map[string]string, error) { + minifySpecsFiles := map[string]string{} + for key, value := range m.operations { + minifiedSpecName, err := m.minifySpecsFile(key, value) + if err != nil { + return nil, err + } + minifySpecsFiles[key] = minifiedSpecName + } + return minifySpecsFiles, nil +} + +func (m *OpenApiMinifier) minifySpecsFile(specFileName string, operations sets.Set[string]) (string, error) { + specFile := filepath.Join(m.params.SpecsDir, specFileName) + data, err := os.ReadFile(specFile) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to read OpenAPI document: %w", err) + } + + doc, err := openapi3.NewLoader().LoadFromData(data) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to load OpenAPI document: %w", err) + } + if doc.Paths == nil { + return "", fmt.Errorf("OpenAPI document %s has no paths", specFileName) + } + for key, value := range doc.Paths.Map() { + for method, operation := range value.Operations() { + if !operations.Has(operation.OperationID) { + value.SetOperation(method, nil) + } + } + if isPathItemEmpty(value) { + doc.Paths.Delete(key) + } + } + + minifiedFile, err := m.writeMinifiedFileToDisk(specFile, doc) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to write minified file of %s : %w", specFile, err) + } + finalSize, err := validateSpecsFileSize(minifiedFile) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: Minification of %s failed: %w", specFile, err) + } + + initialSize, err := os.Stat(specFile) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to get file %s info: %w", specFile, err) + } + + fmt.Printf("āœ… Minified file %s created with %d bytes (original size: %d bytes)\n", minifiedFile, finalSize, initialSize.Size()) + return minifiedFile, nil +} + +func (m *OpenApiMinifier) findWorkflowFile() error { + file, err := common.FindSonataFlowFile(workflowExtensionsType) + if err != nil { + return err + } + m.workflows = append(m.workflows, file) + return nil +} + +func (m *OpenApiMinifier) findSubflowsFiles(cfg *OpenApiMinifierOpts) { + files := common.FindSonataFlowFiles(cfg.SubflowsDir, workflowExtensionsType) + m.workflows = append(m.workflows, files...) +} + +func (m *OpenApiMinifier) GetWorkflow(workflowFile string) (*v1alpha08.Flow, error) { + workflow := &v1alpha08.Flow{} + file, err := os.Open(workflowFile) + if err != nil { + return workflow, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return workflow, fmt.Errorf("failed to read workflow file %s: %w", workflowFile, err) + } + + if err = yamlk8s.Unmarshal(data, workflow); err != nil { + return workflow, fmt.Errorf("failed to unmarshal workflow file %s: %w", workflowFile, err) + } + return workflow, nil +} + +func (m *OpenApiMinifier) writeMinifiedFileToDisk(specFile string, doc *openapi3.T) (string, error) { + var output []byte + var err error + if strings.HasSuffix(specFile, metadata.YAMLExtension) || strings.HasSuffix(specFile, metadata.YMLExtension) { + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(2) + err = encoder.Encode(doc) + output = buf.Bytes() + } else { + output, err = json.Marshal(doc) + } + + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to marshal OpenAPI document: %w", err) + } + + minifiedSpecFile, err := MinifiedName(specFile) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to get minified file name: %w", err) + } + + err = os.WriteFile(minifiedSpecFile, output, 0644) + if err != nil { + return "", fmt.Errorf("āŒ ERROR: failed to write OpenAPI document: %w", err) + } + return minifiedSpecFile, nil +} + +func validateSpecsFileSize(specFile string) (int64, error) { + file, err := os.Stat(specFile) + if err != nil { + return -1, fmt.Errorf("āŒ ERROR: failed to get file info: %w", err) + } + if file.Size() >= k8sFileSizeLimit { + return -1, fmt.Errorf("āŒ ERROR: Minified file %s exceeds the size limit of %d bytes", specFile, k8sFileSizeLimit) + } + return file.Size(), nil +} + +func isPathItemEmpty(pathItem *openapi3.PathItem) bool { + return pathItem.Get == nil && + pathItem.Put == nil && + pathItem.Post == nil && + pathItem.Delete == nil && + pathItem.Options == nil && + pathItem.Head == nil && + pathItem.Patch == nil && + pathItem.Trace == nil +} + +func MinifiedName(specFile string) (string, error) { + for _, ext := range minifiedExtensionsType { + if strings.HasSuffix(specFile, ext) { + return strings.TrimSuffix(specFile, ext) + ".min" + ext, nil + } + } + return "", fmt.Errorf("āŒ ERROR: unknown file extension: %s", specFile) +} diff --git a/packages/kn-plugin-workflow/pkg/specs/openapi_minifier_test.go b/packages/kn-plugin-workflow/pkg/specs/openapi_minifier_test.go new file mode 100644 index 00000000000..b3be4bfc93e --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/openapi_minifier_test.go @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "fmt" + "io" + "os" + "path" + "strings" + "testing" + + "github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08" + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/yaml" +) + +type spec struct { + file string + initial int + minified int +} + +type minifyTest struct { + workflowFile string + openapiSpecFiles []spec + specsDir string + subflowsDir string + subflows []string +} + +func TestOpenAPIMinify(t *testing.T) { + tests := []minifyTest{ + { + workflowFile: "testdata/workflow.sw.yaml", // 4 functions, 2 of them are ref to the same openapi spec + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 3}}, // 5 operations, 3 must left + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow.sw.json", // 4 functions, 2 of them are ref to the same openapi spec + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 3}}, // 5 operations, 3 must left + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-json-openapi.sw.json", // 4 functions, 2 of them are ref to the same openapi spec + openapiSpecFiles: []spec{{"testdata/flink-openapi-json.json", 5, 3}}, // 5 operations, 3 must left + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow2.sw.yaml", // 4 functions, 1 per openapi spec file + openapiSpecFiles: []spec{ + {"testdata/flink1-openapi.yaml", 3, 1}, + {"testdata/flink2-openapi.yaml", 3, 1}, + {"testdata/flink3-openapi.yaml", 3, 1}, + {"testdata/flink4-openapi.yaml", 3, 1}}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty.sw.yaml", // check don't fail with empty workflow + openapiSpecFiles: []spec{}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty.sw.yaml", // check all operations are removed + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 0}}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-mySpecsDir.sw.yaml", // check all operations are removed, with different specs dir + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 3}}, + specsDir: "mySpecsDir", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-mySpecsDir-one-finction.sw.yaml", // check all operations are removed, with different specs dir + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 2}}, + specsDir: "mySpecsDir", + subflowsDir: "subflows", + subflows: []string{"testdata/subflow-mySpecsDir.sw.yaml"}, + }, + { + workflowFile: "testdata/workflow-empty.sw.yaml", // check all operations are removed, with different subflow dir + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 0}}, + specsDir: "mySpecsDir", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty2.sw.yaml", // check don't fail with workflow with non openapi functions + openapiSpecFiles: []spec{}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty2.sw.yaml", // check functions is on subflow + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 2}}, + specsDir: "specs", + subflowsDir: "subflows", + subflows: []string{"testdata/subflow.sw.yaml"}, + }, + { + workflowFile: "testdata/workflow-empty2.sw.yaml", // check functions is on subflow, with different subflow and specs dirs + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 2}}, + specsDir: "mySpecsDir", + subflowsDir: "mySubFlowDir", + subflows: []string{"testdata/subflow-mySpecsDir.sw.yaml"}, + }, + { + workflowFile: "testdata/workflow-greeting.sw.yaml", // check we can process subflows with the same file name but different extensions + openapiSpecFiles: []spec{{"testdata/greetingAPI.yaml", 3, 1}}, + specsDir: "specs", + subflowsDir: "custom_subflows", + subflows: []string{"testdata/hello.sw.json", "testdata/hello.sw.yaml"}, // 2 subflows, 1 of them has a function that uses the greetingAPI.yaml + }, + { + workflowFile: "testdata/workflow-greeting.sw.yaml", // check we can process subflows with the same file name but different extensions + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 2}}, + specsDir: "custom_specs", + subflowsDir: "custom_subflows", + subflows: []string{"testdata/subflow-custom.sw.json", "testdata/subflow-custom.sw.yaml"}, // 2 subflows, each one has a function that uses the flink-openapi.yaml + }, + { + workflowFile: "testdata/workflow-subflow-custom.sw.yaml", // workflow with a function that uses a subflow with a function that uses the flink-openapi.yaml + openapiSpecFiles: []spec{{"testdata/flink-openapi.yaml", 5, 3}}, + specsDir: "custom_specs", + subflowsDir: "custom_subflows", + subflows: []string{"testdata/subflow-custom.sw.json", "testdata/subflow-custom.sw.yaml"}, // 2 subflows, each one has a function that uses the flink-openapi.yaml + }, + } + + current, err := os.Getwd() + if err != nil { + t.Fatalf("Error getting current directory: %v", err) + } + + for _, test := range tests { + t.Run(test.workflowFile, func(t *testing.T) { + if err := os.Mkdir(test.specsDir, 0755); err != nil { + t.Fatalf("Error creating specs directory: %v", err) + } + defer os.RemoveAll(test.specsDir) + if err := copyFile(test.workflowFile, path.Base(test.workflowFile)); err != nil { + t.Fatalf("Error copying workflow file: %v", err) + } + defer os.Remove(path.Base(test.workflowFile)) + if len(test.subflows) > 0 { + if err := os.Mkdir(test.subflowsDir, 0755); err != nil { + t.Fatalf("Error creating subflows directory: %v", err) + } + defer os.RemoveAll(test.subflowsDir) + for _, subflow := range test.subflows { + if err := copyFile(subflow, path.Join(test.subflowsDir, path.Base(subflow))); err != nil { + t.Fatalf("Error copying subflow file: %v", err) + } + } + } + for _, openapiSpecFile := range test.openapiSpecFiles { + if err := copyFile(openapiSpecFile.file, path.Join(test.specsDir, path.Base(openapiSpecFile.file))); err != nil { + t.Fatalf("Error copying openapi spec file: %v", err) + } + } + + minifiedfiles, err := NewMinifier(&OpenApiMinifierOpts{ + SpecsDir: path.Join(current, test.specsDir), + SubflowsDir: path.Join(current, test.subflowsDir), + }).Minify() + if err != nil { + t.Fatalf("Error minifying openapi specs: %v", err) + } + checkInitial(t, test) + checkResult(t, test, minifiedfiles) + }) + } +} + +// checkInitial checks the initial number of operations in the openapi specs +func checkInitial(t *testing.T, test minifyTest) { + for _, spec := range test.openapiSpecFiles { + data, err := os.ReadFile(spec.file) + if err != nil { + t.Fatalf("Error reading openapi spec file: %v", err) + } + doc, err := openapi3.NewLoader().LoadFromData(data) + if err != nil { + t.Fatalf("Error loading openapi spec file: %v", err) + } + assert.Equalf(t, spec.initial, len(doc.Paths.Map()), "Initial number of operations in %s is not correct", spec.file) + } +} + +// checkResult checks the number of operations in the minified openapi specs +func checkResult(t *testing.T, test minifyTest, minifiedFiles map[string]string) { + workflow, err := parseWorkflow(path.Base(test.workflowFile)) + if err != nil { + t.Fatalf("Error parsing workflow file: %v", err) + } + + functions := map[string]sets.Set[string]{} + parseFunctions(t, functions, workflow, test) + for _, subflow := range test.subflows { + subflowWorkflow, err := parseWorkflow(subflow) + if err != nil { + t.Fatalf("Error parsing subflow file: %v", err) + } + parseFunctions(t, functions, subflowWorkflow, test) + } + + countOfOperationInSpecs := map[string]int{} + + for file, operationSet := range functions { + minified := minifiedFiles[file] + data, err := os.ReadFile(minified) + if err != nil { + t.Fatalf("Error reading minified file %s: %v", minified, err) + } + doc, err := openapi3.NewLoader().LoadFromData(data) + for _, value := range doc.Paths.Map() { + for _, operation := range value.Operations() { + assert.True(t, operationSet.Has(operation.OperationID), "Operation %s not found in workflow", operation.OperationID) + } + } + countOfOperationInSpecs[file] = len(doc.Paths.Map()) + assert.Equal(t, len(operationSet), len(doc.Paths.Map())) + } + for _, spec := range test.openapiSpecFiles { + assert.Equalf(t, spec.minified, countOfOperationInSpecs[path.Base(spec.file)], "Minified number of operations in %s is not correct", spec.file) + } + +} + +func parseFunctions(t *testing.T, functions map[string]sets.Set[string], workflow *v1alpha08.Flow, test minifyTest) { + for _, function := range workflow.Functions { + if strings.HasPrefix(function.Operation, test.specsDir) { + trimmedPrefix := strings.TrimPrefix(function.Operation, test.specsDir+"/") + if !strings.Contains(trimmedPrefix, "#") { + t.Fatalf("Invalid operation format in function: %s", function.Operation) + } + parts := strings.SplitN(trimmedPrefix, "#", 2) + if len(parts) != 2 { + t.Fatalf("Invalid operation format: %s", function.Operation) + } + apiFileName := path.Base(parts[0]) + operation := parts[1] + + if _, ok := functions[apiFileName]; !ok { + functions[apiFileName] = sets.Set[string]{} + } + functions[apiFileName].Insert(operation) + } + } +} + +func parseWorkflow(workflowFile string) (*v1alpha08.Flow, error) { + workflow := &v1alpha08.Flow{} + file, err := os.Open(workflowFile) + if err != nil { + return workflow, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return workflow, fmt.Errorf("failed to read workflow file %s: %w", workflowFile, err) + } + + if err = yaml.Unmarshal(data, workflow); err != nil { + return workflow, fmt.Errorf("failed to unmarshal workflow file %s: %w", workflowFile, err) + } + return workflow, nil +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + + return nil +} diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/flink-openapi-json.json b/packages/kn-plugin-workflow/pkg/specs/testdata/flink-openapi-json.json new file mode 100644 index 00000000000..7049ac6b90d --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/flink-openapi-json.json @@ -0,0 +1,123 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Flink JobManager REST API", + "contact": { + "email": "user@flink.apache.org" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "v1/1.20-SNAPSHOT" + }, + "paths": { + "/jars": { + "get": { + "description": "Returns a list of all jars previously uploaded via '/jars/upload'.", + "operationId": "getJarList", + "responses": { + "200": { + "description": "The request was successful." + } + } + } + }, + "/jars/{jarid}/run": { + "post": { + "description": "Submits a job for execution.", + "operationId": "submitJobFromJar", + "parameters": [ + { + "name": "jarid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The request was successful." + } + } + } + }, + "/jobs/{jobid}": { + "get": { + "description": "Returns details of a job.", + "operationId": "getJobDetails", + "parameters": [ + { + "name": "jobid", + "in": "path", + "description": "32-character hexadecimal string value that identifies a job.", + "required": true + } + ], + "responses": { + "200": { + "description": "The request was successful." + } + } + }, + "patch": { + "description": "Terminates a job.", + "operationId": "cancelJob", + "parameters": [ + { + "name": "jobid", + "in": "path", + "description": "32-character hexadecimal string value that identifies a job.", + "required": true + }, + { + "name": "mode", + "in": "query", + "description": "String value that specifies the termination mode. The only supported value is: \"cancel\".", + "required": false, + "style": "form" + } + ], + "responses": { + "202": { + "description": "The request was successful." + } + } + } + }, + "/jars/upload": { + "post": { + "description": "Uploads a jar.", + "operationId": "uploadJar", + "requestBody": { + "content": { + "application/x-java-archive": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "The request was successful." + } + } + } + }, + "/test": { + "get": { + "description": "Test endpoint", + "operationId": "test", + "responses": { + "200": { + "description": "The request was successful." + } + } + } + } + } +} diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/flink-openapi.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/flink-openapi.yaml new file mode 100644 index 00000000000..14874d7adbb --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/flink-openapi.yaml @@ -0,0 +1,79 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars: + get: + description: Returns a list of all jars previously uploaded via '/jars/upload'. + operationId: getJarList + responses: + "200": + description: The request was successful. + /jars/{jarid}/run: + post: + description: Submits a job for execution. + operationId: submitJobFromJar + parameters: + - name: jarid + in: path + required: true + schema: + type: string + responses: + "200": + description: The request was successful. + /jobs/{jobid}: + get: + description: Returns details of a job. + operationId: getJobDetails + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + responses: + "200": + description: The request was successful. + patch: + description: Terminates a job. + operationId: cancelJob + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + - name: mode + in: query + description: "String value that specifies the termination mode. The only supported\ + \ value is: \"cancel\"." + required: false + style: form + responses: + "202": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/flink1-openapi.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/flink1-openapi.yaml new file mode 100644 index 00000000000..585a9cc2ac7 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/flink1-openapi.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars: + get: + description: Returns a list of all jars previously uploaded via '/jars/upload'. + operationId: getJarList + responses: + "200": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/flink2-openapi.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/flink2-openapi.yaml new file mode 100644 index 00000000000..9827c5878b1 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/flink2-openapi.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars/{jarid}/run: + post: + description: Submits a job for execution. + operationId: submitJobFromJar + parameters: + - name: jarid + in: path + required: true + schema: + type: string + responses: + "200": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/flink3-openapi.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/flink3-openapi.yaml new file mode 100644 index 00000000000..321baa6bae6 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/flink3-openapi.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jobs/{jobid}: + get: + description: Returns details of a job. + operationId: getJobDetails + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + responses: + "200": + description: The request was successful. + patch: + description: Terminates a job. + operationId: cancelJob + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + - name: mode + in: query + description: "String value that specifies the termination mode. The only supported\ + \ value is: \"cancel\"." + required: false + style: form + responses: + "202": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/flink4-openapi.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/flink4-openapi.yaml new file mode 100644 index 00000000000..585a9cc2ac7 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/flink4-openapi.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars: + get: + description: Returns a list of all jars previously uploaded via '/jars/upload'. + operationId: getJarList + responses: + "200": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/greetingAPI.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/greetingAPI.yaml new file mode 100644 index 00000000000..6402f4d0a1b --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/greetingAPI.yaml @@ -0,0 +1,282 @@ +--- +openapi: 3.0.0 +info: + title: greeting-flow API + version: "1.0" +paths: + /: + post: + requestBody: + content: + "*/*": + schema: + $ref: "#/components/schemas/CloudEvent" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Response" + /hello: + get: + tags: + - Reactive Greeting Resource + operationId: hello + responses: + "200": + description: OK + content: + text/plain: + schema: + type: string + /messaging/topics: + get: + tags: + - Quarkus Topics Information Resource + responses: + "200": + description: OK +components: + schemas: + Type: + type: object + properties: + tags: + uniqueItems: true + type: array + items: + type: string + CloudEvent: + type: object + properties: + specVersion: + $ref: "#/components/schemas/SpecVersion" + id: + type: string + type: + type: string + source: + format: uri + type: string + dataContentType: + type: string + dataSchema: + format: uri + type: string + subject: + type: string + time: + format: date-time + type: string + attributeNames: + uniqueItems: true + type: array + items: + type: string + extensionNames: + uniqueItems: true + type: array + items: + type: string + data: + $ref: "#/components/schemas/CloudEventData" + CloudEventData: + type: object + EntityTag: + type: object + properties: + value: + type: string + weak: + type: boolean + Family: + enum: + - INFORMATIONAL + - SUCCESSFUL + - REDIRECTION + - CLIENT_ERROR + - SERVER_ERROR + - OTHER + type: string + Link: + type: object + properties: + uri: + format: uri + type: string + uriBuilder: + $ref: "#/components/schemas/UriBuilder" + rel: + type: string + rels: + type: array + items: + type: string + title: + type: string + type: + type: string + params: + type: object + additionalProperties: + type: string + Locale: + type: object + properties: + language: + type: string + script: + type: string + country: + type: string + variant: + type: string + extensionKeys: + uniqueItems: true + type: array + items: + format: byte + type: string + unicodeLocaleAttributes: + uniqueItems: true + type: array + items: + type: string + unicodeLocaleKeys: + uniqueItems: true + type: array + items: + type: string + iSO3Language: + type: string + iSO3Country: + type: string + displayLanguage: + type: string + displayScript: + type: string + displayCountry: + type: string + displayVariant: + type: string + displayName: + type: string + MediaType: + type: object + properties: + type: + type: string + subtype: + type: string + parameters: + type: object + additionalProperties: + type: string + wildcardType: + type: boolean + wildcardSubtype: + type: boolean + MultivaluedMapStringObject: + type: object + additionalProperties: + type: array + items: + type: object + MultivaluedMapStringString: + type: object + additionalProperties: + type: array + items: + type: string + NewCookie: + type: object + properties: + name: + type: string + value: + type: string + version: + format: int32 + type: integer + path: + type: string + domain: + type: string + comment: + type: string + maxAge: + format: int32 + type: integer + expiry: + format: date + type: string + secure: + type: boolean + httpOnly: + type: boolean + Response: + type: object + properties: + status: + format: int32 + type: integer + statusInfo: + $ref: "#/components/schemas/StatusType" + entity: + type: object + mediaType: + $ref: "#/components/schemas/MediaType" + language: + $ref: "#/components/schemas/Locale" + length: + format: int32 + type: integer + allowedMethods: + uniqueItems: true + type: array + items: + type: string + cookies: + type: object + additionalProperties: + $ref: "#/components/schemas/NewCookie" + entityTag: + $ref: "#/components/schemas/EntityTag" + date: + format: date + type: string + lastModified: + format: date + type: string + location: + format: uri + type: string + links: + uniqueItems: true + type: array + items: + $ref: "#/components/schemas/Link" + metadata: + $ref: "#/components/schemas/MultivaluedMapStringObject" + headers: + $ref: "#/components/schemas/MultivaluedMapStringObject" + stringHeaders: + $ref: "#/components/schemas/MultivaluedMapStringString" + SpecVersion: + enum: + - V03 + - V1 + type: string + StatusType: + type: object + properties: + statusCode: + format: int32 + type: integer + family: + $ref: "#/components/schemas/Family" + reasonPhrase: + type: string + UriBuilder: + type: object diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/hello.sw.json b/packages/kn-plugin-workflow/pkg/specs/testdata/hello.sw.json new file mode 100644 index 00000000000..bb79e3ff84d --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/hello.sw.json @@ -0,0 +1,33 @@ +{ + "id": "helloworldjson", + "version": "1.0", + "specVersion": "0.8", + "name": "Hello World Workflow", + "description": "JSON based hello world workflow", + "start": "Inject Hello World SubFlow", + "functions": [ + { + "name": "HelloFunction", + "operation": "specs/greetingAPI.yaml#hello", + "type": "rest" + } + ], + "states": [ + { + "name": "Inject Hello World SubFlow", + "type": "inject", + "data": { + "greeting-subflow": "Hello World SubFlow" + }, + "transition": "Inject Mantra SubFlow" + }, + { + "name": "Inject Mantra SubFlow", + "type": "inject", + "data": { + "mantra-subflow": "SubFlow Serverless Workflow is awesome!" + }, + "end": true + } + ] +} diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/hello.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/hello.sw.yaml new file mode 100644 index 00000000000..89577531933 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/hello.sw.yaml @@ -0,0 +1,12 @@ +id: helloworldyaml +version: "1.0.0" +specVersion: "0.8" +name: Hello World Workflow +description: Inject Hello World +start: Hello State +states: + - name: Hello State + type: inject + data: + result: Hello World YAML SubFlow! Eder + end: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-custom.sw.json b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-custom.sw.json new file mode 100644 index 00000000000..aff6964b64f --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-custom.sw.json @@ -0,0 +1,33 @@ +{ + "id": "helloworldjson", + "version": "1.0", + "specVersion": "0.8", + "name": "Hello World Workflow", + "description": "JSON based hello world workflow", + "start": "Inject Hello World SubFlow", + "functions": [ + { + "name": "HelloFunction", + "operation": "custom_specs/flink-openapi.yaml#getJarList", + "type": "rest" + } + ], + "states": [ + { + "name": "Inject Hello World SubFlow", + "type": "inject", + "data": { + "greeting-subflow": "Hello World SubFlow" + }, + "transition": "Inject Mantra SubFlow" + }, + { + "name": "Inject Mantra SubFlow", + "type": "inject", + "data": { + "mantra-subflow": "SubFlow Serverless Workflow is awesome!" + }, + "end": true + } + ] +} diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-custom.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-custom.sw.yaml new file mode 100644 index 00000000000..2dce01fa269 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-custom.sw.yaml @@ -0,0 +1,22 @@ +--- +id: "helloworldyaml" +version: "1.0" +specVersion: "0.8" +name: "Hello World Workflow" +description: "JSON based hello world workflow" +start: "Inject Hello World SubFlow" +functions: + - name: "HelloFunction" + operation: "custom_specs/flink-openapi.yaml#submitJobFromJar" + type: "rest" +states: + - name: "Inject Hello World SubFlow" + type: "inject" + data: + greeting-subflow: "Hello World SubFlow" + transition: "Inject Mantra SubFlow" + - name: "Inject Mantra SubFlow" + type: "inject" + data: + mantra-subflow: "SubFlow Serverless Workflow is awesome!" + end: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-mySpecsDir.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-mySpecsDir.sw.yaml new file mode 100644 index 00000000000..d56c3e798d2 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow-mySpecsDir.sw.yaml @@ -0,0 +1,30 @@ +id: fraudhandling +name: Fraud Handling +expressionLang: jsonpath +start: FraudHandling +version: "1.0" +events: + - kind: produced + name: FraudEvaluation + type: fraudEvaluation + source: fraudEvaluation +functions: + - name: getFlinkJobs + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: mySpecsDir/flink-openapi.yaml#submitJobFromJar +states: + - name: FraudHandling + type: switch + dataConditions: + - condition: "{{ $.[?(@.total > 1000)] }}" + transition: FraudVerificationNeeded + - condition: "{{ $.[?(@.total <= 1000)] }}" + end: true + - name: FraudVerificationNeeded + type: inject + data: + fraudEvaluation: true + end: + produceEvents: + - eventRef: FraudEvaluation diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/subflow.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow.sw.yaml new file mode 100644 index 00000000000..776924c8455 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/subflow.sw.yaml @@ -0,0 +1,30 @@ +id: fraudhandling +name: Fraud Handling +expressionLang: jsonpath +start: FraudHandling +version: "1.0" +events: + - kind: produced + name: FraudEvaluation + type: fraudEvaluation + source: fraudEvaluation +functions: + - name: getFlinkJobs + operation: specs/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: specs/flink-openapi.yaml#submitJobFromJar +states: + - name: FraudHandling + type: switch + dataConditions: + - condition: "{{ $.[?(@.total > 1000)] }}" + transition: FraudVerificationNeeded + - condition: "{{ $.[?(@.total <= 1000)] }}" + end: true + - name: FraudVerificationNeeded + type: inject + data: + fraudEvaluation: true + end: + produceEvents: + - eventRef: FraudEvaluation diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-empty.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-empty.sw.yaml new file mode 100644 index 00000000000..111322b346e --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-empty.sw.yaml @@ -0,0 +1,33 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-empty2.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-empty2.sw.yaml new file mode 100644 index 00000000000..bd4c84d6c89 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-empty2.sw.yaml @@ -0,0 +1,29 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-greeting.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-greeting.sw.yaml new file mode 100644 index 00000000000..76feeb20c86 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-greeting.sw.yaml @@ -0,0 +1,18 @@ +id: subworkflows +version: "1.0.0" +specVersion: "0.8" +name: Parallel Execution Workflow +description: Executes two branches in parallel +start: ParallelExec +states: + - name: ParallelExec + type: parallel + completionType: allOf + branches: + - name: Sub1 + actions: + - subFlowRef: helloworldyaml + - name: Sub2 + actions: + - subFlowRef: helloworldjson + end: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-json-openapi.sw.json b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-json-openapi.sw.json new file mode 100644 index 00000000000..600c518e94b --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-json-openapi.sw.json @@ -0,0 +1,70 @@ +{ + "id": "flink-workflow", + "version": "1.0", + "specVersion": "0.8", + "name": "flink workflow", + "description": "Create a starter flink job management", + "functions": [ + { + "name": "getFlinkJobs", + "operation": "specs/flink-openapi-json.json#getJarList" + }, + { + "name": "runFlinkJob", + "operation": "specs/flink-openapi-json.json#submitJobFromJar" + }, + { + "name": "stopFlinkJob", + "operation": "specs/flink-openapi-json.json#cancelJob" + }, + { + "name": "getJars", + "operation": "specs/flink-openapi-json.json#getJarList" + }, + { + "name": "sysout", + "type": "custom", + "operation": "sysout" + } + ], + "start": "Get Flink Jars", + "states": [ + { + "name": "Get Flink Jars", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "name": "Get Flink Jars", + "functionRef": { + "refName": "getJars" + } + } + ], + "transition": "Run Flink Job" + }, + { + "name": "Run Flink Job", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "actionDataFilter": { + "useResults": true + }, + "name": "Run Flink Job", + "functionRef": { + "refName": "runFlinkJob", + "arguments": { + "jarid": "72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar", + "entry-class": "com.demo.flink.streaming.StreamingJob" + } + } + } + ], + "end": { + "terminate": true + } + } + ] +} diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-mySpecsDir-one-finction.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-mySpecsDir-one-finction.sw.yaml new file mode 100644 index 00000000000..5fdb58a6b5e --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-mySpecsDir-one-finction.sw.yaml @@ -0,0 +1,35 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-mySpecsDir.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-mySpecsDir.sw.yaml new file mode 100644 index 00000000000..4aa6d2cafa4 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-mySpecsDir.sw.yaml @@ -0,0 +1,41 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: mySpecsDir/flink-openapi.yaml#submitJobFromJar + - name: stopFlinkJob + operation: mySpecsDir/flink-openapi.yaml#cancelJob + - name: getJars + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-subflow-custom.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-subflow-custom.sw.yaml new file mode 100644 index 00000000000..ac15349841f --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow-subflow-custom.sw.yaml @@ -0,0 +1,35 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: stopFlinkJob + operation: custom_specs/flink-openapi.yaml#cancelJob + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow.sw.json b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow.sw.json new file mode 100644 index 00000000000..54a0ed68408 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow.sw.json @@ -0,0 +1,70 @@ +{ + "id": "flink-workflow", + "version": "1.0", + "specVersion": "0.8", + "name": "flink workflow", + "description": "Create a starter flink job management", + "functions": [ + { + "name": "getFlinkJobs", + "operation": "specs/flink-openapi.yaml#getJarList" + }, + { + "name": "runFlinkJob", + "operation": "specs/flink-openapi.yaml#submitJobFromJar" + }, + { + "name": "stopFlinkJob", + "operation": "specs/flink-openapi.yaml#cancelJob" + }, + { + "name": "getJars", + "operation": "specs/flink-openapi.yaml#getJarList" + }, + { + "name": "sysout", + "type": "custom", + "operation": "sysout" + } + ], + "start": "Get Flink Jars", + "states": [ + { + "name": "Get Flink Jars", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "name": "Get Flink Jars", + "functionRef": { + "refName": "getJars" + } + } + ], + "transition": "Run Flink Job" + }, + { + "name": "Run Flink Job", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "actionDataFilter": { + "useResults": true + }, + "name": "Run Flink Job", + "functionRef": { + "refName": "runFlinkJob", + "arguments": { + "jarid": "72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar", + "entry-class": "com.demo.flink.streaming.StreamingJob" + } + } + } + ], + "end": { + "terminate": true + } + } + ] +} diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow.sw.yaml new file mode 100644 index 00000000000..45b28a077a5 --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow.sw.yaml @@ -0,0 +1,41 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: specs/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: specs/flink-openapi.yaml#submitJobFromJar + - name: stopFlinkJob + operation: specs/flink-openapi.yaml#cancelJob + - name: getJars + operation: specs/flink-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/kn-plugin-workflow/pkg/specs/testdata/workflow2.sw.yaml b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow2.sw.yaml new file mode 100644 index 00000000000..3232c6bccaa --- /dev/null +++ b/packages/kn-plugin-workflow/pkg/specs/testdata/workflow2.sw.yaml @@ -0,0 +1,41 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: specs/flink1-openapi.yaml#getJarList + - name: runFlinkJob + operation: specs/flink2-openapi.yaml#submitJobFromJar + - name: stopFlinkJob + operation: specs/flink3-openapi.yaml#cancelJob + - name: getJars + operation: specs/flink4-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx b/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx index 5f2ea170b45..a0fdb9a1fc0 100644 --- a/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx +++ b/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { Flex, FlexItem } from "@patternfly/react-core/dist/js/layouts/Flex"; import { Grid, GridItem } from "@patternfly/react-core/dist/js/layouts/Grid"; import { Split, SplitItem } from "@patternfly/react-core/dist/js/layouts/Split"; @@ -48,9 +48,15 @@ import WorkflowVariables from "../WorkflowVariables/WorkflowVariables"; import WorkflowDetailsMilestonesPanel from "../WorkflowDetailsMilestonesPanel/WorkflowDetailsMilestonesPanel"; import WorkflowDetailsTimelinePanel from "../WorkflowDetailsTimelinePanel/WorkflowDetailsTimelinePanel"; import SwfCombinedEditor from "../SwfCombinedEditor/SwfCombinedEditor"; -import { Job, WorkflowInstance, WorkflowInstanceState } from "@kie-tools/runtime-tools-swf-gateway-api/dist/types"; +import { + Job, + JobStatus, + WorkflowInstance, + WorkflowInstanceState, +} from "@kie-tools/runtime-tools-swf-gateway-api/dist/types"; const SWFCOMBINEDEDITOR_WIDTH = 1000; +const CHECK_EXPIRED_JOBS_TIMEOUT = 5000; interface WorkflowDetailsProps { isEnvelopeConnectedToChannel: boolean; @@ -78,7 +84,7 @@ const WorkflowDetails: React.FC = ({ isEnvelopeConnectedTo try { const workflowResponse: WorkflowInstance = await driver.workflowDetailsQuery(workflowDetails.id); workflowResponse && setData(workflowResponse); - getAllJobs(); + loadJobs(); setIsLoading(false); } catch (errorString) { setError(errorString); @@ -86,10 +92,36 @@ const WorkflowDetails: React.FC = ({ isEnvelopeConnectedTo } }; - const getAllJobs = async (): Promise => { + const loadJobs = useCallback(async () => { const jobsResponse: Job[] = await driver.jobsQuery(workflowDetails.id); jobsResponse && setJobs(jobsResponse); - }; + }, [workflowDetails.id, driver]); + + /** + * check every N seconds for jobs which are SCHEDULED and epired + * @return + */ + const checkExpiredJobs = useCallback(async () => { + await new Promise((resolve) => setTimeout(resolve, CHECK_EXPIRED_JOBS_TIMEOUT)); + const scheduledJobs = jobs.filter((job) => job.status === JobStatus.Scheduled); + + if (!scheduledJobs.length) { + return; + } + + const expiredJob = scheduledJobs.find((job) => new Date(job.expirationTime) < new Date()); + + if (expiredJob) { + loadJobs(); + return; + } + + checkExpiredJobs(); + }, [loadJobs, jobs]); + + useEffect(() => { + jobs.length && checkExpiredJobs(); + }, [jobs, checkExpiredJobs]); useEffect(() => { const getVariableJSON = (): void => { @@ -101,7 +133,7 @@ const WorkflowDetails: React.FC = ({ isEnvelopeConnectedTo if (isEnvelopeConnectedToChannel) { getVariableJSON(); } - }, [data]); + }, [data, isEnvelopeConnectedToChannel, workflowDetails.id]); useEffect(() => { if (variableError && variableError.length > 0) { @@ -109,12 +141,16 @@ const WorkflowDetails: React.FC = ({ isEnvelopeConnectedTo } }, [variableError]); - useEffect(() => { - if (isEnvelopeConnectedToChannel) { - setData(workflowDetails); - getAllJobs(); - } - }, [isEnvelopeConnectedToChannel]); + useEffect( + () => { + if (isEnvelopeConnectedToChannel) { + setData(workflowDetails); + loadJobs(); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [isEnvelopeConnectedToChannel] + ); const handleSave = (): void => { driver diff --git a/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx b/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx index f54261b947a..1b126ca33ca 100644 --- a/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx +++ b/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx @@ -18,13 +18,22 @@ */ import * as React from "react"; -import { render } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import WorkflowDetails from "@kie-tools/runtime-tools-swf-enveloped-components/dist/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails"; -import { WorkflowInstance, WorkflowInstanceState } from "@kie-tools/runtime-tools-swf-gateway-api/dist/types"; +import { + Job, + JobStatus, + WorkflowInstance, + WorkflowInstanceState, +} from "@kie-tools/runtime-tools-swf-gateway-api/dist/types"; + +jest.useFakeTimers(); const mockDriver = { - jobsQuery: jest.fn(), + jobsQuery: jest.fn((_id: string) => { + return [{ ...sampleJob, expirationTime: new Date(Date.now() + 10000).toISOString() }]; + }), }; const sampleWorkflowDetails: WorkflowInstance = { @@ -43,6 +52,25 @@ const sampleWorkflowDetails: WorkflowInstance = { nodes: [], }; +const sampleJob: Job = { + id: "a62d9d0a-87ea-4c13-87fb-67965d133020", + priority: 0, + lastUpdate: new Date("2024-10-30T15:31:46.709Z"), + workflowId: sampleWorkflowDetails.processId, + workflowInstanceId: sampleWorkflowDetails.id, + status: JobStatus.Scheduled, + expirationTime: new Date("2024-10-30T15:31:46.709Z"), + callbackEndpoint: + "http://localhost:4000/management/jobs/callback_state_timeouts/instances/9750c042-3fb2-40b7-96ba-ff10b6178c58/timers/-1", + repeatInterval: 0, + repeatLimit: 0, + scheduledId: "143", + retries: 0, + endpoint: "http://localhost:4000/jobs", + nodeInstanceId: "ee6c3f6e-8bc3-43dc-a249-dad1b19b52bb", + executionCounter: 0, +}; + describe("WorkflowDetails component", () => { beforeEach(() => { jest.clearAllMocks(); @@ -86,4 +114,51 @@ describe("WorkflowDetails component", () => { } } ); + + test("should render the job correctly", async () => { + const component = render( + + ); + + await waitFor(() => expect(mockDriver.jobsQuery).toHaveBeenCalledWith(sampleWorkflowDetails.id)); + + expect(component.queryByText("Jobs")).toBeInTheDocument(); + expect(component.queryByText("Scheduled")).toBeInTheDocument(); + expect(component.queryByText(sampleJob.id.slice(0, 7))).toBeInTheDocument(); + }); + + test("should update sampleJob status to EXECUTED after 30 seconds", async () => { + const component = render( + + ); + + await waitFor(() => expect(mockDriver.jobsQuery).toHaveBeenCalledWith(sampleWorkflowDetails.id)); + + expect(component.queryByText("Jobs")).toBeInTheDocument(); + expect(component.queryByText("Scheduled")).toBeInTheDocument(); + expect(component.queryByText(sampleJob.id.slice(0, 7))).toBeInTheDocument(); + + jest.advanceTimersByTime(10000); + + await waitFor(() => { + expect(component.queryByText("Scheduled")).toBeInTheDocument(); + }); + + mockDriver.jobsQuery.mockReturnValue([ + { ...sampleJob, expirationTime: new Date("2023-10-30T15:31:46.709Z").toISOString(), status: JobStatus.Executed }, + ]); + jest.advanceTimersByTime(20000); + + await waitFor(() => { + expect(component.queryByText("Executed")).toBeInTheDocument(); + }); + }); }); diff --git a/packages/sonataflow-dev-app/README.md b/packages/sonataflow-dev-app/README.md index 6c734a6da97..1a60602207f 100644 --- a/packages/sonataflow-dev-app/README.md +++ b/packages/sonataflow-dev-app/README.md @@ -29,6 +29,26 @@ To run the development app, use the following command: `pnpm start` +### GraphQL Modifications + +This section covers modifications to the GraphQL database. + +## Changing Job Status to Executed + +To update a job's status to `"EXECUTED"`, use the following `curl` command. Replace `{JOB_ID}` with the actual ID of the job you want to update. + +```bash +curl -X POST http://localhost:4000/graphql \ + -H "Content-Type: application/json" \ + -d '{ + "query": "mutation JobExecute($id: String!) { JobExecute(id: $id) }", + "variables": { + "id": "{JOB_ID}" + } + }' + +``` + --- Apache KIE (incubating) is an effort undergoing incubation at The Apache Software diff --git a/packages/sonataflow-dev-app/src/MockData/graphql.js b/packages/sonataflow-dev-app/src/MockData/graphql.js index 33f4f6fd014..42d2b323399 100644 --- a/packages/sonataflow-dev-app/src/MockData/graphql.js +++ b/packages/sonataflow-dev-app/src/MockData/graphql.js @@ -18,6 +18,171 @@ */ module.exports = { ProcessInstanceData: [ + { + id: "9750c042-3fb2-40b7-96ba-ff10b6178c58", + processId: "callback_state_timeouts", + processName: "Callback State Timeouts Example", + businessKey: null, + parentProcessInstanceId: null, + parentProcessInstance: null, + roles: [], + variables: null, + state: "ACTIVE", + start: "2024-10-30T15:31:46.571Z", + lastUpdate: "2024-10-30T15:31:46.571Z", + end: null, + addons: [ + "kubernetes", + "microprofile-config-service-catalog", + "process-management", + "source-files", + "cloudevents", + "knative-eventing", + "knative-serving", + "jobs-knative-eventing", + "jobs-management", + ], + endpoint: "http://localhost:4000/callback_state_timeouts", + serviceUrl: "http://localhost:4000", + source: + '{\n "id": "callback_state_timeouts",\n "version": "1.0",\n "name": "Callback State Timeouts Example",\n "description": "Simple process to show the callback state timeout working",\n "start": "PrintStartMessage",\n "events": [\n {\n "name": "callbackEvent",\n "source": "",\n "type": "callback_event_type"\n }\n ],\n "functions": [\n {\n "name": "systemOut",\n "type": "custom",\n "operation": "sysout"\n }\n ],\n "states": [\n {\n "name": "PrintStartMessage",\n "type": "operation",\n "actions": [\n {\n "name": "printSystemOut",\n "functionRef": {\n "refName": "systemOut",\n "arguments": {\n "message": "${\\"callback-state-timeouts: \\" + $WORKFLOW.instanceId + \\" has started.\\"}"\n }\n }\n }\n ],\n "transition": "CallbackState"\n },\n {\n "name": "CallbackState",\n "type": "callback",\n "action": {\n "name": "callbackAction",\n "functionRef": {\n "refName": "systemOut",\n "arguments": {\n "message": "${\\"callback-state-timeouts: \\" + $WORKFLOW.instanceId + \\" has executed the callbackFunction.\\"}"\n }\n }\n },\n "eventRef": "callbackEvent",\n "transition": "CheckEventArrival",\n "timeouts": {\n "eventTimeout": "PT30S"\n }\n },\n {\n "name": "CheckEventArrival",\n "type": "switch",\n "dataConditions": [\n {\n "condition": "${ .eventData != null }",\n "transition": "EventArrived"\n }\n ],\n "defaultCondition": {\n "transition": "EventNotArrived"\n }\n },\n {\n "name": "EventArrived",\n "type": "inject",\n "data": {\n "exitMessage": "The callback event has arrived."\n },\n "transition": "PrintExitMessage"\n },\n {\n "name": "EventNotArrived",\n "type": "inject",\n "data": {\n "exitMessage": "The callback event has not arrived, and the timeout has overdue."\n },\n "transition": "PrintExitMessage"\n },\n {\n "name": "PrintExitMessage",\n "type": "operation",\n "actions": [\n {\n "name": "printSystemOut",\n "functionRef": {\n "refName": "systemOut",\n "arguments": {\n "message": "${\\"callback-state-timeouts: \\" + $WORKFLOW.instanceId + \\" has finalized. \\" + .exitMessage + \\" eventData: \\" + .eventData}"\n }\n }\n }\n ],\n "end": true\n }\n ]\n}', + error: null, + childProcessInstances: [], + nodes: [ + { + id: "fe78615e-9aa0-4d08-9a3f-b67dbfcf5d9d", + nodeId: "9", + name: "CallbackState", + enter: "2024-10-30T15:31:46.566Z", + exit: null, + type: "CompositeContextNode", + definitionId: "9", + __typename: "NodeInstance", + }, + { + id: "7aaa5b45-ec4d-4267-8b8a-503cfc3e286b", + nodeId: "19", + name: "TimerNode_19", + enter: "2024-10-30T15:31:46.568Z", + exit: null, + type: "TimerNode", + definitionId: "19", + __typename: "NodeInstance", + }, + { + id: "e73715b4-d9de-49e8-b367-835e6fff3f53", + nodeId: "15", + name: "callbackEvent", + enter: "2024-10-30T15:31:46.569Z", + exit: null, + type: "EventNode", + definitionId: "15", + __typename: "NodeInstance", + }, + { + id: "ee6c3f6e-8bc3-43dc-a249-dad1b19b52bb", + nodeId: "17", + name: "EventSplit_17", + enter: "2024-10-30T15:31:46.567Z", + exit: "2024-10-30T15:31:46.57Z", + type: "Split", + definitionId: "17", + __typename: "NodeInstance", + }, + { + id: "e7f5fc8b-fb50-4983-b5dd-a0795bab324d", + nodeId: "13", + name: "Script", + enter: "2024-10-30T15:31:46.567Z", + exit: "2024-10-30T15:31:46.57Z", + type: "ActionNode", + definitionId: "13", + __typename: "NodeInstance", + }, + { + id: "7396e050-33c9-4096-bebd-7b579463c79a", + nodeId: "12", + name: "systemOut", + enter: "2024-10-30T15:31:46.566Z", + exit: "2024-10-30T15:31:46.57Z", + type: "ActionNode", + definitionId: "12", + __typename: "NodeInstance", + }, + { + id: "62dbdf7f-3922-411a-9cbe-201e1b2d0070", + nodeId: "10", + name: "EmbeddedStart", + enter: "2024-10-30T15:31:46.566Z", + exit: "2024-10-30T15:31:46.57Z", + type: "StartNode", + definitionId: "10", + __typename: "NodeInstance", + }, + { + id: "059f883b-cf4e-4cc2-8fc4-ea9a87ce55cf", + nodeId: "3", + name: "PrintStartMessage", + enter: "2024-10-30T15:31:46.559Z", + exit: "2024-10-30T15:31:46.57Z", + type: "CompositeContextNode", + definitionId: "3", + __typename: "NodeInstance", + }, + { + id: "9555d6b3-c00e-452a-964f-0a09799a7c74", + nodeId: "8", + name: "EmbeddedEnd", + enter: "2024-10-30T15:31:46.565Z", + exit: "2024-10-30T15:31:46.57Z", + type: "EndNode", + definitionId: "8", + __typename: "NodeInstance", + }, + { + id: "d337fe91-eeaa-4366-a87c-ceb4a1f80aa0", + nodeId: "7", + name: "Script", + enter: "2024-10-30T15:31:46.565Z", + exit: "2024-10-30T15:31:46.57Z", + type: "ActionNode", + definitionId: "7", + __typename: "NodeInstance", + }, + { + id: "5ba61e92-0683-4ecb-b686-58d45a169c3e", + nodeId: "6", + name: "systemOut", + enter: "2024-10-30T15:31:46.559Z", + exit: "2024-10-30T15:31:46.57Z", + type: "ActionNode", + definitionId: "6", + __typename: "NodeInstance", + }, + { + id: "42c5c1cd-960e-4380-af71-dced7c01cdbb", + nodeId: "4", + name: "EmbeddedStart", + enter: "2024-10-30T15:31:46.559Z", + exit: "2024-10-30T15:31:46.571Z", + type: "StartNode", + definitionId: "4", + __typename: "NodeInstance", + }, + { + id: "40a009ca-35f3-483a-b87f-e479a51eba39", + nodeId: "1", + name: "Start", + enter: "2024-10-30T15:31:46.558Z", + exit: "2024-10-30T15:31:46.571Z", + type: "StartNode", + definitionId: "1", + __typename: "NodeInstance", + }, + ], + milestones: [], + __typename: "ProcessInstance", + }, { id: "e995b0d2-078a-488f-8346-0176ed8d5033", processId: "service", @@ -494,5 +659,46 @@ module.exports = { __typename: "ProcessDefinition", }, ], - JobsData: [], + JobsData: [ + { + id: "a62d9d0a-87ea-4c13-87fb-67965d133020", + processId: "callback_state_timeouts", + processInstanceId: "9750c042-3fb2-40b7-96ba-ff10b6178c58", + rootProcessId: null, + status: "SCHEDULED", + expirationTime: () => new Date(Date.now() + 1 * 10 * 1000).toISOString(), + priority: 0, + callbackEndpoint: + "http://localhost:4000/management/jobs/callback_state_timeouts/instances/9750c042-3fb2-40b7-96ba-ff10b6178c58/timers/-1", + repeatInterval: 0, + repeatLimit: 0, + scheduledId: "143", + retries: 0, + lastUpdate: "2024-10-30T15:31:46.709Z", + endpoint: "http://localhost:4000/jobs", + nodeInstanceId: "ee6c3f6e-8bc3-43dc-a249-dad1b19b52bb", + executionCounter: 0, + __typename: "Job", + }, + { + id: "e47fa096-8bc8-42c0-a66d-ad9b3b4b0d7f", + processId: "callback_state_timeouts", + processInstanceId: "9750c042-3fb2-40b7-96ba-ff10b6178c58", + rootProcessId: null, + status: "EXECUTED", + expirationTime: null, + priority: 0, + callbackEndpoint: + "http://localhost:4000/management/jobs/callback_state_timeouts/instances/d818e6dc-e949-4b11-b87b-678b614c0739/timers/-1", + repeatInterval: 0, + repeatLimit: 0, + scheduledId: "283", + retries: 0, + lastUpdate: "2024-10-30T16:27:22.201Z", + endpoint: "http://localhost:4000/jobs", + nodeInstanceId: "fb066c9c-4b25-42b6-a202-cbcdcac68d1b", + executionCounter: 1, + __typename: "Job", + }, + ], }; diff --git a/packages/sonataflow-dev-app/src/MockData/types.js b/packages/sonataflow-dev-app/src/MockData/types.js index cdf6c23ef90..eaa22f959dd 100644 --- a/packages/sonataflow-dev-app/src/MockData/types.js +++ b/packages/sonataflow-dev-app/src/MockData/types.js @@ -26,6 +26,10 @@ module.exports = typeDefs = gql` query: Query } + type Mutation { + JobExecute(id: String): String + } + type Query { ProcessInstances( where: ProcessInstanceArgument diff --git a/packages/sonataflow-dev-app/src/server.js b/packages/sonataflow-dev-app/src/server.js index 4be09f446dd..f885872b496 100644 --- a/packages/sonataflow-dev-app/src/server.js +++ b/packages/sonataflow-dev-app/src/server.js @@ -112,6 +112,16 @@ function paginatedResult(arr, offset, limit) { } // Provide resolver functions for your schema fields const resolvers = { + Mutation: { + JobExecute: async (_parent, args) => { + const job = data.JobsData.find((data) => { + return data.id === args["id"]; + }); + if (!job) return; + job.expirationTime = null; + job.status = "EXECUTED"; + }, + }, Query: { ProcessInstances: async (parent, args) => { let result = data.ProcessInstanceData.filter((datum) => { @@ -169,7 +179,16 @@ const resolvers = { await timeout(); return data.ProcessDefinitionData; }, - Jobs: () => [], + Jobs: async (parent, args) => + data.JobsData.filter((job) => { + if (!args["where"]) { + return true; + } else if (args["where"].processInstanceId && args["where"].processInstanceId.equal) { + return job.processInstanceId == args["where"].processInstanceId.equal; + } else { + return false; + } + }), }, DateTime: new GraphQLScalarType({ diff --git a/KOGITO_UPGRADE_PROCESS.md b/repo/KOGITO_UPGRADE_PROCESS.md similarity index 100% rename from KOGITO_UPGRADE_PROCESS.md rename to repo/KOGITO_UPGRADE_PROCESS.md diff --git a/repo/MANUAL.md b/repo/MANUAL.md index f283ede6f04..d36f764cb9a 100644 --- a/repo/MANUAL.md +++ b/repo/MANUAL.md @@ -30,7 +30,7 @@ structure, concepts like "packages" and specifics on important tools like Maven, | `repo/`
`scripts/` | Related to the monorepo itself. `repo` contains files describing the monorepo structure (I.e., packages DAG, for enabling partial clones with sparse checkout). `scripts` contains code to make the monorepo run smoothly. (E.g., `update-version-to` or `bootstrap`). | | `docs/`
`gifs/` | Files referenced externally in READMEs, for instance. Could be unified into a single directory only, probably with a better name. | | `package.json`
`pnpm-workspace.yaml` | Together they define the monorepo structure and make some commands available at the root dir. The root `package.json` acts like the glue holding everything together. It declares dependencies that are necessary for the monorepo to operate. Packages inside `scripts` are part of these dependencies. | -| `.envrc`
`devbox.lock`
`devbox.json` | Direnv and Devbox configuration files. See these instructions to set it up. See [NIX_DEV_ENV.md](./../../NIX_DEV_ENV.md) | +| `.envrc`
`devbox.lock`
`devbox.json` | Direnv and Devbox configuration files. See these instructions to set it up. See [NIX_DEV_ENV.md](./NIX_DEV_ENV.md) | | `.ci/` | Jeknins configuration for Apache release jobs | | `.github/` | GitHub configuration | | `.asf.yaml` | Apache Software Foundation (ASF) YAML for configuring GitHub features, as no one has access to the Settings tab of repos under the Apache organization. | @@ -257,7 +257,7 @@ This section contains relevant topics about developing packages hosted on KIE To #### Setting up your environment - Nix.dev, Devbox, and `direnv` _**(recommended!)**_ - - See [NIX_DEV_ENV.md](./../../NIX_DEV_ENV.md) + - See [NIX_DEV_ENV.md](./NIX_DEV_ENV.md) - Traditional - See [the top-level README.md](../../README.md#step-0-install-the-necessary-tools) diff --git a/NIX_DEV_ENV.md b/repo/NIX_DEV_ENV.md similarity index 100% rename from NIX_DEV_ENV.md rename to repo/NIX_DEV_ENV.md