diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..1727ac48 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,713 @@ +#!/usr/bin/env bash + +set -euo pipefail +[[ -n "${DEBUGME:-}" ]] && set -x + +script_name="${BASH_SOURCE:-$0}" + +# Default container images +DEFAULT_BUILDER_IMAGE="registry.redhat.io/openshift-serverless-1/logic-swf-builder-rhel9:1.37.0-19" +DEFAULT_RUNTIME_IMAGE="registry.access.redhat.com/ubi9/openjdk-17:1.21-2" + +# Logger functions +RED='\033[0;31m' +YELLOW='\033[0;33m' +GREEN='\033[0;32m' +DEFAULT='\033[0m' + +function log_warning() { + local message="$1" + + echo >&2 -e "${YELLOW}WARN: ${message}${DEFAULT}" +} + +function log_error() { + local message="$1" + + echo >&2 -e "${RED}ERROR: ${message}${DEFAULT}" +} + +function log_info() { + local message="$1" + + echo >&2 -e "INFO: ${message}" +} + +function log_success() { + local message="$1" + + echo >&2 -e "${GREEN}SUCCESS: ${message}${DEFAULT}" +} + +# Dependency validation +function check_dependencies() { + local missing_deps=() + local required_tools=("kn-workflow" "yq" "git") + + # Detect and validate container engine + if [[ -n "${args["container-engine"]:-}" ]]; then + # Use user-specified container engine + if ! command -v "${args["container-engine"]}" >/dev/null 2>&1; then + missing_deps+=("${args["container-engine"]}") + else + DETECTED_CONTAINER_ENGINE="${args["container-engine"]}" + log_info "Using specified container engine: ${args["container-engine"]}" + fi + else + # Auto-detect container engine + if command -v docker >/dev/null 2>&1; then + DETECTED_CONTAINER_ENGINE="docker" + log_info "Auto-detected container engine: docker" + elif command -v podman >/dev/null 2>&1; then + DETECTED_CONTAINER_ENGINE="podman" + log_info "Auto-detected container engine: podman" + else + missing_deps+=("docker or podman") + fi + fi + + # Check for kubectl only if deploy flag is set + if [[ -n "${args["deploy"]:-}" ]] && ! command -v kubectl >/dev/null 2>&1; then + missing_deps+=("kubectl") + fi + + # Check required tools + for tool in "${required_tools[@]}"; do + if ! command -v "$tool" >/dev/null 2>&1; then + missing_deps+=("$tool") + fi + done + + if [[ ${#missing_deps[@]} -gt 0 ]]; then + log_error "Missing required dependencies: ${missing_deps[*]}" + log_error "Please install the missing tools and try again." + return 13 + fi + + log_info "All dependencies are available" +} + +# Helper functions +function is_macos { + [[ "$(uname)" == Darwin ]] +} + +# A wrapper for the find command that uses the -E flag on macOS. +# Extended regex (ERE) is not supported by default on macOS. +function findw { + if is_macos; then + find -E "$@" + else + find "$@" + fi +} + +function validate_image_name() { + local image="$1" + + # Validate image name format - handles registry with port, namespaces, and tags + # Examples: + # - registry.com/image:tag + # - registry.com:443/namespace/image:tag + # - localhost:5000/project/image:latest + if [[ ! "$image" =~ ^[a-zA-Z0-9._-]+(\.[a-zA-Z0-9._-]+)*(:[0-9]+)?(/[a-zA-Z0-9._-]+)+:[a-zA-Z0-9._-]+$ ]]; then + log_error "Invalid image name format: $image" + log_error "Expected format: [registry[:port]/]namespace/image:tag" + return 14 + fi +} + +function validate_directory() { + local dir="$1" + local description="$2" + + if [[ ! -d "$dir" ]]; then + log_error "$description directory does not exist: $dir" + return 15 + fi +} + +function get_workflow_id { + local workdir="$1" + local workflow_file="" + local workflow_id="" + + workflow_file=$(findw "$workdir" -type f -regex '.*\.sw\.ya?ml$') + if [ -z "$workflow_file" ]; then + log_error "No workflow file found with *.sw.yaml or *.sw.yml suffix in: $workdir" + return 10 + fi + + workflow_id=$(yq '.id | downcase' "$workflow_file" 2>/dev/null) + if [ -z "$workflow_id" ]; then + log_error "The workflow file doesn't seem to have an 'id' property: $workflow_file" + return 11 + fi + + echo "$workflow_id" +} + +function container_engine { + if [[ -z "$DETECTED_CONTAINER_ENGINE" ]]; then + log_error "Container engine not detected. Please run dependency check first." + return 16 + fi + + "$DETECTED_CONTAINER_ENGINE" "$@" +} + +function assert_optarg_not_empty { + local arg="$1" + + if [[ -z "${arg#*=}" ]]; then + log_error "Option --${arg%=*} requires an argument when specified." + return 12 + fi +} + +function usage { + cat <= v1.36.0) +2. Builds the workflow image using podman or docker +3. Optionally, deploys the application: + - Pushes the workflow image to the container registry specified by the image path + - Applies the generated manifests using kubectl in the current k8s namespace + +Usage: + $script_name [flags] + +Flags: + -i|--image= (required) The full container image path to use for the workflow, e.g: quay.io/orchestrator/demo:latest. + -b|--builder-image= Overrides the image to use for building the workflow image. Default: $DEFAULT_BUILDER_IMAGE. + -r|--runtime-image= Overrides the image to use for running the workflow. Default: $DEFAULT_RUNTIME_IMAGE. + -d|--dockerfile= Path to a custom Dockerfile to use for building the workflow image (absolute or relative to current directory). Default: uses embedded dockerfile. + -n|--namespace= The target namespace where the manifests will be applied. Default: current namespace. + -m|--manifests-directory= The operator manifests will be generated inside the specified directory. Default: 'manifests' directory in the current directory. + -w|--workflow-directory= Path to the directory containing the workflow's files. For Quarkus projects, this should be the directory containing 'src'. For non-Quarkus layout, this should be the directory containing the workflow files directly. Default: current directory. + -c|--container-engine= Container engine to use (docker or podman). Default: auto-detect. + -P|--no-persistence Skips adding persistence configuration to the sonataflow CR. + -S|--non-quarkus Use non-Quarkus layout where workflow resources are in the project root instead of src/main/resources. + --push Pushes the workflow image to the container registry. + --deploy Deploys the application. + -h|--help Prints this help message. + +Notes: + - This script respects the 'QUARKUS_EXTENSIONS' and 'MAVEN_ARGS_APPEND' environment variables. + - Use --non-quarkus for non-Quarkus projects where workflow files (.sw.yaml, schemas/, etc.) are in the project root directory. + - Without --non-quarkus, the script expects Quarkus project structure with resources in src/main/resources/. + - Use --container-engine to specify docker or podman. If not specified, the script will auto-detect which one is available. +EOF +} + +declare -A args +args["image"]="" +args["deploy"]="" +args["push"]="" +args["namespace"]="" +args["builder-image"]="$DEFAULT_BUILDER_IMAGE" +args["runtime-image"]="$DEFAULT_RUNTIME_IMAGE" +args["dockerfile"]="" +args["container-engine"]="" +args["no-persistence"]="" +args["non-quarkus"]="" +args["workflow-directory"]="$PWD" +args["manifests-directory"]="$PWD/manifests" + +# Global variable to store the detected container engine +DETECTED_CONTAINER_ENGINE="" + +function create_default_dockerignore() { + local dockerignore_path="$1" + cat > "$dockerignore_path" << 'EOF' +* +!LICENSE +!src/main/java/**/* +!src/main/resources/**/* +EOF +} +# Function to create default dockerfile content +function create_default_dockerfile() { + local dockerfile_path="$1" + cat > "$dockerfile_path" << 'EOF' + + +FROM registry.redhat.io/openshift-serverless-1/logic-swf-builder-rhel9:1.37.0-19 AS builder + +# Variables that can be overridden by the builder +# To add a Quarkus extension to your application +ARG QUARKUS_EXTENSIONS +ENV QUARKUS_EXTENSIONS=${QUARKUS_EXTENSIONS} + +# Additional Java/Maven arguments to pass to the builder +ARG MAVEN_ARGS_APPEND +ENV MAVEN_ARGS_APPEND=${MAVEN_ARGS_APPEND} + +COPY --chown=1001 . . + +# Copy from build context to skeleton resources project +COPY --chown=1001 . ./resources/ +RUN ls -la ./resources + +ENV swf_home_dir=/home/kogito/serverless-workflow-project +RUN if [[ -d "./resources/src" ]]; then cp -r ./resources/src/* ./src/; fi + +RUN /home/kogito/launch/build-app.sh ./resources + +#============================= +# Runtime +#============================= +FROM registry.access.redhat.com/ubi9/openjdk-17:1.21-2 + +ENV LANGUAGE='en_US:en' LANG='en_US.UTF-8' + +# We make four distinct layers so if there are application changes, the library layers can be re-used +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/lib/ /deployments/lib/ +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/*.jar /deployments/ +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/app/ /deployments/app/ +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 + +# Disable Jolokia agent +ENV AB_JOLOKIA_OFF="" + +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] +EOF +} + +function parse_args { + while getopts ":i:b:r:d:n:m:w:c:hPS-:" opt; do + case $opt in + h) usage; exit ;; + P) args["no-persistence"]="YES" ;; + S) args["non-quarkus"]="YES" ;; + i) args["image"]="$OPTARG" ;; + n) args["namespace"]="$OPTARG" ;; + m) args["manifests-directory"]="$(realpath "$OPTARG" 2>/dev/null || echo "$PWD/$OPTARG")" ;; + w) args["workflow-directory"]="$(realpath "$OPTARG")" ;; + c) args["container-engine"]="$OPTARG" ;; + b) args["builder-image"]="$OPTARG" ;; + r) args["runtime-image"]="$OPTARG" ;; + d) args["dockerfile"]="$OPTARG" ;; + -) + case "${OPTARG}" in + help) + usage; exit ;; + deploy) + args["deploy"]="YES" + args["push"]="YES" + ;; + push) + args["push"]="YES" ;; + no-persistence) + args["no-persistence"]="YES" ;; + non-quarkus) + args["non-quarkus"]="YES" ;; + image=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["image"]="${OPTARG#*=}" + ;; + namespace=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["namespace"]="${OPTARG#*=}" + ;; + manifests-directory=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["manifests-directory"]="$(realpath "${OPTARG#*=}" 2>/dev/null || echo "$PWD/${OPTARG#*=}")" + ;; + workflow-directory=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["workflow-directory"]="$(realpath "${OPTARG#*=}")" ;; + builder-image=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["builder-image"]="${OPTARG#*=}" + ;; + runtime-image=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["runtime-image"]="${OPTARG#*=}" + ;; + dockerfile=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["dockerfile"]="${OPTARG#*=}" + ;; + container-engine=*) + assert_optarg_not_empty "$OPTARG" || exit $? + args["container-engine"]="${OPTARG#*=}" + ;; + *) log_error "Invalid option: --$OPTARG"; usage; exit 1 ;; + esac + ;; + \?) log_error "Invalid option: -$OPTARG"; usage; exit 2 ;; + :) log_error "Option -$OPTARG requires an argument."; usage; exit 3 ;; + esac + done + + if [[ -z "${args["image"]:-}" ]]; then + log_error "Missing required flag: --image" + usage; exit 4 + fi + + # Validate inputs + validate_image_name "${args["image"]}" + validate_directory "${args["workflow-directory"]}" "Workflow" + + # Validate container engine if specified + if [[ -n "${args["container-engine"]:-}" ]]; then + if [[ "${args["container-engine"]}" != "docker" && "${args["container-engine"]}" != "podman" ]]; then + log_error "Invalid container engine: ${args["container-engine"]}" + log_error "Supported container engines: docker, podman" + exit 27 + fi + fi + + # Create manifests directory if it doesn't exist + mkdir -p "${args["manifests-directory"]}" + + # Resolve dockerfile path before any directory changes + if [[ -n "${args["dockerfile"]:-}" ]]; then + if [[ "${args["dockerfile"]}" = /* ]]; then + # Absolute path - use as is + args["dockerfile"]="${args["dockerfile"]}" + else + # Relative path - resolve from current working directory + args["dockerfile"]="$(realpath "${args["dockerfile"]}" 2>/dev/null || echo "$PWD/${args["dockerfile"]}")" + fi + + # Validate dockerfile exists + if [[ ! -f "${args["dockerfile"]}" ]]; then + log_error "Dockerfile not found: ${args["dockerfile"]}" + exit 28 + fi + + log_info "Dockerfile resolved to: ${args["dockerfile"]}" + fi +} + +function gen_manifests { + log_info "=== Generating Kubernetes manifests ===" + + local res_dir_path + res_dir_path="${args["workflow-directory"]}" + + + # Validate resource directory exists + validate_directory "$res_dir_path" "Workflow resources" + + local workflow_id + workflow_id="$(get_workflow_id "$res_dir_path")" + log_info "Found workflow ID: $workflow_id" + + cd "$res_dir_path" + log_info "Switched directory: $res_dir_path" + + local gen_manifest_args=( + -c="${args["manifests-directory"]}" + --profile='gitops' + --image="${args["image"]}" + ) + if [[ -z "${args["namespace"]:-}" ]]; then + gen_manifest_args+=(--skip-namespace) + log_info "Generating manifests without namespace" + else + gen_manifest_args+=(--namespace="${args["namespace"]}") + log_info "Generating manifests for namespace: ${args["namespace"]}" + fi + + log_info "Running: kn-workflow gen-manifest ${gen_manifest_args[*]}" + if ! kn-workflow gen-manifest "${gen_manifest_args[@]}"; then + log_error "Failed to generate manifests" + return 17 + fi + + cd "${args["workflow-directory"]}" + log_info "Switched directory: ${args["workflow-directory"]}" + + # Find the sonataflow CR for the workflow + local sonataflow_cr + sonataflow_cr="$(findw "${args["manifests-directory"]}" -type f -name "*-sonataflow_${workflow_id}.yaml")" + + if [[ -z "$sonataflow_cr" ]]; then + log_error "Could not find sonataflow CR for workflow: $workflow_id" + return 18 + fi + + log_info "Found sonataflow CR: $sonataflow_cr" + + if [[ -z "${args["no-persistence"]:-}" ]]; then + log_info "Adding persistence configuration to sonataflow CR" + if ! yq --inplace ".spec |= ( + . + { + \"persistence\": { + \"postgresql\": { + \"secretRef\": { + \"name\": \"sonataflow-psql-postgresql\", + \"userKey\": \"postgres-username\", + \"passwordKey\": \"postgres-password\" + }, + \"serviceRef\": { + \"name\": \"sonataflow-psql-postgresql\", + \"port\": 5432, + \"databaseName\": \"sonataflow\", + \"databaseSchema\": \"${workflow_id}\" + } + } + } + } + )" "${sonataflow_cr}"; then + log_error "Failed to add persistence configuration" + return 19 + fi + log_success "Added persistence configuration to sonataflow CR" + else + log_info "Skipping persistence configuration" + fi + + log_success "Manifests generated successfully in: ${args["manifests-directory"]}" +} + +function build_image { + log_info "=== Building workflow image ===" + + local image_name="${args["image"]%:*}" + local tag="${args["image"]#*:}" + + log_info "Building image: ${args["image"]}" + log_info "Image name: $image_name" + log_info "Tag: $tag" + log_info "Container engine: $DETECTED_CONTAINER_ENGINE" + log_info "Builder image: ${args["builder-image"]}" + log_info "Runtime image: ${args["runtime-image"]}" + + # Base extensions that are always included + local base_quarkus_extensions="\ + io.quarkiverse.openapi.generator:quarkus-openapi-generator:2.11.0-lts,\ + org.kie:kie-addons-quarkus-monitoring-sonataflow,\ + org.kie:kogito-addons-quarkus-jobs-knative-eventing" + + # Add persistence extensions only when persistence is enabled + if [[ -z "${args["no-persistence"]:-}" ]]; then + base_quarkus_extensions="${base_quarkus_extensions},\ + org.kie:kie-addons-quarkus-persistence-jdbc,\ + io.quarkus:quarkus-jdbc-postgresql:3.20.3.redhat-00003,\ + io.quarkus:quarkus-agroal:3.20.3.redhat-00003" + log_info "Including persistence extensions" + else + log_info "Skipping persistence extensions" + fi + + # The 'maxYamlCodePoints' parameter contols the maximum size for YAML input files. + # Set to 35000000 characters which is ~33MB in UTF-8. + local base_maven_args_append="-DmaxYamlCodePoints=35000000" + + # Add persistence configuration only when persistence is enabled + if [[ -z "${args["no-persistence"]:-}" ]]; then + base_maven_args_append="${base_maven_args_append} \ + -Dkogito.persistence.type=jdbc \ + -Dquarkus.datasource.db-kind=postgresql \ + -Dkogito.persistence.proto.marshaller=false" + fi + + if [[ -n "${QUARKUS_EXTENSIONS:-}" ]]; then + base_quarkus_extensions="${base_quarkus_extensions},${QUARKUS_EXTENSIONS}" + log_info "Added custom extensions: ${QUARKUS_EXTENSIONS}" + fi + + if [[ -n "${MAVEN_ARGS_APPEND:-}" ]]; then + base_maven_args_append="${base_maven_args_append} ${MAVEN_ARGS_APPEND}" + log_info "Added custom Maven args: ${MAVEN_ARGS_APPEND}" + fi + + # Set dockerfile path - use specified dockerfile or create temporary one from embedded content + local dockerfile_path + local dockerignore_path + local temp_dockerfile_created=false + + if [[ -n "${args["dockerfile"]:-}" ]]; then + # Use pre-resolved dockerfile path from parse_args + dockerfile_path="${args["dockerfile"]}" + echo >&2 -e "${GREEN}INFO: Using custom dockerfile: $dockerfile_path${DEFAULT}" + else + # Create temporary dockerfile from embedded content + dockerfile_path="$(mktemp -t dockerfile.XXXXXX)" + + temp_dockerfile_created=true + echo >&2 -e "${GREEN}INFO: Using embedded default dockerfile (temporary file: $dockerfile_path)${DEFAULT}" + + if ! create_default_dockerfile "$dockerfile_path"; then + log_error "Failed to create temporary dockerfile" + return 20 + fi + + if [[ -z "${args["non-quarkus"]:-}" ]]; then + dockerignore_path="${dockerfile_path}.dockerignore" + echo >&2 -e "${GREEN}INFO: Using quarkus layout, creating temporary file $dockerignore_path" + if ! create_default_dockerignore "$dockerignore_path"; then + log_error "Failed to create temporary dockerignore" + return 20 + fi + fi + + + fi + + # Build specifically for linux/amd64 to ensure compatibility with OSL v1.35.0 + local container_args=( + -f="$dockerfile_path" + --tag="${args["image"]}" + --platform='linux/amd64' + --ulimit='nofile=4096:4096' + --build-arg="QUARKUS_EXTENSIONS=${base_quarkus_extensions}" + --build-arg="MAVEN_ARGS_APPEND=${base_maven_args_append}" + ) + [[ -n "${args["builder-image"]:-}" ]] && container_args+=(--build-arg="BUILDER_IMAGE=${args["builder-image"]}") + [[ -n "${args["runtime-image"]:-}" ]] && container_args+=(--build-arg="RUNTIME_IMAGE=${args["runtime-image"]}") + + log_info "Starting container build (this may take several minutes)..." + if ! container_engine build --no-cache "${container_args[@]}" "${args["workflow-directory"]}"; then + log_error "Container build failed" + # Retain temporary dockerfile for debugging if created + if [[ "$temp_dockerfile_created" == true ]]; then + log_info "Temporary dockerfile retained for debugging: $dockerfile_path and, if created, $dockerignore_path" + fi + return 21 + fi + + log_success "Container build completed successfully" + + # Clean up temporary dockerfile only on success + if [[ "$temp_dockerfile_created" == true ]]; then + rm -f "$dockerfile_path" + if [[ -z "${args["non-quarkus"]:-}" ]]; then + rm -f "$dockerignore_path" + fi + log_info "Cleaned up temporary dockerfile" + fi + + # Tag with git commit hash if available + if ! git rev-parse --short=8 HEAD >/dev/null 2>&1; then + log_warning "Failed to get git commit hash, skipping commit tag" + else + local commit_hash + commit_hash=$(git rev-parse --short=8 HEAD) + log_info "Tagging with commit hash: $commit_hash" + container_engine tag "${args["image"]}" "$image_name:$commit_hash" + fi + + # Tag with latest if not already latest + if [[ "$tag" != "latest" ]]; then + log_info "Tagging with 'latest' tag" + container_engine tag "${args["image"]}" "$image_name:latest" + fi + + log_success "Image built successfully with tags:" + container_engine images --filter="reference=$image_name" --format="{{.Repository}}:{{.Tag}}" +} + +function push_image { + log_info "=== Pushing workflow image ===" + + local image_name="${args["image"]%:*}" + local tag="${args["image"]#*:}" + + log_info "Pushing image: ${args["image"]}" + + if ! container_engine push "${args["image"]}"; then + log_error "Failed to push image: ${args["image"]}" + return 22 + fi + log_success "Pushed: ${args["image"]}" + + # Push commit hash tag if available + if ! git rev-parse --short=8 HEAD >/dev/null 2>&1; then + log_warning "Failed to get git commit hash, skipping commit tag push" + else + local commit_hash + commit_hash=$(git rev-parse --short=8 HEAD) + local commit_tag="$image_name:$commit_hash" + log_info "Pushing commit tag: $commit_tag" + if ! container_engine push "$commit_tag"; then + log_warning "Failed to push commit tag: $commit_tag" + else + log_success "Pushed: $commit_tag" + fi + fi + + # Push latest tag if not already latest + if [[ "$tag" != "latest" ]]; then + local latest_tag="$image_name:latest" + log_info "Pushing latest tag: $latest_tag" + if ! container_engine push "$latest_tag"; then + log_warning "Failed to push latest tag: $latest_tag" + else + log_success "Pushed: $latest_tag" + fi + fi + + log_success "Image push completed successfully" +} + +function deploy_manifests { + log_info "=== Deploying manifests ===" + + if [[ ! -d "${args["manifests-directory"]}" ]]; then + log_error "Manifests directory not found: ${args["manifests-directory"]}" + return 23 + fi + + local manifest_files + manifest_files=$(find "${args["manifests-directory"]}" -name "*.yaml" -o -name "*.yml" | wc -l) + + if [[ "$manifest_files" -eq 0 ]]; then + log_error "No manifest files found in: ${args["manifests-directory"]}" + return 24 + fi + + log_info "Found $manifest_files manifest file(s)" + + if [[ -n "${args["namespace"]:-}" ]]; then + log_info "Applying manifests to namespace: ${args["namespace"]}" + if ! kubectl apply -f "${args["manifests-directory"]}" -n "${args["namespace"]}"; then + log_error "Failed to apply manifests to namespace: ${args["namespace"]}" + return 25 + fi + else + log_info "Applying manifests to current namespace" + if ! kubectl apply -f "${args["manifests-directory"]}"; then + log_error "Failed to apply manifests" + return 26 + fi + fi + + log_success "Manifests deployed successfully" +} + +# Main execution +function main { + log_info "=== Starting workflow build process ===" + + parse_args "$@" + + check_dependencies + + gen_manifests + + build_image + + if [[ -n "${args["push"]}" ]]; then + push_image + fi + + if [[ -n "${args["deploy"]}" ]]; then + deploy_manifests + fi + + log_success "=== Workflow build process completed successfully ===" +} + +# Run main function with all arguments +main "$@" diff --git a/workflows/experimentals/user-onboarding/application.properties b/workflows/experimentals/user-onboarding/application.properties index 5e016d2c..a4a5ecf4 100644 --- a/workflows/experimentals/user-onboarding/application.properties +++ b/workflows/experimentals/user-onboarding/application.properties @@ -14,4 +14,5 @@ quarkus.flyway.migrate-at-start=true # Possible values: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL. # and see https://quarkus.io/guides/logging for documentation quarkus.log.category."org.apache.http".level=INFO -quarkus.log.level=INFO \ No newline at end of file +quarkus.log.level=INFO +kie.flyway.enabled = true \ No newline at end of file diff --git a/workflows/experimentals/user-onboarding/manifests/00-secret_user-onboarding.yaml b/workflows/experimentals/user-onboarding/manifests/00-secret_user-onboarding.yaml deleted file mode 100644 index f8355a13..00000000 --- a/workflows/experimentals/user-onboarding/manifests/00-secret_user-onboarding.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -data: - BACKSTAGE_NOTIFICATIONS_URL: aHR0cDovL2JhY2tzdGFnZS1iYWNrc3RhZ2UucmhkaC1vcGVyYXRvcg== - NOTIFICATIONS_BEARER_TOKEN: "" - ONBOARDING_SERVER_URL: aHR0cDovL3VzZXItb25ib2FyZGluZy1zZXJ2ZXIuc29uYXRhZmxvdy1pbmZyYTo4MDgw -kind: Secret -metadata: - creationTimestamp: null - name: user-onboarding-creds diff --git a/workflows/experimentals/user-onboarding/manifests/01-configmap_user-onboarding-props.yaml b/workflows/experimentals/user-onboarding/manifests/01-configmap_user-onboarding-props.yaml index 0d9fa12c..06a7e1e0 100755 --- a/workflows/experimentals/user-onboarding/manifests/01-configmap_user-onboarding-props.yaml +++ b/workflows/experimentals/user-onboarding/manifests/01-configmap_user-onboarding-props.yaml @@ -1,6 +1,6 @@ apiVersion: v1 data: - application.properties: | + application.properties: |- # Backstage Notifications service quarkus.rest-client.notifications.url=${BACKSTAGE_NOTIFICATIONS_URL} quarkus.openapi-generator.notifications.auth.BearerToken.bearer-token=${NOTIFICATIONS_BEARER_TOKEN} @@ -24,6 +24,9 @@ metadata: creationTimestamp: null labels: app: user-onboarding + app.kubernetes.io/component: serverless-workflow + app.kubernetes.io/managed-by: sonataflow-operator + app.kubernetes.io/name: user-onboarding sonataflow.org/workflow-app: user-onboarding + sonataflow.org/workflow-namespace: "" name: user-onboarding-props - namespace: "" diff --git a/workflows/experimentals/user-onboarding/manifests/02-secret_user-onboarding-secrets.yaml b/workflows/experimentals/user-onboarding/manifests/02-secret_user-onboarding-secrets.yaml new file mode 100755 index 00000000..1131adfc --- /dev/null +++ b/workflows/experimentals/user-onboarding/manifests/02-secret_user-onboarding-secrets.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + app: user-onboarding + app.kubernetes.io/component: serverless-workflow + app.kubernetes.io/managed-by: sonataflow-operator + app.kubernetes.io/name: user-onboarding + sonataflow.org/workflow-app: user-onboarding + sonataflow.org/workflow-namespace: "" + name: user-onboarding-secrets +stringData: + BACKSTAGE_NOTIFICATIONS_URL: http://backstage-backstage.rhdh-operator + NOTIFICATIONS_BEARER_TOKEN: "" + ONBOARDING_SERVER_URL: http://user-onboarding-server.sonataflow-infra:8080 diff --git a/workflows/experimentals/user-onboarding/manifests/03-configmap_01-user-onboarding-resources-schemas.yaml b/workflows/experimentals/user-onboarding/manifests/03-configmap_01-user-onboarding-resources-schemas.yaml new file mode 100755 index 00000000..38e23187 --- /dev/null +++ b/workflows/experimentals/user-onboarding/manifests/03-configmap_01-user-onboarding-resources-schemas.yaml @@ -0,0 +1,131 @@ +apiVersion: v1 +data: + user-onboarding-schema.json: |2 + { + "$id": "classpath:/schemas/user-onboarding-schema.json", + "title": "Workflow input data", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "userId": { + "title": "The user ID that triggers the workflow", + "description": "The user that triggers the workflow", + "type": "string", + "default": "user:default/guest" + }, + "iterationNum": { + "title": "The iteration number for running the workflow", + "description": "The iteration number for running the workflow", + "type": "integer" + }, + "username": { + "title": "The name of the user", + "description": "The name of the user", + "type": "string", + "pattern": "^[a-zA-Z]*" + }, + "recipients": { + "title": "Recipients", + "type": "array", + "description": "A list of recipients for the notification in the format of 'user:/' or 'group:/', i.e. 'user:default/jsmith'", + "items": { + "title": "Recipient", + "type": "string", + "pattern": "^(user|group):[a-z0-9]([-a-z0-9]*[a-z0-9])?/([a-z0-9]([-a-z0-9]*[a-z0-9])?)$" + }, + "minItems": 1 + } + }, + "required": [ + "userId", + "iterationNum", + "recipients" + ] + } + workflow-output-schema.json: |- + { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WorkflowResult", + "description": "Schema of workflow output", + "type": "object", + "properties": { + "result": { + "$ref": "./workflow-result-schema.json", + "type": "object" + } + } + } + workflow-result-schema.json: |- + { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WorkflowResult", + "description": "Result of a workflow execution", + "type": "object", + "properties": { + "message": { + "description": "High-level summary of the current status, free-form text, human readable.", + "type": "string" + }, + "nextWorkflows": { + "description": "List of workflows suggested to run next. Items at lower indexes are of higher priority.", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "Workflow identifier", + "type": "string" + }, + "name": { + "description": "Human readable title describing the workflow.", + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + }, + "outputs": { + "description": "Additional structured output of workflow processing. This can contain identifiers of created resources, links to resources, logs or other output.", + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "description": "Unique identifier of the option. Preferably human-readable.", + "type": "string" + }, + "value": { + "description": "Free form value of the option.", + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "format": { + "description": "More detailed type of the 'value' property. Defaults to 'text'.", + "enum": [ + "text", + "number", + "link" + ] + } + }, + "required": [ + "key", + "value" + ] + } + } + } + } +kind: ConfigMap +metadata: + creationTimestamp: null + name: 01-user-onboarding-resources-schemas diff --git a/workflows/experimentals/user-onboarding/manifests/04-configmap_02-user-onboarding-resources-specs.yaml b/workflows/experimentals/user-onboarding/manifests/04-configmap_02-user-onboarding-resources-specs.yaml new file mode 100755 index 00000000..9a7c48ec --- /dev/null +++ b/workflows/experimentals/user-onboarding/manifests/04-configmap_02-user-onboarding-resources-specs.yaml @@ -0,0 +1,68 @@ +apiVersion: v1 +data: + onboarding-openapi.yaml: | + openapi: 3.0.3 + info: + title: User Onboarding API + description: API for simulating a user onboarding process, where a user reaches "Ready" status after three requests. + version: 1.0.0 + paths: + /onboard: + post: + summary: Initiates or checks the onboarding status of a user + description: | + - The first two requests for a given `user_id` return `"In Progress"`. + - The third request returns `"Ready"`, and starts a **5-minute** cleanup timer. + - After 5 minutes, the user is removed from the cache and starts fresh. + operationId: onboardUser + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/OnboardRequest" + responses: + "200": + description: Successfully retrieved onboarding status. + content: + application/json: + schema: + $ref: "#/components/schemas/OnboardResponse" + "400": + description: Invalid request payload. + "405": + description: Invalid request method. + components: + schemas: + OnboardRequest: + type: object + required: + - user_id + - name + properties: + user_id: + type: string + description: Unique identifier for the user. + example: "12345" + name: + type: string + description: Name of the user. + example: "Alice Johnson" + OnboardResponse: + type: object + properties: + user_id: + type: string + description: Unique identifier for the user. + example: "12345" + status: + type: string + description: Current onboarding status. + enum: + - "In Progress" + - "Ready" + example: "In Progress" +kind: ConfigMap +metadata: + creationTimestamp: null + name: 02-user-onboarding-resources-specs diff --git a/workflows/experimentals/user-onboarding/manifests/04-sonataflow_user-onboarding.yaml b/workflows/experimentals/user-onboarding/manifests/05-sonataflow_user-onboarding.yaml similarity index 88% rename from workflows/experimentals/user-onboarding/manifests/04-sonataflow_user-onboarding.yaml rename to workflows/experimentals/user-onboarding/manifests/05-sonataflow_user-onboarding.yaml index 818ed1bf..e67b9b92 100755 --- a/workflows/experimentals/user-onboarding/manifests/04-sonataflow_user-onboarding.yaml +++ b/workflows/experimentals/user-onboarding/manifests/05-sonataflow_user-onboarding.yaml @@ -9,9 +9,12 @@ metadata: creationTimestamp: null labels: app: user-onboarding + app.kubernetes.io/component: serverless-workflow + app.kubernetes.io/managed-by: sonataflow-operator + app.kubernetes.io/name: user-onboarding sonataflow.org/workflow-app: user-onboarding + sonataflow.org/workflow-namespace: "" name: user-onboarding - namespace: "" spec: flow: dataInputSchema: @@ -180,11 +183,24 @@ spec: type: operation podTemplate: container: + env: + - name: ONBOARDING_SERVER_URL + valueFrom: + secretKeyRef: + key: ONBOARDING_SERVER_URL + name: user-onboarding-secrets + - name: BACKSTAGE_NOTIFICATIONS_URL + valueFrom: + secretKeyRef: + key: BACKSTAGE_NOTIFICATIONS_URL + name: user-onboarding-secrets + - name: NOTIFICATIONS_BEARER_TOKEN + valueFrom: + secretKeyRef: + key: NOTIFICATIONS_BEARER_TOKEN + name: user-onboarding-secrets + image: quay.io/orchestrator/demo-user-onboarding:osl_1_37 resources: {} - image: quay.io/orchestrator/demo-user-onboarding:latest - envFrom: - - secretRef: - name: user-onboarding-creds resources: configMaps: - configMap: diff --git a/workflows/fail-switch/src/main/resources/manifests/04-sonataflow_failswitch.yaml b/workflows/fail-switch/src/main/resources/manifests/04-sonataflow_failswitch.yaml index fdf9b202..a70b5a95 100755 --- a/workflows/fail-switch/src/main/resources/manifests/04-sonataflow_failswitch.yaml +++ b/workflows/fail-switch/src/main/resources/manifests/04-sonataflow_failswitch.yaml @@ -101,7 +101,7 @@ spec: type: operation podTemplate: container: - image: quay.io/orchestrator/fail-switch:latest + image: quay.io/orchestrator/fail-switch:osl_1_37 resources: {} resources: configMaps: diff --git a/workflows/greeting/manifests/01-configmap_greeting-props.yaml b/workflows/greeting/manifests/01-configmap_greeting-props.yaml new file mode 100755 index 00000000..06deb7a5 --- /dev/null +++ b/workflows/greeting/manifests/01-configmap_greeting-props.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +data: + application.properties: | + # This property is used to select the log level, which controls the amount + # of information logged on HTTP requests based on the severity of the events. + # Possible values: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL. + # and see https://quarkus.io/guides/logging for documentation + quarkus.log.category."org.apache.http".level=INFO + quarkus.log.level=INFO + + kie.flyway.enabled=true +kind: ConfigMap +metadata: + creationTimestamp: null + labels: + app: greeting + app.kubernetes.io/component: serverless-workflow + app.kubernetes.io/managed-by: sonataflow-operator + app.kubernetes.io/name: greeting + sonataflow.org/workflow-app: greeting + sonataflow.org/workflow-namespace: "" + name: greeting-props diff --git a/workflows/greeting/manifests/02-configmap_01-greeting-resources-schemas.yaml b/workflows/greeting/manifests/02-configmap_01-greeting-resources-schemas.yaml new file mode 100755 index 00000000..f9ba09ed --- /dev/null +++ b/workflows/greeting/manifests/02-configmap_01-greeting-resources-schemas.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +data: + greeting.sw.input-schema.json: | + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "language": { + "title": "Language", + "description": "Language to greet", + "type": "string", + "enum": ["English", "Spanish"], + "default": "English" + } + } + } + workflow-output-schema.json: |- + { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WorkflowResult", + "description": "Schema of workflow output", + "type": "object", + "properties": { + "result": { + "$ref": "../shared/schemas/workflow-result-schema.json", + "type": "object" + } + } + } +kind: ConfigMap +metadata: + creationTimestamp: null + name: 01-greeting-resources-schemas diff --git a/workflows/greeting/manifests/03-sonataflow_greeting.yaml b/workflows/greeting/manifests/03-sonataflow_greeting.yaml new file mode 100755 index 00000000..c5ca11c1 --- /dev/null +++ b/workflows/greeting/manifests/03-sonataflow_greeting.yaml @@ -0,0 +1,101 @@ +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: YAML based greeting workflow + sonataflow.org/expressionLang: jq + sonataflow.org/profile: gitops + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: greeting + app.kubernetes.io/component: serverless-workflow + app.kubernetes.io/managed-by: sonataflow-operator + app.kubernetes.io/name: greeting + sonataflow.org/workflow-app: greeting + sonataflow.org/workflow-namespace: "" + name: greeting +spec: + flow: + annotations: + - workflow-type/infrastructure + dataInputSchema: + failOnValidationErrors: true + schema: schemas/greeting.sw.input-schema.json + functions: + - name: greetFunction + operation: sysout + type: custom + - name: successResult + operation: '{ "result": { "message": "Greeting workflow completed successfully", "outputs":[ { "key":"Selected language", "value": .language }, { "key":"Greeting message", "value": .greeting } ] } }' + type: expression + start: + stateName: ChooseOnLanguage + states: + - dataConditions: + - condition: .language == "English" + transition: + nextState: GreetInEnglish + - condition: .language == "Spanish" + transition: + nextState: GreetInSpanish + defaultCondition: + transition: + nextState: GreetInEnglish + name: ChooseOnLanguage + type: switch + - data: + greeting: Hello from YAML Workflow + name: GreetInEnglish + transition: + nextState: GreetPerson + type: inject + - data: + greeting: Saludos desde YAML Workflow + name: GreetInSpanish + transition: + nextState: GreetPerson + type: inject + - actionMode: sequential + actions: + - actionDataFilter: + useResults: true + functionRef: + arguments: + message: .greeting + invoke: sync + refName: greetFunction + name: greetAction + - actionDataFilter: + useResults: true + functionRef: + invoke: sync + refName: successResult + name: setOutput + end: + terminate: true + name: GreetPerson + type: operation + podTemplate: + container: + image: quay.io/orchestrator/serverless-workflow-greeting:osl_1_37 + resources: {} + resources: + configMaps: + - configMap: + name: 01-greeting-resources-schemas + workflowPath: schemas + persistence: + postgresql: + secretRef: + name: sonataflow-psql-postgresql + userKey: postgres-username + passwordKey: postgres-password + serviceRef: + name: sonataflow-psql-postgresql + port: 5432 + databaseName: sonataflow + databaseSchema: greeting +status: + address: {} + lastTimeRecoverAttempt: null