diff --git a/application/entrypoint b/application/entrypoint new file mode 100755 index 00000000..02b756e8 --- /dev/null +++ b/application/entrypoint @@ -0,0 +1,63 @@ +#!/bin/bash + +CLEAN_CONTEXT=$(echo "$NP_ACTION_CONTEXT" | sed "s/^'//;s/'$//") + +export NP_ACTION_CONTEXT="$CLEAN_CONTEXT" + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") + +cd "$CURRENT_DIR" && cd .. + +TOKEN=$(np token create --body "{\"api_key\": \"$NP_API_KEY\"}" --format json | jq -r .access_token) + +CALLBACK_URL=$(echo "$NP_ACTION_CONTEXT" | jq -r .notification.callback_url) + +logs=$(np service workflow exec --workflow application/workflows/create_app.yaml) +EXIT_CODE=$? +# Determine status based on exit code +if [ $EXIT_CODE -eq 0 ]; then + STATUS="success" +else + STATUS="failed" +fi + +timestamp=$(($(date +%s) * 1000)) + +messages=$(echo "$logs" | while IFS= read -r line; do + [[ -z "$line" ]] && continue + + if [[ "$line" =~ "failed" ]] || [[ "$line" =~ "ERROR" ]] || [[ "$line" =~ "error" ]]; then + level="ERROR" + elif [[ "$line" =~ "warning" ]] || [[ "$line" =~ "WARNING" ]] || [[ "$line" =~ "warn" ]]; then + level="WARNING" + else + level="INFO" + fi + + jq -n \ + --arg level "$level" \ + --arg message "$line" \ + --arg ts "$timestamp" \ + '{level: $level, message: $message, timestamp: ($ts | tonumber)}' +done | jq -s '.') + +payload=$(jq -n \ + --arg status "$STATUS" \ + --argjson messages "$messages" \ + '{status: $status, messages: $messages}') + +echo "$payload" | jq . + +echo "Updating hook with status: $STATUS. URL: $CALLBACK_URL" + +# Call the callback URL with the status +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X PATCH "$CALLBACK_URL" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$payload") + +if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "✓ Successfully updated hook (HTTP $HTTP_CODE)" +else + echo "✗ Failed to update hook (HTTP $HTTP_CODE)" +fi \ No newline at end of file diff --git a/application/scripts/asset-repo/create_asset_repository b/application/scripts/asset-repo/create_asset_repository new file mode 100644 index 00000000..308cc150 --- /dev/null +++ b/application/scripts/asset-repo/create_asset_repository @@ -0,0 +1,21 @@ +#!/bin/bash + +ASSET_REPOSITORY=$(np provider list --categories assets-repository --nrn "$NRN" --format json | jq ".results[0]") + +if [[ -n "$ASSET_REPOSITORY_PROVIDER" ]]; then + echo "Using ASSET_REPOSITORY_PROVIDER from environment: $ASSET_REPOSITORY_PROVIDER" +else + ASSET_REPOSITORY_SPEC=$(echo "$ASSET_REPOSITORY" | jq -r .specification_id) + ASSET_REPOSITORY_PROVIDER=$(np provider specification read --id "$ASSET_REPOSITORY_SPEC" --format json | jq -r .slug) + + echo "No ASSET_REPOSITORY_PROVIDER configured from environment, will use the provider configured in platform settings: $ASSET_REPOSITORY_PROVIDER" +fi + +ASSET_REPOSITORY_SCRIPT_PATH="application/scripts/asset-repo/$ASSET_REPOSITORY_PROVIDER" + +export ASSET_REPOSITORY_TYPE +export ASSET_REPOSITORY + +export ASSET_REPOSITORY_SCRIPT_PATH + +np service workflow exec --workflow application/workflows/create_asset_repository.yaml \ No newline at end of file diff --git a/application/scripts/asset-repo/docker-server/build_context b/application/scripts/asset-repo/docker-server/build_context new file mode 100644 index 00000000..0d4836a5 --- /dev/null +++ b/application/scripts/asset-repo/docker-server/build_context @@ -0,0 +1,27 @@ +#!/bin/bash + +if [[ -n "$DOCKER_SERVER_URL" ]]; then + echo "Using DOCKER_SERVER_URL from environment: $DOCKER_SERVER_URL" +else + DOCKER_SERVER_URL=$(echo "$ASSET_REPOSITORY" | jq -r '.attributes.setup.server') + echo "No DOCKER_SERVER_URL in environment, using value from platform: $DOCKER_SERVER_URL" +fi + +if [[ -n "$DOCKER_SERVER_PATH" ]]; then + echo "Using DOCKER_SERVER_PATH from environment: $DOCKER_SERVER_PATH" +else + DOCKER_SERVER_PATH=$(echo "$ASSET_REPOSITORY" | jq -r '.attributes.setup.path') + echo "No DOCKER_SERVER_PATH in environment, using value from platform: $DOCKER_SERVER_PATH" +fi + +if [[ -n "$DOCKER_SERVER_USE_NAMESPACE" ]]; then + echo "Using DOCKER_SERVER_USE_NAMESPACE from environment: $DOCKER_SERVER_USE_NAMESPACE" +else + DOCKER_SERVER_USE_NAMESPACE=$(echo "$ASSET_REPOSITORY" | jq -r '.attributes.setup.use_namespace') + echo "No DOCKER_SERVER_USE_NAMESPACE in environment, using value from platform: $DOCKER_SERVER_USE_NAMESPACE" +fi + + +export DOCKER_SERVER_URL +export DOCKER_SERVER_PATH +export DOCKER_SERVER_USE_NAMESPACE \ No newline at end of file diff --git a/application/scripts/asset-repo/docker-server/create_repository b/application/scripts/asset-repo/docker-server/create_repository new file mode 100644 index 00000000..e3b7564c --- /dev/null +++ b/application/scripts/asset-repo/docker-server/create_repository @@ -0,0 +1,3 @@ +#!/bin/bash + +np nrn patch --nrn "$NRN" --body "{\"docker.repository_uri\":\"$DOCKER_SERVER_URI\"}" \ No newline at end of file diff --git a/application/scripts/asset-repo/docker-server/generate_repository_uri b/application/scripts/asset-repo/docker-server/generate_repository_uri new file mode 100644 index 00000000..e81d7db6 --- /dev/null +++ b/application/scripts/asset-repo/docker-server/generate_repository_uri @@ -0,0 +1,16 @@ +#!/bin/bash + +DOCKER_SERVER_URI=$DOCKER_SERVER_URL + +if [[ ! -z "$DOCKER_SERVER_PATH" ]]; then + DOCKER_SERVER_URI="$DOCKER_SERVER_URI/$DOCKER_SERVER_PATH" +fi + +SEPARATOR="-" +if [[ "$DOCKER_SERVER_USE_NAMESPACE" == "true" ]]; then + SEPARATOR="/" +fi + +DOCKER_SERVER_URI="$DOCKER_SERVER_URI/$NAMESPACE_SLUG$SEPARATOR$APPLICATION_SLUG" + +export DOCKER_SERVER_URI \ No newline at end of file diff --git a/application/scripts/base_context b/application/scripts/base_context new file mode 100644 index 00000000..d8f60bec --- /dev/null +++ b/application/scripts/base_context @@ -0,0 +1,25 @@ +#!/bin/bash + +NRN=$(echo "$NP_ACTION_CONTEXT" | jq -r .notification.nrn) + +ACCOUNT_ID=$(echo "$NRN" | sed -n 's/.*account=\([0-9]*\).*/\1/p') +NAMESPACE_ID=$(echo "$NRN" | sed -n 's/.*namespace=\([0-9]*\).*/\1/p') +APPLICATION_ID=$(echo "$NRN" | sed -n 's/.*application=\([0-9]*\).*/\1/p') + +NAMESPACE=$(np namespace read --format json --id "$NAMESPACE_ID") + +APPLICATION=$(np application read --format json --id "$APPLICATION_ID") + +APPLICATION_SLUG=$(echo "$APPLICATION" | jq -r .slug) +NAMESPACE_SLUG=$(echo "$NAMESPACE" | jq -r .slug) + +export ACCOUNT_ID + +export NAMESPACE +export NAMESPACE_ID +export NAMESPACE_SLUG + +export APPLICATION +export APPLICATION_ID +export APPLICATION_SLUG +export NRN diff --git a/application/scripts/code-repo/create_code_repository b/application/scripts/code-repo/create_code_repository new file mode 100644 index 00000000..99cdd32c --- /dev/null +++ b/application/scripts/code-repo/create_code_repository @@ -0,0 +1,45 @@ +#!/bin/bash + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") + +REPOSITORY_URL=$(echo "$APPLICATION" | jq -r .repository_url) +TEMPLATE_ID=$(echo "$APPLICATION" | jq -r '.template_id // empty' ) + +if [[ -n "$TEMPLATE_ID" ]]; then + CODE_REPOSITORY_STRATEGY=create + + TEMPLATE_URL=$(np template read --id "$TEMPLATE_ID" --format json | jq -r .url) + + export TEMPLATE_URL +else + CODE_REPOSITORY_STRATEGY=import +fi + +REPOSITORY_NAME=$(basename "$REPOSITORY_URL") + +if [[ -n "$CODE_REPOSITORY_PROVIDER" ]]; then + echo "Using CODE_REPOSITORY_PROVIDER from environment: $CODE_REPOSITORY_PROVIDER" +else + ACCOUNT=$(np account read --id "$ACCOUNT_ID" --format json) + + CODE_REPOSITORY_PROVIDER=$(echo "$ACCOUNT" | jq -r .repository_provider) + + echo "No CODE_REPOSITORY_PROVIDER configured from environment, will use the provider configured in the nullplatform account: $CODE_REPOSITORY_PROVIDER" +fi + +CODE_REPOSITORY=$(np provider list --categories code-repository --nrn "$NRN" --format json | jq ".results[0]") + +CODE_REPOSITORY_SCRIPT_PATH="application/scripts/code-repo/$CODE_REPOSITORY_PROVIDER" + +CODE_REPOSITORY_COLLABORATORS=$(echo "$CODE_REPOSITORY" | jq .attributes.access.default_collaborators) + +export CODE_REPOSITORY_STRATEGY + +export REPOSITORY_URL +export REPOSITORY_NAME + +export CODE_REPOSITORY +export CODE_REPOSITORY_SCRIPT_PATH +export CODE_REPOSITORY_COLLABORATORS + +np service workflow exec --workflow application/workflows/create_code_repository.yaml \ No newline at end of file diff --git a/application/scripts/code-repo/generate_secrets b/application/scripts/code-repo/generate_secrets new file mode 100644 index 00000000..98c855a6 --- /dev/null +++ b/application/scripts/code-repo/generate_secrets @@ -0,0 +1,33 @@ +#!/bin/bash +CI_ROLE_ID=1855672260 +# TODO(federico.maleh) missing support for mono repo +# TODO(federico.maleh) missing support for extra secrets +# TODO(federico.maleh) missing support for extra roles in api key + +API_KEY_NAME="ci-$NAMESPACE_SLUG-$NAMESPACE_ID-$APPLICATION_SLUG-$APPLICATION_ID" + +echo "Creating api key for ci: $API_KEY_NAME" + +API_KEY_BODY=$(jq -nc \ + --arg name "$API_KEY_NAME" \ + --arg nrn "$NRN" \ + --argjson role_id "$CI_ROLE_ID" \ + '{name: $name, grants: [{nrn: $nrn, role_id: $role_id}], tags: [{key: "ci", value: true}], internal: true}') + +API_KEY_RESPONSE=$(np api-key create --format json --body "$API_KEY_BODY") + +API_KEY=$(echo "$API_KEY_RESPONSE" | jq -r .api_key) +API_KEY_ID=$(echo "$API_KEY_RESPONSE" | jq -r .id) + +if [[ -n "$API_KEY_ID" ]]; then + echo "Created ci api key: $API_KEY_ID" +else + echo "Error creating ci api key" + echo "$API_API_KEY_RESPONSE" | jq + + exit 1 +fi + +CODE_REPOSITORY_SECRETS="{\"NP_API_KEY\": \"$API_KEY\"}" + +export CODE_REPOSITORY_SECRETS \ No newline at end of file diff --git a/application/scripts/code-repo/gitlab/add_collaborators b/application/scripts/code-repo/gitlab/add_collaborators new file mode 100644 index 00000000..bfd0f1be --- /dev/null +++ b/application/scripts/code-repo/gitlab/add_collaborators @@ -0,0 +1,74 @@ +#!/bin/bash + +PROJECT_PATH="${GITLAB_ENCODED_GROUP_PATH}%2F${REPOSITORY_NAME}" + +get_access_level() { + local role="$1" # Don't convert to uppercase + case $role in + guest) echo "10" ;; + reporter) echo "20" ;; + developer) echo "30" ;; + maintainer) echo "40" ;; + owner) echo "50" ;; + *) echo "30" ;; # default to DEVELOPER + esac +} + +# Function to get user ID from username +get_user_id() { + local username="$1" + curl --silent --header "PRIVATE-TOKEN: ${GITLAB_ACCESS_TOKEN}" \ + "${GITLAB_INSTALLATION_URL}/api/v4/users?username=${username}" | jq -r '.[0].id' +} + +# Function to get group ID from group path +get_group_id() { + local group_path="$1" + + curl --silent --header "PRIVATE-TOKEN: ${GITLAB_ACCESS_TOKEN}" \ + "${GITLAB_INSTALLATION_URL}/api/v4/groups/${group_path}" | jq -r '.id' +} + +echo "$CODE_REPOSITORY_COLLABORATORS" | jq -c '.[]' | while read -r collaborator; do + id=$(echo "$collaborator" | jq -r '.id') + role=$(echo "$collaborator" | jq -r '.role') + type=$(echo "$collaborator" | jq -r '.type') + access_level=$(get_access_level "$role") + + if [ "$type" = "user" ]; then + # Look up user ID from username + user_id=$(get_user_id "$id") + + if [ "$user_id" = "null" ] || [ -z "$user_id" ]; then + echo "Error: User '$id' not found" + continue + fi + + echo "Adding user '$id' (ID: $user_id) with role '$role' (access_level=$access_level)" + + curl --request POST \ + --header "PRIVATE-TOKEN: ${GITLAB_ACCESS_TOKEN}" \ + --data "user_id=${user_id}&access_level=${access_level}" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${PROJECT_PATH}/members" + echo "" + + elif [ "$type" = "group" ]; then + # Look up group ID from group path + group_id=$(get_group_id "$id") + + if [ "$group_id" = "null" ] || [ -z "$group_id" ]; then + echo "Error: Group '$id' not found" + continue + fi + + echo "Adding group '$id' (ID: $group_id) with role '$role' (access_level=$access_level)" + + curl --request POST \ + --header "PRIVATE-TOKEN: ${GITLAB_ACCESS_TOKEN}" \ + --data "group_id=${group_id}&group_access=${access_level}" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${PROJECT_PATH}/share" + echo "" + fi +done + +echo "Finished adding collaborators" \ No newline at end of file diff --git a/application/scripts/code-repo/gitlab/build_context b/application/scripts/code-repo/gitlab/build_context new file mode 100755 index 00000000..e95b153b --- /dev/null +++ b/application/scripts/code-repo/gitlab/build_context @@ -0,0 +1,44 @@ +#!/bin/bash + +if [[ -n "$GITLAB_ACCESS_TOKEN" ]]; then + echo "Using GITLAB_ACCESS_TOKEN from environment (hidden)" +else + GITLAB_ACCESS_TOKEN=$(echo "$CODE_REPOSITORY" | jq -r '.attributes.setup.access_token') + echo "No GITLAB_ACCESS_TOKEN in environment, using value from platform (hidden)" +fi + +if [[ -n "$GITLAB_GROUP_PATH" ]]; then + echo "Using GITLAB_GROUP_PATH from environment: $GITLAB_GROUP_PATH" +else + GITLAB_GROUP_PATH=$(echo "$CODE_REPOSITORY" | jq -r '.attributes.setup.group_path') + echo "No GITLAB_GROUP_PATH in environment, using value from platform: $GITLAB_GROUP_PATH" +fi + +if [[ -n "$GITLAB_INSTALLATION_URL" ]]; then + echo "Using GITLAB_INSTALLATION_URL from environment: $GITLAB_INSTALLATION_URL" +else + GITLAB_INSTALLATION_URL=$(echo "$CODE_REPOSITORY" | jq -r '.attributes.setup.installation_url') + echo "No GITLAB_INSTALLATION_URL in environment, using value from platform: $GITLAB_INSTALLATION_URL" +fi + + +echo "Getting group ID for path: $GITLAB_GROUP_PATH" +GROUP_RESPONSE=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + "${GITLAB_INSTALLATION_URL}/api/v4/groups?search=${GITLAB_GROUP_PATH}") + +GITLAB_GROUP_ID=$(echo "$GROUP_RESPONSE" | jq -r ".[] | select(.full_path == \"${GITLAB_GROUP_PATH}\") | .id") + +if [ -z "$GITLAB_GROUP_ID" ] || [ "$GITLAB_GROUP_ID" == "null" ]; then + echo "Error: Group not found for path: $GITLAB_GROUP_PATH" + exit 1 +fi + +echo "Found gitlab Group ID: $GITLAB_GROUP_ID" + +GITLAB_ENCODED_GROUP_PATH=$(echo "$GITLAB_GROUP_PATH" | sed 's/\//%2F/g') + +export GITLAB_ACCESS_TOKEN +export GITLAB_GROUP_PATH +export GITLAB_INSTALLATION_URL +export GITLAB_GROUP_ID +export GITLAB_ENCODED_GROUP_PATH \ No newline at end of file diff --git a/application/scripts/code-repo/gitlab/create_repository b/application/scripts/code-repo/gitlab/create_repository new file mode 100644 index 00000000..e6d8c8e9 --- /dev/null +++ b/application/scripts/code-repo/gitlab/create_repository @@ -0,0 +1,41 @@ +#!/bin/bash + +if [[ $CODE_REPOSITORY_STRATEGY == "import" ]]; then + echo "Strategy is set to 'import'. Skipping repo creation." + return +fi + +IMPORT_URL=$TEMPLATE_URL + +if [[ "$TEMPLATE_URL" == "$GITLAB_INSTALLATION_URL"* ]]; then + PARSED_HOST=$(echo "$TEMPLATE_URL" | sed -E 's|https?://||' | cut -d'/' -f1) + PARSED_PATH=$(echo "$TEMPLATE_URL" | sed -E 's|https?://[^/]+||') + + IMPORT_URL="https://oauth2:${GITLAB_ACCESS_TOKEN}@${PARSED_HOST}${PARSED_PATH}.git" +fi + +CREATE_REPO_BODY="{ + \"name\": \"$REPOSITORY_NAME\", + \"namespace_id\": $GITLAB_GROUP_ID, + \"visibility\": \"private\", + \"import_url\": \"$IMPORT_URL\"}" + +echo "Creating repository with body: " +echo "$CREATE_REPO_BODY" | jq . + +CREATE_RESPONSE=$(curl --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + --header "Content-Type: application/json" \ + --data "$CREATE_REPO_BODY" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects") + +GITLAB_PROJECT_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id') + +if [ -z "$GITLAB_PROJECT_ID" ] || [ "$GITLAB_PROJECT_ID" == "null" ]; then + echo "Error creating repository:" + echo "$CREATE_RESPONSE" | jq + exit 1 +fi + +echo "Repository created with ID: $GITLAB_PROJECT_ID" + +export GITLAB_PROJECT_ID \ No newline at end of file diff --git a/application/scripts/code-repo/gitlab/create_secrets b/application/scripts/code-repo/gitlab/create_secrets new file mode 100644 index 00000000..fb72b31e --- /dev/null +++ b/application/scripts/code-repo/gitlab/create_secrets @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "$CODE_REPOSITORY_SECRETS" \ +| jq -r 'to_entries[] | [.key, (.value|tostring)] | @tsv' \ +| while IFS=$'\t' read -r key value; do + echo "Creating secret: $key" + + curl -s -o /dev/null -w "" --request DELETE \ + --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/variables/$key" \ + >/dev/null 2>&1 || true + + SECRET_RESPONSE=$(curl -s --request POST \ + --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + --header "Content-Type: application/json" \ + --data "$(jq -n \ + --arg key "$key" \ + --arg value "$value" \ + '{ key: $key, value: $value, protected: false, masked: true }')" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/variables" + ) + + if echo "$SECRET_RESPONSE" | jq -e '.key' > /dev/null 2>&1; then + echo "Secret $key created successfully" + else + echo "Error creating $key secret:" + echo "$SECRET_RESPONSE" | jq + fi +done \ No newline at end of file diff --git a/application/scripts/code-repo/gitlab/run_first_build b/application/scripts/code-repo/gitlab/run_first_build new file mode 100644 index 00000000..e4e15c5b --- /dev/null +++ b/application/scripts/code-repo/gitlab/run_first_build @@ -0,0 +1,28 @@ +#!/bin/bash + +PIPELINE_RESPONSE=$(curl -s --request POST --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/pipeline?ref=main" 2>&1 || echo "") + +if echo "$PIPELINE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then + echo "Pipeline triggered successfully" +else + echo "Could not trigger pipeline directly, trying to create a commit..." + + # Create an empty commit to trigger CI + COMMIT_RESPONSE=$(curl -s --request POST --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + --header "Content-Type: application/json" \ + --data "{ + \"branch\": \"main\", + \"commit_message\": \"Trigger CI\", + \"actions\": [] + }" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/repository/commits") + + if echo "$COMMIT_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then + echo "Commit created to trigger CI" + else + echo "Warning: Could not trigger CI automatically" + echo "$COMMIT_RESPONSE" | jq + + fi +fi \ No newline at end of file diff --git a/application/scripts/code-repo/gitlab/validate_repository_does_not_exist b/application/scripts/code-repo/gitlab/validate_repository_does_not_exist new file mode 100644 index 00000000..215a2ed3 --- /dev/null +++ b/application/scripts/code-repo/gitlab/validate_repository_does_not_exist @@ -0,0 +1,25 @@ +#!/bin/bash + +echo "Checking if repository exists: $GITLAB_INSTALLATION_URL/$GITLAB_GROUP_PATH/$REPOSITORY_NAME" + +REPO_CHECK=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" \ + "${GITLAB_INSTALLATION_URL}/api/v4/projects/${GITLAB_ENCODED_GROUP_PATH}%2F${REPOSITORY_NAME}" || echo "") + +if echo "$REPO_CHECK" | jq -e '.id' > /dev/null 2>&1; then + # Repository exists + if [ "$CODE_REPOSITORY_STRATEGY" = "create" ]; then + echo "Error: Repository already exists but strategy is set to 'create'. Please use a different repository name or change strategy to 'import'." + exit 1 + fi + + GITLAB_PROJECT_ID=$(echo "$REPO_CHECK" | jq -r '.id') + + export GITLAB_PROJECT_ID + + echo "Repository found and strategy is set to 'import'. Proceeding with import configuration..." +elif [ "$CODE_REPOSITORY_STRATEGY" = "import" ]; then + echo "Error: Repository does not exist but strategy is set to 'import'. Please create the repository first or change strategy to 'create'." + exit 1 +else + echo "Repository does not exist and strategy is set to 'create'. Proceeding with repository creation..." +fi \ No newline at end of file diff --git a/application/workflows/create_app.yaml b/application/workflows/create_app.yaml new file mode 100644 index 00000000..4f6e79ea --- /dev/null +++ b/application/workflows/create_app.yaml @@ -0,0 +1,10 @@ +steps: + - name: base_context + type: script + file: application/scripts/base_context + - name: create_code_repository + type: script + file: application/scripts/code-repo/create_code_repository + - name: create_asset_repository + type: script + file: application/scripts/asset-repo/create_asset_repository \ No newline at end of file diff --git a/application/workflows/create_asset_repository.yaml b/application/workflows/create_asset_repository.yaml new file mode 100644 index 00000000..6acd3ee1 --- /dev/null +++ b/application/workflows/create_asset_repository.yaml @@ -0,0 +1,10 @@ +steps: + - name: build_context + type: script + file: "$ASSET_REPOSITORY_SCRIPT_PATH/build_context" + - name: generate_repository_uri + type: script + file: "$ASSET_REPOSITORY_SCRIPT_PATH/generate_repository_uri" + - name: create_repository + type: script + file: "$ASSET_REPOSITORY_SCRIPT_PATH/create_repository" \ No newline at end of file diff --git a/application/workflows/create_code_repository.yaml b/application/workflows/create_code_repository.yaml new file mode 100644 index 00000000..7a500284 --- /dev/null +++ b/application/workflows/create_code_repository.yaml @@ -0,0 +1,22 @@ +steps: + - name: build_context + type: script + file: "$CODE_REPOSITORY_SCRIPT_PATH/build_context" + - name: validate_repository_does_not_exist + type: script + file: "$CODE_REPOSITORY_SCRIPT_PATH/validate_repository_does_not_exist" + - name: create_repository + type: script + file: "$CODE_REPOSITORY_SCRIPT_PATH/create_repository" + - name: add_collaborators + type: script + file: "$CODE_REPOSITORY_SCRIPT_PATH/add_collaborators" + - name: generate_secrets + type: script + file: "application/scripts/code-repo/generate_secrets" + - name: create_secrets + type: script + file: "$CODE_REPOSITORY_SCRIPT_PATH/create_secrets" + - name: run_first_build + type: script + file: "$CODE_REPOSITORY_SCRIPT_PATH/run_first_build" \ No newline at end of file diff --git a/azure-aro/metric/get_prom_token b/azure-aro/metric/get_prom_token new file mode 100644 index 00000000..6381ee79 --- /dev/null +++ b/azure-aro/metric/get_prom_token @@ -0,0 +1,4 @@ +#!/bin/bash + +PROMETHEUS_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +export PROMETHEUS_TOKEN \ No newline at end of file diff --git a/azure-aro/metric/workflows/metric.yaml b/azure-aro/metric/workflows/metric.yaml new file mode 100644 index 00000000..0702337c --- /dev/null +++ b/azure-aro/metric/workflows/metric.yaml @@ -0,0 +1,5 @@ +steps: + - name: get prometheus token + type: script + file: "$OVERRIDES_PATH/metric/get_prom_token" + after: build context \ No newline at end of file diff --git a/azure-aro/scripts/build_internal_domain b/azure-aro/scripts/build_internal_domain new file mode 100644 index 00000000..e250e20c --- /dev/null +++ b/azure-aro/scripts/build_internal_domain @@ -0,0 +1,15 @@ +#!/bin/bash + +SCOPE_DOMAIN=$(echo "$CONTEXT" | jq .scope.domain -r) + +PUBLIC_HOSTED_ZONE_NAME="$(echo "$CONTEXT" | jq -r '.providers["cloud-providers"].networking.public_dns_zone_name')" +PRIVATE_HOSTED_ZONE_NAME="$(echo "$CONTEXT" | jq -r '.providers["cloud-providers"].networking.private_dns_zone_name')" + +INTERNAL_DOMAIN="${SCOPE_DOMAIN%.$PUBLIC_HOSTED_ZONE_NAME}.$PRIVATE_HOSTED_ZONE_NAME" + +CONTEXT=$(echo "$CONTEXT" | jq \ + --arg internal_domain "$INTERNAL_DOMAIN" \ + '. + {internal_domain: $internal_domain}') + +export INTERNAL_DOMAIN +export CONTEXT \ No newline at end of file diff --git a/azure-aro/values.yaml b/azure-aro/values.yaml index 7ff585b6..6f906911 100644 --- a/azure-aro/values.yaml +++ b/azure-aro/values.yaml @@ -7,4 +7,9 @@ configuration: SERVICE_TEMPLATE: "$SERVICE_PATH/deployment/templates/istio/service.yaml.tpl" INITIAL_INGRESS_PATH: "$SERVICE_PATH/deployment/templates/aro/initial-httproute.yaml.tpl" BLUE_GREEN_INGRESS_PATH: "$SERVICE_PATH/deployment/templates/aro/blue-green-httproute.yaml.tpl" - GATEWAY_TYPE: aro_cluster \ No newline at end of file + GATEWAY_TYPE: aro_cluster +steps: + - name: build_private_domain + type: script + file: "$OVERRIDES_PATH/scripts/build_internal_domain" + after: build context \ No newline at end of file diff --git a/k8s/deployment/templates/aro/blue-green-httproute.yaml.tpl b/k8s/deployment/templates/aro/blue-green-httproute.yaml.tpl index cc1c9aa3..872d3968 100644 --- a/k8s/deployment/templates/aro/blue-green-httproute.yaml.tpl +++ b/k8s/deployment/templates/aro/blue-green-httproute.yaml.tpl @@ -14,45 +14,118 @@ metadata: scope: {{ .scope.slug }} scope_id: "{{ .scope.id }}" deployment-strategy: "blue-green" -{{- $global := index .k8s_modifiers "global" }} -{{- if $global }} + type: {{ .ingress_visibility }} + {{- $global := index .k8s_modifiers "global" }} + {{- if $global }} {{- $labels := index $global "labels" }} {{- if $labels }} -{{ data.ToYAML $labels | indent 4 }} + {{ data.ToYAML $labels | indent 4 }} {{- end }} -{{- end }} -{{- $ingress := index .k8s_modifiers "ingress" }} -{{- if $ingress }} + {{- end }} + {{- $ingress := index .k8s_modifiers "ingress" }} + {{- if $ingress }} {{- $labels := index $ingress "labels" }} {{- if $labels }} -{{ data.ToYAML $labels | indent 4 }} + {{ data.ToYAML $labels | indent 4 }} + {{- end }} {{- end }} -{{- end }} annotations: route.openshift.io/deployment-strategy: "blue-green" + haproxy.router.openshift.io/disable_cookies: "true" {{- $global := index .k8s_modifiers "global" }} {{- if $global }} - {{- $annotations := index $global "annotations" }} - {{- if $annotations }} +{{- $annotations := index $global "annotations" }} +{{- if $annotations }} {{ data.ToYAML $annotations | indent 4 }} - {{- end }} +{{- end }} {{- end }} {{- $ingress := index .k8s_modifiers "ingress" }} {{- if $ingress }} - {{- $annotations := index $ingress "annotations" }} - {{- if $annotations }} +{{- $annotations := index $ingress "annotations" }} +{{- if $annotations }} {{ data.ToYAML $annotations | indent 4 }} - {{- end }} +{{- end }} {{- end }} spec: host: {{ .scope.domain }} + port: + targetPort: 80 + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + wildcardPolicy: None to: kind: Service name: d-{{ .scope.id }}-{{ .blue_deployment_id }} weight: {{ sub 100 .deployment.strategy_data.desired_switched_traffic }} + alternateBackends: + - kind: Service + name: d-{{ .scope.id }}-{{ .deployment.id }} + weight: {{ .deployment.strategy_data.desired_switched_traffic }} + {{- if eq .ingress_visibility "internet-facing" }} +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: k-8-s-{{ .scope.slug }}-{{ .scope.id }}-internal + namespace: {{ .k8s_namespace }} + labels: + nullplatform: "true" + account: {{ .account.slug }} + account_id: "{{ .account.id }}" + namespace: {{ .namespace.slug }} + namespace_id: "{{ .namespace.id }}" + application: {{ .application.slug }} + application_id: "{{ .application.id }}" + scope: {{ .scope.slug }} + scope_id: "{{ .scope.id }}" + deployment-strategy: "blue-green" + type: internal + {{- $global := index .k8s_modifiers "global" }} + {{- if $global }} + {{- $labels := index $global "labels" }} + {{- if $labels }} + {{ data.ToYAML $labels | indent 4 }} + {{- end }} + {{- end }} + {{- $ingress := index .k8s_modifiers "ingress" }} + {{- if $ingress }} + {{- $labels := index $ingress "labels" }} + {{- if $labels }} + {{ data.ToYAML $labels | indent 4 }} + {{- end }} + {{- end }} + annotations: + route.openshift.io/deployment-strategy: "blue-green" + haproxy.router.openshift.io/disable_cookies: "true" +{{- $global := index .k8s_modifiers "global" }} +{{- if $global }} +{{- $annotations := index $global "annotations" }} +{{- if $annotations }} +{{ data.ToYAML $annotations | indent 4 }} +{{- end }} +{{- end }} +{{- $ingress := index .k8s_modifiers "ingress" }} +{{- if $ingress }} +{{- $annotations := index $ingress "annotations" }} +{{- if $annotations }} +{{ data.ToYAML $annotations | indent 4 }} +{{- end }} +{{- end }} +spec: + host: {{ .internal_domain }} port: targetPort: 80 + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + wildcardPolicy: None + to: + kind: Service + name: d-{{ .scope.id }}-{{ .blue_deployment_id }} + weight: {{ sub 100 .deployment.strategy_data.desired_switched_traffic }} alternateBackends: - kind: Service name: d-{{ .scope.id }}-{{ .deployment.id }} - weight: {{ .deployment.strategy_data.desired_switched_traffic }} \ No newline at end of file + weight: {{ .deployment.strategy_data.desired_switched_traffic }} + {{- end }} \ No newline at end of file diff --git a/k8s/deployment/templates/aro/initial-httproute.yaml.tpl b/k8s/deployment/templates/aro/initial-httproute.yaml.tpl index 5f13b77e..f3167a7c 100644 --- a/k8s/deployment/templates/aro/initial-httproute.yaml.tpl +++ b/k8s/deployment/templates/aro/initial-httproute.yaml.tpl @@ -13,39 +13,105 @@ metadata: application_id: "{{ .application.id }}" scope: {{ .scope.slug }} scope_id: "{{ .scope.id }}" -{{- $global := index .k8s_modifiers "global" }} -{{- if $global }} + type: {{ .ingress_visibility }} + {{- $global := index .k8s_modifiers "global" }} + {{- if $global }} {{- $labels := index $global "labels" }} {{- if $labels }} -{{ data.ToYAML $labels | indent 4 }} + {{ data.ToYAML $labels | indent 4 }} + {{- end }} + {{- end }} + {{- $ingress := index .k8s_modifiers "ingress" }} + {{- if $ingress }} + {{- $labels := index $ingress "labels" }} + {{- if $labels }} + {{ data.ToYAML $labels | indent 4 }} {{- end }} + {{- end }} + annotations: + haproxy.router.openshift.io/disable_cookies: "true" +{{- $global := index .k8s_modifiers "global" }} +{{- if $global }} +{{- $annotations := index $global "annotations" }} +{{- if $annotations }} +{{ data.ToYAML $annotations | indent 4 }} +{{- end }} {{- end }} {{- $ingress := index .k8s_modifiers "ingress" }} {{- if $ingress }} +{{- $annotations := index $ingress "annotations" }} +{{- if $annotations }} +{{ data.ToYAML $annotations | indent 4 }} +{{- end }} +{{- end }} +spec: + host: {{ .scope.domain }} + port: + targetPort: 80 + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + wildcardPolicy: None + to: + kind: Service + name: d-{{ .scope.id }}-{{ .deployment.id }} + {{- if eq .ingress_visibility "internet-facing" }} +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: k-8-s-{{ .scope.slug }}-{{ .scope.id }}-internal + namespace: {{ .k8s_namespace }} + labels: + nullplatform: "true" + account: {{ .account.slug }} + account_id: "{{ .account.id }}" + namespace: {{ .namespace.slug }} + namespace_id: "{{ .namespace.id }}" + application: {{ .application.slug }} + application_id: "{{ .application.id }}" + scope: {{ .scope.slug }} + scope_id: "{{ .scope.id }}" + type: internal + {{- $global := index .k8s_modifiers "global" }} + {{- if $global }} + {{- $labels := index $global "labels" }} + {{- if $labels }} + {{ data.ToYAML $labels | indent 4 }} + {{- end }} + {{- end }} + {{- $ingress := index .k8s_modifiers "ingress" }} + {{- if $ingress }} {{- $labels := index $ingress "labels" }} {{- if $labels }} -{{ data.ToYAML $labels | indent 4 }} + {{ data.ToYAML $labels | indent 4 }} + {{- end }} {{- end }} -{{- end }} annotations: + haproxy.router.openshift.io/disable_cookies: "true" {{- $global := index .k8s_modifiers "global" }} {{- if $global }} - {{- $annotations := index $global "annotations" }} - {{- if $annotations }} +{{- $annotations := index $global "annotations" }} +{{- if $annotations }} {{ data.ToYAML $annotations | indent 4 }} - {{- end }} +{{- end }} {{- end }} {{- $ingress := index .k8s_modifiers "ingress" }} {{- if $ingress }} - {{- $annotations := index $ingress "annotations" }} - {{- if $annotations }} +{{- $annotations := index $ingress "annotations" }} +{{- if $annotations }} {{ data.ToYAML $annotations | indent 4 }} - {{- end }} +{{- end }} {{- end }} spec: - host: {{ .scope.domain }} + host: {{ .internal_domain }} + port: + targetPort: 80 + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + wildcardPolicy: None to: kind: Service name: d-{{ .scope.id }}-{{ .deployment.id }} - port: - targetPort: 80 \ No newline at end of file + {{- end }} \ No newline at end of file diff --git a/k8s/metric/metric b/k8s/metric/metric index 49fb97b9..678e81f8 100755 --- a/k8s/metric/metric +++ b/k8s/metric/metric @@ -198,13 +198,28 @@ build_query() { query_prometheus() { local query="$1" local start_time="$2" - local end_time="$3" + #local end_time="$3" + local end_time=$(date +%s) local step="$4" local url="${PROM_URL}/api/v1/query_range" - local params="query=$(urlencode "$query")&start=$start_time&end=$end_time&step=${step}s" - curl -s -G "$url" --data-urlencode "query=$query" --data-urlencode "start=$start_time" --data-urlencode "end=$end_time" --data-urlencode "step=${step}s" + local params="query=$(urlencode "$query")&start=$start_time&end=$end_time&step=${step}s" + + + if [[ -n "$PROMETHEUS_TOKEN" ]]; then + curl -v -k -H "Authorization: Bearer $PROMETHEUS_TOKEN" -G "$url" \ + --data-urlencode "query=$query" \ + --data-urlencode "start=$start_time" \ + --data-urlencode "end=$end_time" \ + --data-urlencode "step=${step}s" + else + curl -v -k -G "$url" \ + --data-urlencode "query=$query" \ + --data-urlencode "start=$start_time" \ + --data-urlencode "end=$end_time" \ + --data-urlencode "step=${step}s" + fi } urlencode() { @@ -230,7 +245,16 @@ if [[ -n "$START_TIME" && -n "$END_TIME" ]]; then start_time=$(echo "$START_TIME" | sed 's/T/ /' | sed 's/\.[0-9]*Z$//' | xargs -I {} date -u -d "{}" +%s 2>/dev/null || echo "0") now=$(echo "$END_TIME" | sed 's/T/ /' | sed 's/\.[0-9]*Z$//' | xargs -I {} date -u -d "{}" +%s 2>/dev/null || echo "0") step=${PERIOD:-60} + ##add + current_time=$(date +%s) + if [[ $end_time -gt $current_time ]]; then + now=$current_time + else + now=$end_time + fi + ###end # Calculate interval like JavaScript service: period/60 + "m" + if [[ -n "$PERIOD" && "$PERIOD" -gt 0 ]]; then interval_minutes=$((PERIOD / 60)) if [[ $interval_minutes -lt 1 ]]; then @@ -296,10 +320,14 @@ query=$(build_query "$METRIC_NAME" "$filters" "$INTERVAL") response=$(query_prometheus "$query" "$start_time" "$now" "$step") + + + transform_response() { local response="$1" local status=$(echo "$response" | jq -r '.status') + if [[ "$status" != "success" ]]; then echo "[]" return @@ -323,4 +351,6 @@ transform_response() { transformed_results=$(transform_response "$response") + + echo "{\"metric\":\"$METRIC_NAME\",\"type\":\"$metric_type\",\"period_in_seconds\":$step,\"unit\":\"$unit\",\"results\":$transformed_results}" diff --git a/k8s/scope/build_context b/k8s/scope/build_context index 08018b9b..ffed13fb 100755 --- a/k8s/scope/build_context +++ b/k8s/scope/build_context @@ -14,11 +14,11 @@ SCOPE_VISIBILITY=$(echo "$CONTEXT" | jq -r '.scope.capabilities.visibility') if [ "$SCOPE_VISIBILITY" = "public" ]; then DOMAIN=$(echo "$CONTEXT" | jq -r --arg default "$DOMAIN" ' - .providers["cloud-providers"].networking.domain_name // $default + .providers["cloud-providers"].networking.public_dns_zone_name // .providers["cloud-providers"].networking.domain_name // $default ') else DOMAIN=$(echo "$CONTEXT" | jq -r --arg private_default "$PRIVATE_DOMAIN" --arg default "$DOMAIN" ' - (.providers["cloud-providers"].networking.private_domain_name // $private_default | if . == "" then empty else . end) // .providers["cloud-providers"].networking.domain_name // $default + (.providers["cloud-providers"].networking.private_domain_name // .providers["cloud-providers"].networking.private_dns_zone_name // $private_default | if . == "" then empty else . end) // .providers["cloud-providers"].networking.domain_name // $default ') fi SCOPE_DOMAIN=$(echo "$CONTEXT" | jq .scope.domain -r) diff --git a/k8s/scope/networking/dns/az-records/manage_route b/k8s/scope/networking/dns/az-records/manage_route index 2ba49ce6..5eb98b34 100755 --- a/k8s/scope/networking/dns/az-records/manage_route +++ b/k8s/scope/networking/dns/az-records/manage_route @@ -40,6 +40,7 @@ RESOURCE_GROUP="" AZURE_SUBSCRIPTION_ID="" HOSTED_ZONE_NAME="" HOSTED_ZONE_RG="" +VISIBILITY="" for arg in "$@"; do case $arg in @@ -49,15 +50,16 @@ for arg in "$@"; do --gateway-name=*) GATEWAY_NAME="${arg#*=}" ;; --hosted-zone-name=*) HOSTED_ZONE_NAME="${arg#*=}" ;; --hosted-zone-rg=*) HOSTED_ZONE_RG="${arg#*=}" ;; + --visibility=*) VISIBILITY="${arg#*=}" ;; esac done # Get IP based on gateway type if [ "${GATEWAY_TYPE:-istio}" = "aro_cluster" ]; then # Get IP from OpenShift router service - GATEWAY_IP=$(kubectl get svc router-default -n openshift-ingress \ + GATEWAY_IP=$(kubectl get svc "$GATEWAY_NAME" -n openshift-ingress \ -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null) - + if [ -z "$GATEWAY_IP" ]; then echo "Error: Could not get IP address from ARO router service" >&2 echo "Falling back to istio gateway..." >&2 @@ -81,14 +83,17 @@ if [ -z "$SCOPE_SUBDOMAIN" ]; then SCOPE_SUBDOMAIN="${SCOPE_DOMAIN%.$HOSTED_ZONE_NAME}" fi +echo "manage_route SCOPE_SUBDOMAIN = $SCOPE_SUBDOMAIN HOSTED_ZONE_NAME = $HOSTED_ZONE_NAME GATEWAY_IP = $GATEWAY_IP, VISIBILITY = $VISIBILITY" + if [ "$ACTION" = "CREATE" ]; then # Get access token ACCESS_TOKEN=$(get_azure_token) || exit 1 - # Create or update A record - RECORD_SET_URL="https://management.azure.com/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${HOSTED_ZONE_RG}/providers/Microsoft.Network/dnsZones/${HOSTED_ZONE_NAME}/A/${SCOPE_SUBDOMAIN}?api-version=2018-05-01" - - RECORD_BODY=$(cat <