From: Matthias Kruk Date: Mon, 14 Feb 2022 04:08:24 +0000 (+0900) Subject: Move bots to src directory X-Git-Url: https://git.corax.cc/?a=commitdiff_plain;h=15df90295e33bcec4e8702ef69f05ae15741f6b4;p=foundry Move bots to src directory The sources of the bots are not located in any subdirectories, making the installation more complicated than necessary. This commit moves the bots into the src directory, allowing the bots to be installed in one swoop. --- diff --git a/buildbot.sh b/buildbot.sh deleted file mode 100755 index debd8be..0000000 --- a/buildbot.sh +++ /dev/null @@ -1,346 +0,0 @@ -#!/bin/bash - -store_packages() { - local context="$1" - local builddir="$2" - - local package - - while read -r package; do - if ! foundry_context_add_file "$context" "build" "$package"; then - log_error "Could not store artifact $package in $context" - return 1 - fi - done < <(find "$builddir" -type f -name "*.deb") - - return 0 -} - -increase_version() { - local verrel="$1" - - local version - local unixtime - - version="${verrel%-*}" - - if ! unixtime=$(date +"%s"); then - return 1 - fi - - echo "$version-$unixtime" - return 0 -} - -make_changelog_entry() { - local package="$1" - local version="$2" - local branch="$3" - local ref="$4" - - local datetime - - if ! datetime=$(date -R); then - return 1 - fi - - cat < $datetime -EOF - - return 0 -} - -prepend_changelog() { - local changelog="$1" - local branch="$2" - local ref="$3" - - local package - local prev_version - local next_version - local prev_changelog - local updated_changelog - - if ! prev_changelog=$(< "$changelog"); then - log_error "Could not read changelog" - - elif ! package=$(grep -m 1 -oP '^\K[^ ]+' <<< "$prev_changelog"); then - log_error "Could not parse package name from changelog" - - elif ! prev_version=$(grep -m 1 -oP '^[^ ]+ \(\K[^\)]+' <<< "$prev_changelog"); then - log_error "Could not parse previous version from changelog" - - elif ! next_version=$(increase_version "$prev_version"); then - log_error "Could not increase version" - - elif ! updated_changelog=$(make_changelog_entry "$package" \ - "$next_version" \ - "$branch" \ - "$ref"); then - log_error "Could not make changlog entry" - - elif ! printf '%s\n\n\n%s\n' "$updated_changelog" \ - "$prev_changelog" > "$changelog"; then - log_error "Could not write to changelog" - - else - return 0 - fi - - return 1 -} - -build() { - local context="$1" - local repository="$2" - local branch="$3" - local ref="$4" - local builddir="$5" - - local output - local err - - err=0 - - if ! output=$(git clone "$repository" "$builddir/sources" 2>&1) || - ! output+=$(cd "$builddir/sources" 2>&1 && git checkout "$branch" 2>&1); then - err=1 - fi - - if ! foundry_context_log "$context" "build" <<< "$output"; then - log_error "Could not log to $context" - return 1 - fi - - if (( err != 0 )); then - return 1 - fi - - if [[ "$branch" == "unstable" ]]; then - if ! prepend_changelog "$builddir/sources/debian/changelog" \ - "$branch" \ - "$ref"; then - log_error "Could not add entry to $builddir/sources/debian/changelog" - return 1 - fi - fi - - if ! output=$(cd "$builddir/sources" && dpkg-buildpackage -us -uc 2>&1); then - err=1 - fi - - if ! foundry_context_log "$context" "build" <<< "$output"; then - log_error "Could not log to $context" - return 1 - fi - - if (( err != 0 )); then - return 1 - fi - - if ! store_packages "$context" "$builddir"; then - log_error "Could not store packages for $context" - return 1 - fi - - return 0 -} - -send_build_notification() { - local endpoint="$1" - local topic="$2" - local context="$3" - local repository="$4" - local branch="$5" - local ref="$6" - local result="$7" - - local buildmsg - local artifacts - - artifacts=() - - if ! buildmsg=$(foundry_msg_build_new "$context" \ - "$repository" \ - "$branch" \ - "$ref" \ - "$result" \ - artifacts); then - log_error "Could not make build message" - return 1 - fi - - log_info "Sending build message to $topic" - if ! ipc_endpoint_publish "$endpoint" "$topic" "$buildmsg"; then - log_error "Could not publish message on $endpoint to $topic" - return 1 - fi - - return 0 -} - -handle_commit_message() { - local endpoint="$1" - local publish_to="$2" - local commit="$3" - - local buildable_branches - local repository - local branch - local ref - local context_name - local context - local builddir - local -i result - local -i err - - buildable_branches=( - "master" - "stable" - "testing" - "unstable" - ) - result=0 - err=0 - - if ! branch=$(foundry_msg_commit_get_branch "$commit"); then - log_warn "No branch in commit message" - return 1 - fi - - if ! array_contains "$branch" "${buildable_branches[@]}"; then - log_warn "Refusing to build from $branch branch" - return 0 - fi - - if ! repository=$(foundry_msg_commit_get_repository "$commit"); then - log_warn "No repository in commit message" - return 1 - fi - - if ! ref=$(foundry_msg_commit_get_ref "$commit"); then - log_warn "No ref in commit message" - return 1 - fi - - context_name="${repository##*/}" - - if ! context=$(foundry_context_new "$context_name"); then - log_error "Could not create a context for $context_name" - return 1 - fi - - inst_set_status "Building $context" - - if ! builddir=$(mktemp -d); then - log_error "Could not make temporary build directory" - return 1 - fi - - log_info "Building $context in $builddir" - if ! build "$context" "$repository" "$branch" "$ref" "$builddir"; then - result=1 - fi - - log_info "Finished build of $context with status $result" - - if ! send_build_notification "$endpoint" "$publish_to" "$context" \ - "$repository" "$branch" "$ref" "$result"; then - err=1 - fi - - if ! rm -rf "$builddir"; then - log_warn "Could not remove temporary build directory $builddir" - fi - - return "$err" -} - -dispatch_tasks() { - local endpoint_name="$1" - local watch="$2" - local publish_to="$3" - - local endpoint - - if ! endpoint=$(ipc_endpoint_open "$endpoint_name"); then - log_error "Could not open endpoint $endpoint_name" - return 1 - fi - - if ! ipc_endpoint_subscribe "$endpoint" "$watch"; then - log_error "Could not subscribe to $watch" - return 1 - fi - - while inst_running; do - local msg - local data - local msgtype - - inst_set_status "Awaiting commit messages" - - if ! msg=$(ipc_endpoint_recv "$endpoint" 5); then - continue - fi - - if ! data=$(ipc_msg_get_data "$msg"); then - log_warn "Dropping malformed message" - continue - fi - - if ! msgtype=$(foundry_msg_get_type "$data") || - [[ "$msgtype" != "commit" ]]; then - log_warn "Dropping message with unexpected type" - continue - fi - - inst_set_status "Handling commit message" - - handle_commit_message "$endpoint" "$publish_to" "$data" - done - - return 0 -} - -main() { - local endpoint - local watch - local publish_to - - opt_add_arg "e" "endpoint" "v" "pub/buildbot" "The IPC endpoint to listen on" - opt_add_arg "w" "watch" "v" "commits" "The topic to watch for commit messages" - opt_add_arg "p" "publish-to" "v" "builds" "The topic to publish builds under" - - if ! opt_parse "$@"; then - return 1 - fi - - endpoint=$(opt_get "endpoint") - watch=$(opt_get "watch") - publish_to=$(opt_get "publish-to") - - if ! inst_start dispatch_tasks "$endpoint" "$watch" "$publish_to"; then - return 1 - fi - - return 0 -} - -{ - if ! . toolbox.sh; then - exit 1 - fi - - if ! include "log" "opt" "inst" "ipc" "foundry/msg" "foundry/context"; then - exit 1 - fi - - main "$@" - exit "$?" -} diff --git a/distbot.sh b/distbot.sh deleted file mode 100755 index 0403ecf..0000000 --- a/distbot.sh +++ /dev/null @@ -1,342 +0,0 @@ -#!/bin/bash - -make_repo_config() { - local domain="$1" - local codename="$2" - local architectures="$3" - local gpgkeyid="$4" - local description="$5" - - echo "Origin: $domain" - echo "Label: $domain" - echo "Codename: $codename" - echo "Architectures: $architectures" - echo "Components: main" - echo "Description: $description" - echo "SignWith: $gpgkeyid" - - return 0 -} - -repo_init() { - local repo="$1" - local domain="$2" - local codename="$3" - local arch="$4" - local gpgkeyid="$5" - local description="$6" - - local config - - if ! mkdir -p "$repo/conf" "$repo/incoming" "$repo/failed" &>/dev/null; then - log_error "Could not create directory structure in $repo" - return 1 - fi - - config=$(make_repo_config "$domain" "$codename" "$arch" \ - "$gpgkeyid" "$description") - - if ! echo "$config" > "$repo/conf/distributions"; then - return 1 - fi - - return 0 -} - -repo_add_package() { - local repository="$1" - local codename="$2" - local package="$3" - - log_info "Adding $package to $repository:$codename" - - if ! reprepro -b "$repository" includedeb \ - "$codename" "$package"; then - return 1 - fi - - return 0 -} - -verify_package() { - local package="$1" - - log_info "Verifying signature on $package" - if ! dpkg-sig --verify "$package" | log_info "dpkg-sig --verify \"$package\""; then - log_error "Could not verify signature on $package" - - return 1 - fi - - log_info "Good signature on $package" - - return 0 -} - -process_new_package() { - local context="$1" - local package="$2" - local repo="$3" - local codename="$4" - - local failed - local logoutput - - failed=true - - log_info "[#$context] New package: $package" - - if ! logoutput=$(verify_package "$package" 2>&1); then - log_error "[#$context] Invalid signature on package $package" - elif ! logoutput+=$(repo_add_package "$repo" "$codename" "$package" 2>&1); then - log_error "[#$context] Could not process $package" - else - log_info "[#$context] $package successfully added to $repo:$codename" - failed=false - fi - - if "$failed"; then - if ! log_output+=$(mv "$package" "$repo/failed/." 2>&1); then - log_error "[#$context] Could not move $package to $repo/failed/." - fi - else - if ! log_output+=$(rm "$package" 2>&1); then - log_error "[#$context] Could not remove $package" - fi - fi - - if ! foundry_context_log "$context" "dist" <<< "$logoutput"; then - log_error "Could not log to dist log of $context" - return 1 - fi - - return 0 -} - -publish_result() { - local endpoint="$1" - local publish_to="$2" - local repository="$3" - local branch="$4" - local ref="$5" - local distribution="$6" - local artifacts=("${@:7}") - - local message - - if ! message=$(foundry_msg_dist_new "$repository" \ - "$branch" \ - "$ref" \ - "$distribution" \ - "${artifacts[@]}"); then - log_error "Could not make dist message" - return 1 - fi - - if ! ipc_endpoint_publish "$endpoint" "$publish_to" "$message"; then - log_error "Could not publish message to $publish_to" - return 1 - fi - - return 0 -} - -process_sign_message() { - local repo="$1" - local codename="$2" - local signmsg="$3" - local endpoint="$4" - local publish_to="$5" - - local artifacts - local artifact - local context - local repository - local branch - local ref - local distributed - - distributed=() - - if ! repository=$(foundry_msg_sign_get_repository "$signmsg") || - ! branch=$(foundry_msg_sign_get_branch "$signmsg") || - ! ref=$(foundry_msg_sign_get_ref "$signmsg") || - ! context=$(foundry_msg_sign_get_context "$signmsg"); then - log_warn "Dropping malformed message" - return 1 - fi - - if [[ "$branch" == "unstable" ]]; then - codename="unstable" - fi - - readarray -t artifacts < <(foundry_context_get_files "$context" "signed") - - for artifact in "${artifacts[@]}"; do - local artifact_name - local extension - - artifact_name="${artifact##*/}" - extension="${artifact_name##*.}" - - if [[ "$extension" != "deb" ]]; then - log_debug "Skipping non-deb artifact $artifact_name" - continue - fi - - if process_new_package "$context" "$artifact" "$repo" "$codename"; then - distributed+=("$artifact_name") - else - log_error "Could not distribute $artifact_name" - fi - done - - if (( ${#distributed[@]} == 0 )); then - log_error "No artifacts distributed" - return 1 - fi - - if ! publish_result "$endpoint" "$publish_to" "$repository" "$branch" \ - "$ref" "$repo" "${distributed[@]}"; then - log_error "Failed to publish results for $context" - return 1 - fi - - return 0 -} - -watch_new_packages() { - local endpoint_name="$1" - local watch="$2" - local publish_to="$3" - local repo="$4" - local codename="$5" - - local endpoint - - if ! endpoint=$(ipc_endpoint_open "$endpoint_name"); then - log_error "Could not listen on IPC endpoint $endpoint_name" - return 1 - fi - - if ! ipc_endpoint_subscribe "$endpoint" "$watch"; then - log_error "Could not subscribe to $watch" - return 1 - fi - - while inst_running; do - local msg - local signmsg - local msgtype - - inst_set_status "Waiting for sign messages" - - if ! msg=$(ipc_endpoint_recv "$endpoint" 5); then - continue - fi - - if ! signmsg=$(ipc_msg_get_data "$msg"); then - log_warn "Dropping message without data" - continue - fi - - if ! msgtype=$(foundry_msg_get_type "$signmsg"); then - log_warn "Dropping message without type" - continue - fi - - if [[ "$msgtype" != "sign" ]]; then - log_warn "Dropping message with unexpected type $msgtype" - continue - fi - - process_sign_message "$repo" "$codename" "$signmsg" \ - "$endpoint" "$publish_to" - done - - return 0 -} - -looks_like_a_repository() { - local path="$1" - - if ! [ -d "$path" ]; then - return 1 - fi - - if ! [ -d "$path/incoming" ]; then - return 1 - fi - - return 0 -} - -main() { - local path - local codename - local endpoint - local watch - local publish_to - local name - local arch - local gpgkey - local desc - - opt_add_arg "e" "endpoint" "v" "pub/distbot" "The IPC endpoint to listen on" - opt_add_arg "w" "watch" "v" "signs" \ - "The topic to watch for sign messages" - opt_add_arg "p" "publish-to" "v" "dists" \ - "The topic to publish dist messages under" - - opt_add_arg "n" "name" "rv" "" "The name of the repository" - opt_add_arg "o" "output" "rv" "" "The path to the repository" - opt_add_arg "c" "codename" "v" "stable" \ - "The codename of the distribution (default: stable)" - opt_add_arg "a" "arch" "rv" "" \ - "Comma separated list of supported architectures" - opt_add_arg "k" "gpg-key" "rv" "" \ - "The GPG key used for signing" - opt_add_arg "d" "description" "rv" "" \ - "Description of the repository" - - if ! opt_parse "$@"; then - return 1 - fi - - path=$(opt_get "output") - codename=$(opt_get "codename") - endpoint=$(opt_get "endpoint") - watch=$(opt_get "watch") - publish_to=$(opt_get "publish-to") - name=$(opt_get "name") - arch=$(opt_get "arch") - gpgkey=$(opt_get "gpg-key") - desc=$(opt_get "description") - - if ! looks_like_a_repository "$path"; then - # Create new repository - log_info "Initializing repository $name:$codename in $path" - - if ! repo_init "$path" "$name" "$codename" "$arch" "$gpgkey" "$desc"; then - log_error "Could not initialize repository" - return 1 - fi - fi - - inst_start watch_new_packages "$endpoint" "$watch" "$publish_to" "$path" "$codename" - - return 0 -} - -{ - if ! . toolbox.sh; then - exit 1 - fi - - if ! include "log" "opt" "queue" "inst" "ipc" "foundry/msg" "foundry/context"; then - exit 1 - fi - - main "$@" - exit "$?" -} diff --git a/signbot.sh b/signbot.sh deleted file mode 100755 index b04609b..0000000 --- a/signbot.sh +++ /dev/null @@ -1,203 +0,0 @@ -#!/bin/bash - -publish_results() { - local endpoint="$1" - local topic="$2" - local key="$3" - local context="$4" - local repository="$5" - local branch="$6" - local ref="$7" - - local sign - - if ! sign=$(foundry_msg_sign_new "$context" \ - "$key" \ - "$repository" \ - "$branch" \ - "$ref"); then - log_error "Could not make sign message" - return 1 - fi - - if ! ipc_endpoint_publish "$endpoint" "$topic" "$sign"; then - log_error "Could not publish sign message" - return 1 - fi - - return 0 -} - -handle_build_message() { - local endpoint="$1" - local publish_to="$2" - local buildmsg="$3" - local signer_key="$4" - - local repository - local branch - local ref - local build_context - local context_name - local context - local artifact - local signlog - local result - - if ! result=$(foundry_msg_build_get_result "$buildmsg") || - ! repository=$(foundry_msg_build_get_repository "$buildmsg") || - ! branch=$(foundry_msg_build_get_branch "$buildmsg") || - ! ref=$(foundry_msg_build_get_ref "$buildmsg") || - ! build_context=$(foundry_msg_build_get_context "$buildmsg"); then - log_warn "Malformed build message. Dropping." - return 1 - fi - - if ! is_digits "$result" || - (( result != 0 )); then - log_warn "Not signing $repository#$branch [$ref] (build result was $result)" - return 1 - fi - - context_name="${repository##*/}" - - if ! context=$(foundry_context_new "$context_name"); then - log_error "Could not make new context for $context_name" - return 1 - fi - - inst_set_status "Signing $context" - - signlog="" - result=0 - - while read -r artifact; do - if [[ "$artifact" != *".deb" ]]; then - continue - fi - - if ! signlog+=$(dpkg-sig --sign "builder" \ - -k "$signer_key" \ - "$artifact" 2>&1); then - log_error "Could not sign $artifact with key $signer_key" - result=1 - - elif ! signlog+=$(foundry_context_add_file "$context" \ - "signed" \ - "$artifact" 2>&1); then - log_error "Could not add $artifact to context $context" - result=1 - fi - done < <(foundry_context_get_files "$build_context" "build") - - if ! foundry_context_log "$context" "sign" <<< "$signlog"; then - log_error "Could not log to context $context" - result=1 - fi - - if (( result == 0 )); then - if ! publish_results "$endpoint" "$publish_to" \ - "$signer_key" "$context" \ - "$repository" "$branch" \ - "$ref"; then - log_error "Could not publish results to $publish_to" - result=1 - fi - else - if ! publish_results "$endpoint" "signbot_errors" \ - "$signer_key" "$context" \ - "$repository" "$branch" \ - "$ref"; then - log_error "Could not send error to signbot_errors" - fi - fi - - return "$result" -} - -dispatch_tasks() { - local endpoint_name="$1" - local watch="$2" - local publish_to="$3" - local signer_key="$4" - - local endpoint - - if ! endpoint=$(ipc_endpoint_open "$endpoint_name"); then - log_error "Could not open IPC endpoint $endpoint_name" - return 1 - fi - - if ! ipc_endpoint_subscribe "$endpoint" "$watch"; then - log_error "Could not subscribe to $watch" - return 1 - fi - - while inst_running; do - local msg - local data - local msgtype - - inst_set_status "Watching for build messages" - - if ! msg=$(ipc_endpoint_recv "$endpoint" 5); then - continue - fi - - if ! data=$(ipc_msg_get_data "$msg"); then - log_warn "Received message without data. Dropping." - continue - fi - - if ! msgtype=$(foundry_msg_get_type "$data") || - [[ "$msgtype" != "build" ]]; then - log_warn "Received message with unexpected type. Dropping." - continue - fi - - inst_set_status "Handling build message" - handle_build_message "$endpoint" "$publish_to" "$data" "$signer_key" - done - - return 0 -} - -main() { - local endpoint - local watch - local publish_to - local key - - opt_add_arg "e" "endpoint" "v" "pub/signbot" "The IPC endpoint to listen on" - opt_add_arg "w" "watch" "v" "builds" "The topic to watch for build messages" - opt_add_arg "p" "publish-to" "v" "signs" "The topic to publish signs under" - opt_add_arg "k" "gpg-key" "rv" "" "Fingerprint of the key to sign with" - - if ! opt_parse "$@"; then - return 1 - fi - - endpoint=$(opt_get "endpoint") - watch=$(opt_get "watch") - publish_to=$(opt_get "publish-to") - key=$(opt_get "gpg-key") - - if ! inst_start dispatch_tasks "$endpoint" "$watch" "$publish_to" "$key"; then - return 1 - fi - - return 0 -} - -{ - if ! . toolbox.sh; then - exit 1 - fi - - if ! include "is" "log" "opt" "inst" "ipc" "foundry/context" "foundry/msg"; then - exit 1 - fi - - main "$@" - exit "$?" -} diff --git a/src/buildbot.sh b/src/buildbot.sh new file mode 100755 index 0000000..debd8be --- /dev/null +++ b/src/buildbot.sh @@ -0,0 +1,346 @@ +#!/bin/bash + +store_packages() { + local context="$1" + local builddir="$2" + + local package + + while read -r package; do + if ! foundry_context_add_file "$context" "build" "$package"; then + log_error "Could not store artifact $package in $context" + return 1 + fi + done < <(find "$builddir" -type f -name "*.deb") + + return 0 +} + +increase_version() { + local verrel="$1" + + local version + local unixtime + + version="${verrel%-*}" + + if ! unixtime=$(date +"%s"); then + return 1 + fi + + echo "$version-$unixtime" + return 0 +} + +make_changelog_entry() { + local package="$1" + local version="$2" + local branch="$3" + local ref="$4" + + local datetime + + if ! datetime=$(date -R); then + return 1 + fi + + cat < $datetime +EOF + + return 0 +} + +prepend_changelog() { + local changelog="$1" + local branch="$2" + local ref="$3" + + local package + local prev_version + local next_version + local prev_changelog + local updated_changelog + + if ! prev_changelog=$(< "$changelog"); then + log_error "Could not read changelog" + + elif ! package=$(grep -m 1 -oP '^\K[^ ]+' <<< "$prev_changelog"); then + log_error "Could not parse package name from changelog" + + elif ! prev_version=$(grep -m 1 -oP '^[^ ]+ \(\K[^\)]+' <<< "$prev_changelog"); then + log_error "Could not parse previous version from changelog" + + elif ! next_version=$(increase_version "$prev_version"); then + log_error "Could not increase version" + + elif ! updated_changelog=$(make_changelog_entry "$package" \ + "$next_version" \ + "$branch" \ + "$ref"); then + log_error "Could not make changlog entry" + + elif ! printf '%s\n\n\n%s\n' "$updated_changelog" \ + "$prev_changelog" > "$changelog"; then + log_error "Could not write to changelog" + + else + return 0 + fi + + return 1 +} + +build() { + local context="$1" + local repository="$2" + local branch="$3" + local ref="$4" + local builddir="$5" + + local output + local err + + err=0 + + if ! output=$(git clone "$repository" "$builddir/sources" 2>&1) || + ! output+=$(cd "$builddir/sources" 2>&1 && git checkout "$branch" 2>&1); then + err=1 + fi + + if ! foundry_context_log "$context" "build" <<< "$output"; then + log_error "Could not log to $context" + return 1 + fi + + if (( err != 0 )); then + return 1 + fi + + if [[ "$branch" == "unstable" ]]; then + if ! prepend_changelog "$builddir/sources/debian/changelog" \ + "$branch" \ + "$ref"; then + log_error "Could not add entry to $builddir/sources/debian/changelog" + return 1 + fi + fi + + if ! output=$(cd "$builddir/sources" && dpkg-buildpackage -us -uc 2>&1); then + err=1 + fi + + if ! foundry_context_log "$context" "build" <<< "$output"; then + log_error "Could not log to $context" + return 1 + fi + + if (( err != 0 )); then + return 1 + fi + + if ! store_packages "$context" "$builddir"; then + log_error "Could not store packages for $context" + return 1 + fi + + return 0 +} + +send_build_notification() { + local endpoint="$1" + local topic="$2" + local context="$3" + local repository="$4" + local branch="$5" + local ref="$6" + local result="$7" + + local buildmsg + local artifacts + + artifacts=() + + if ! buildmsg=$(foundry_msg_build_new "$context" \ + "$repository" \ + "$branch" \ + "$ref" \ + "$result" \ + artifacts); then + log_error "Could not make build message" + return 1 + fi + + log_info "Sending build message to $topic" + if ! ipc_endpoint_publish "$endpoint" "$topic" "$buildmsg"; then + log_error "Could not publish message on $endpoint to $topic" + return 1 + fi + + return 0 +} + +handle_commit_message() { + local endpoint="$1" + local publish_to="$2" + local commit="$3" + + local buildable_branches + local repository + local branch + local ref + local context_name + local context + local builddir + local -i result + local -i err + + buildable_branches=( + "master" + "stable" + "testing" + "unstable" + ) + result=0 + err=0 + + if ! branch=$(foundry_msg_commit_get_branch "$commit"); then + log_warn "No branch in commit message" + return 1 + fi + + if ! array_contains "$branch" "${buildable_branches[@]}"; then + log_warn "Refusing to build from $branch branch" + return 0 + fi + + if ! repository=$(foundry_msg_commit_get_repository "$commit"); then + log_warn "No repository in commit message" + return 1 + fi + + if ! ref=$(foundry_msg_commit_get_ref "$commit"); then + log_warn "No ref in commit message" + return 1 + fi + + context_name="${repository##*/}" + + if ! context=$(foundry_context_new "$context_name"); then + log_error "Could not create a context for $context_name" + return 1 + fi + + inst_set_status "Building $context" + + if ! builddir=$(mktemp -d); then + log_error "Could not make temporary build directory" + return 1 + fi + + log_info "Building $context in $builddir" + if ! build "$context" "$repository" "$branch" "$ref" "$builddir"; then + result=1 + fi + + log_info "Finished build of $context with status $result" + + if ! send_build_notification "$endpoint" "$publish_to" "$context" \ + "$repository" "$branch" "$ref" "$result"; then + err=1 + fi + + if ! rm -rf "$builddir"; then + log_warn "Could not remove temporary build directory $builddir" + fi + + return "$err" +} + +dispatch_tasks() { + local endpoint_name="$1" + local watch="$2" + local publish_to="$3" + + local endpoint + + if ! endpoint=$(ipc_endpoint_open "$endpoint_name"); then + log_error "Could not open endpoint $endpoint_name" + return 1 + fi + + if ! ipc_endpoint_subscribe "$endpoint" "$watch"; then + log_error "Could not subscribe to $watch" + return 1 + fi + + while inst_running; do + local msg + local data + local msgtype + + inst_set_status "Awaiting commit messages" + + if ! msg=$(ipc_endpoint_recv "$endpoint" 5); then + continue + fi + + if ! data=$(ipc_msg_get_data "$msg"); then + log_warn "Dropping malformed message" + continue + fi + + if ! msgtype=$(foundry_msg_get_type "$data") || + [[ "$msgtype" != "commit" ]]; then + log_warn "Dropping message with unexpected type" + continue + fi + + inst_set_status "Handling commit message" + + handle_commit_message "$endpoint" "$publish_to" "$data" + done + + return 0 +} + +main() { + local endpoint + local watch + local publish_to + + opt_add_arg "e" "endpoint" "v" "pub/buildbot" "The IPC endpoint to listen on" + opt_add_arg "w" "watch" "v" "commits" "The topic to watch for commit messages" + opt_add_arg "p" "publish-to" "v" "builds" "The topic to publish builds under" + + if ! opt_parse "$@"; then + return 1 + fi + + endpoint=$(opt_get "endpoint") + watch=$(opt_get "watch") + publish_to=$(opt_get "publish-to") + + if ! inst_start dispatch_tasks "$endpoint" "$watch" "$publish_to"; then + return 1 + fi + + return 0 +} + +{ + if ! . toolbox.sh; then + exit 1 + fi + + if ! include "log" "opt" "inst" "ipc" "foundry/msg" "foundry/context"; then + exit 1 + fi + + main "$@" + exit "$?" +} diff --git a/src/distbot.sh b/src/distbot.sh new file mode 100755 index 0000000..0403ecf --- /dev/null +++ b/src/distbot.sh @@ -0,0 +1,342 @@ +#!/bin/bash + +make_repo_config() { + local domain="$1" + local codename="$2" + local architectures="$3" + local gpgkeyid="$4" + local description="$5" + + echo "Origin: $domain" + echo "Label: $domain" + echo "Codename: $codename" + echo "Architectures: $architectures" + echo "Components: main" + echo "Description: $description" + echo "SignWith: $gpgkeyid" + + return 0 +} + +repo_init() { + local repo="$1" + local domain="$2" + local codename="$3" + local arch="$4" + local gpgkeyid="$5" + local description="$6" + + local config + + if ! mkdir -p "$repo/conf" "$repo/incoming" "$repo/failed" &>/dev/null; then + log_error "Could not create directory structure in $repo" + return 1 + fi + + config=$(make_repo_config "$domain" "$codename" "$arch" \ + "$gpgkeyid" "$description") + + if ! echo "$config" > "$repo/conf/distributions"; then + return 1 + fi + + return 0 +} + +repo_add_package() { + local repository="$1" + local codename="$2" + local package="$3" + + log_info "Adding $package to $repository:$codename" + + if ! reprepro -b "$repository" includedeb \ + "$codename" "$package"; then + return 1 + fi + + return 0 +} + +verify_package() { + local package="$1" + + log_info "Verifying signature on $package" + if ! dpkg-sig --verify "$package" | log_info "dpkg-sig --verify \"$package\""; then + log_error "Could not verify signature on $package" + + return 1 + fi + + log_info "Good signature on $package" + + return 0 +} + +process_new_package() { + local context="$1" + local package="$2" + local repo="$3" + local codename="$4" + + local failed + local logoutput + + failed=true + + log_info "[#$context] New package: $package" + + if ! logoutput=$(verify_package "$package" 2>&1); then + log_error "[#$context] Invalid signature on package $package" + elif ! logoutput+=$(repo_add_package "$repo" "$codename" "$package" 2>&1); then + log_error "[#$context] Could not process $package" + else + log_info "[#$context] $package successfully added to $repo:$codename" + failed=false + fi + + if "$failed"; then + if ! log_output+=$(mv "$package" "$repo/failed/." 2>&1); then + log_error "[#$context] Could not move $package to $repo/failed/." + fi + else + if ! log_output+=$(rm "$package" 2>&1); then + log_error "[#$context] Could not remove $package" + fi + fi + + if ! foundry_context_log "$context" "dist" <<< "$logoutput"; then + log_error "Could not log to dist log of $context" + return 1 + fi + + return 0 +} + +publish_result() { + local endpoint="$1" + local publish_to="$2" + local repository="$3" + local branch="$4" + local ref="$5" + local distribution="$6" + local artifacts=("${@:7}") + + local message + + if ! message=$(foundry_msg_dist_new "$repository" \ + "$branch" \ + "$ref" \ + "$distribution" \ + "${artifacts[@]}"); then + log_error "Could not make dist message" + return 1 + fi + + if ! ipc_endpoint_publish "$endpoint" "$publish_to" "$message"; then + log_error "Could not publish message to $publish_to" + return 1 + fi + + return 0 +} + +process_sign_message() { + local repo="$1" + local codename="$2" + local signmsg="$3" + local endpoint="$4" + local publish_to="$5" + + local artifacts + local artifact + local context + local repository + local branch + local ref + local distributed + + distributed=() + + if ! repository=$(foundry_msg_sign_get_repository "$signmsg") || + ! branch=$(foundry_msg_sign_get_branch "$signmsg") || + ! ref=$(foundry_msg_sign_get_ref "$signmsg") || + ! context=$(foundry_msg_sign_get_context "$signmsg"); then + log_warn "Dropping malformed message" + return 1 + fi + + if [[ "$branch" == "unstable" ]]; then + codename="unstable" + fi + + readarray -t artifacts < <(foundry_context_get_files "$context" "signed") + + for artifact in "${artifacts[@]}"; do + local artifact_name + local extension + + artifact_name="${artifact##*/}" + extension="${artifact_name##*.}" + + if [[ "$extension" != "deb" ]]; then + log_debug "Skipping non-deb artifact $artifact_name" + continue + fi + + if process_new_package "$context" "$artifact" "$repo" "$codename"; then + distributed+=("$artifact_name") + else + log_error "Could not distribute $artifact_name" + fi + done + + if (( ${#distributed[@]} == 0 )); then + log_error "No artifacts distributed" + return 1 + fi + + if ! publish_result "$endpoint" "$publish_to" "$repository" "$branch" \ + "$ref" "$repo" "${distributed[@]}"; then + log_error "Failed to publish results for $context" + return 1 + fi + + return 0 +} + +watch_new_packages() { + local endpoint_name="$1" + local watch="$2" + local publish_to="$3" + local repo="$4" + local codename="$5" + + local endpoint + + if ! endpoint=$(ipc_endpoint_open "$endpoint_name"); then + log_error "Could not listen on IPC endpoint $endpoint_name" + return 1 + fi + + if ! ipc_endpoint_subscribe "$endpoint" "$watch"; then + log_error "Could not subscribe to $watch" + return 1 + fi + + while inst_running; do + local msg + local signmsg + local msgtype + + inst_set_status "Waiting for sign messages" + + if ! msg=$(ipc_endpoint_recv "$endpoint" 5); then + continue + fi + + if ! signmsg=$(ipc_msg_get_data "$msg"); then + log_warn "Dropping message without data" + continue + fi + + if ! msgtype=$(foundry_msg_get_type "$signmsg"); then + log_warn "Dropping message without type" + continue + fi + + if [[ "$msgtype" != "sign" ]]; then + log_warn "Dropping message with unexpected type $msgtype" + continue + fi + + process_sign_message "$repo" "$codename" "$signmsg" \ + "$endpoint" "$publish_to" + done + + return 0 +} + +looks_like_a_repository() { + local path="$1" + + if ! [ -d "$path" ]; then + return 1 + fi + + if ! [ -d "$path/incoming" ]; then + return 1 + fi + + return 0 +} + +main() { + local path + local codename + local endpoint + local watch + local publish_to + local name + local arch + local gpgkey + local desc + + opt_add_arg "e" "endpoint" "v" "pub/distbot" "The IPC endpoint to listen on" + opt_add_arg "w" "watch" "v" "signs" \ + "The topic to watch for sign messages" + opt_add_arg "p" "publish-to" "v" "dists" \ + "The topic to publish dist messages under" + + opt_add_arg "n" "name" "rv" "" "The name of the repository" + opt_add_arg "o" "output" "rv" "" "The path to the repository" + opt_add_arg "c" "codename" "v" "stable" \ + "The codename of the distribution (default: stable)" + opt_add_arg "a" "arch" "rv" "" \ + "Comma separated list of supported architectures" + opt_add_arg "k" "gpg-key" "rv" "" \ + "The GPG key used for signing" + opt_add_arg "d" "description" "rv" "" \ + "Description of the repository" + + if ! opt_parse "$@"; then + return 1 + fi + + path=$(opt_get "output") + codename=$(opt_get "codename") + endpoint=$(opt_get "endpoint") + watch=$(opt_get "watch") + publish_to=$(opt_get "publish-to") + name=$(opt_get "name") + arch=$(opt_get "arch") + gpgkey=$(opt_get "gpg-key") + desc=$(opt_get "description") + + if ! looks_like_a_repository "$path"; then + # Create new repository + log_info "Initializing repository $name:$codename in $path" + + if ! repo_init "$path" "$name" "$codename" "$arch" "$gpgkey" "$desc"; then + log_error "Could not initialize repository" + return 1 + fi + fi + + inst_start watch_new_packages "$endpoint" "$watch" "$publish_to" "$path" "$codename" + + return 0 +} + +{ + if ! . toolbox.sh; then + exit 1 + fi + + if ! include "log" "opt" "queue" "inst" "ipc" "foundry/msg" "foundry/context"; then + exit 1 + fi + + main "$@" + exit "$?" +} diff --git a/src/signbot.sh b/src/signbot.sh new file mode 100755 index 0000000..b04609b --- /dev/null +++ b/src/signbot.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +publish_results() { + local endpoint="$1" + local topic="$2" + local key="$3" + local context="$4" + local repository="$5" + local branch="$6" + local ref="$7" + + local sign + + if ! sign=$(foundry_msg_sign_new "$context" \ + "$key" \ + "$repository" \ + "$branch" \ + "$ref"); then + log_error "Could not make sign message" + return 1 + fi + + if ! ipc_endpoint_publish "$endpoint" "$topic" "$sign"; then + log_error "Could not publish sign message" + return 1 + fi + + return 0 +} + +handle_build_message() { + local endpoint="$1" + local publish_to="$2" + local buildmsg="$3" + local signer_key="$4" + + local repository + local branch + local ref + local build_context + local context_name + local context + local artifact + local signlog + local result + + if ! result=$(foundry_msg_build_get_result "$buildmsg") || + ! repository=$(foundry_msg_build_get_repository "$buildmsg") || + ! branch=$(foundry_msg_build_get_branch "$buildmsg") || + ! ref=$(foundry_msg_build_get_ref "$buildmsg") || + ! build_context=$(foundry_msg_build_get_context "$buildmsg"); then + log_warn "Malformed build message. Dropping." + return 1 + fi + + if ! is_digits "$result" || + (( result != 0 )); then + log_warn "Not signing $repository#$branch [$ref] (build result was $result)" + return 1 + fi + + context_name="${repository##*/}" + + if ! context=$(foundry_context_new "$context_name"); then + log_error "Could not make new context for $context_name" + return 1 + fi + + inst_set_status "Signing $context" + + signlog="" + result=0 + + while read -r artifact; do + if [[ "$artifact" != *".deb" ]]; then + continue + fi + + if ! signlog+=$(dpkg-sig --sign "builder" \ + -k "$signer_key" \ + "$artifact" 2>&1); then + log_error "Could not sign $artifact with key $signer_key" + result=1 + + elif ! signlog+=$(foundry_context_add_file "$context" \ + "signed" \ + "$artifact" 2>&1); then + log_error "Could not add $artifact to context $context" + result=1 + fi + done < <(foundry_context_get_files "$build_context" "build") + + if ! foundry_context_log "$context" "sign" <<< "$signlog"; then + log_error "Could not log to context $context" + result=1 + fi + + if (( result == 0 )); then + if ! publish_results "$endpoint" "$publish_to" \ + "$signer_key" "$context" \ + "$repository" "$branch" \ + "$ref"; then + log_error "Could not publish results to $publish_to" + result=1 + fi + else + if ! publish_results "$endpoint" "signbot_errors" \ + "$signer_key" "$context" \ + "$repository" "$branch" \ + "$ref"; then + log_error "Could not send error to signbot_errors" + fi + fi + + return "$result" +} + +dispatch_tasks() { + local endpoint_name="$1" + local watch="$2" + local publish_to="$3" + local signer_key="$4" + + local endpoint + + if ! endpoint=$(ipc_endpoint_open "$endpoint_name"); then + log_error "Could not open IPC endpoint $endpoint_name" + return 1 + fi + + if ! ipc_endpoint_subscribe "$endpoint" "$watch"; then + log_error "Could not subscribe to $watch" + return 1 + fi + + while inst_running; do + local msg + local data + local msgtype + + inst_set_status "Watching for build messages" + + if ! msg=$(ipc_endpoint_recv "$endpoint" 5); then + continue + fi + + if ! data=$(ipc_msg_get_data "$msg"); then + log_warn "Received message without data. Dropping." + continue + fi + + if ! msgtype=$(foundry_msg_get_type "$data") || + [[ "$msgtype" != "build" ]]; then + log_warn "Received message with unexpected type. Dropping." + continue + fi + + inst_set_status "Handling build message" + handle_build_message "$endpoint" "$publish_to" "$data" "$signer_key" + done + + return 0 +} + +main() { + local endpoint + local watch + local publish_to + local key + + opt_add_arg "e" "endpoint" "v" "pub/signbot" "The IPC endpoint to listen on" + opt_add_arg "w" "watch" "v" "builds" "The topic to watch for build messages" + opt_add_arg "p" "publish-to" "v" "signs" "The topic to publish signs under" + opt_add_arg "k" "gpg-key" "rv" "" "Fingerprint of the key to sign with" + + if ! opt_parse "$@"; then + return 1 + fi + + endpoint=$(opt_get "endpoint") + watch=$(opt_get "watch") + publish_to=$(opt_get "publish-to") + key=$(opt_get "gpg-key") + + if ! inst_start dispatch_tasks "$endpoint" "$watch" "$publish_to" "$key"; then + return 1 + fi + + return 0 +} + +{ + if ! . toolbox.sh; then + exit 1 + fi + + if ! include "is" "log" "opt" "inst" "ipc" "foundry/context" "foundry/msg"; then + exit 1 + fi + + main "$@" + exit "$?" +} diff --git a/src/watchbot.sh b/src/watchbot.sh new file mode 100755 index 0000000..a1317fe --- /dev/null +++ b/src/watchbot.sh @@ -0,0 +1,285 @@ +#!/bin/bash + +_add_to_watchlist() { + local name="$1" + local value="$2" + + # assume master if user didn't specify a branch + if [[ "$value" != *"#"* ]]; then + value+="#master" + fi + + watchlist+=("$value") + return 0 +} + +watch_to_repository() { + local watch="$1" + + if [[ "$watch" == *"#"* ]]; then + echo "${watch%#*}" + else + echo "$watch" + fi + + return 0 +} + +watch_to_branch() { + local watch="$1" + + if [[ "$watch" == *"#"* ]]; then + echo "${watch##*#}" + else + echo "master" + fi + + return 0 +} + +fetch_head_smart_http() { + local watch="$1" + + local repository + local branch + local url + local re + local data + local ref + + repository=$(watch_to_repository "$watch") + branch=$(watch_to_branch "$watch") + + re="00[0-9a-f]{2}\\K[0-9a-f]{40} refs/heads/$branch" + url="$repository/info/refs?service=git-upload-pack" + + if ! data=$(curl --get --silent --location "$url" 2>/dev/null | + grep -oP "$re" --binary-files=text); then + return 1 + fi + + ref="${data%% *}" + echo "$ref" + return 0 +} + +fetch_head_dumb_http() { + local watch="$1" + + local repository + local branch + local info + local line + local re + + repository=$(watch_to_repository "$watch") + branch=$(watch_to_branch "$watch") + + # I don't know what it is that git puts between the hash + # and the "refs/heads" part, but it's not whitespaces + re="^([0-9a-fA-F]+).*refs/heads/$branch" + + if ! info=$(curl --get --silent --location "$repository/info/refs" 2>/dev/null); then + return 1 + fi + + if ! line=$(grep -m 1 "refs/heads/$branch$" <<< "$info"); then + return 1 + fi + + if ! [[ "$line" =~ $re ]]; then + return 1 + fi + + echo "${BASH_REMATCH[1]}" + return 0 +} + +fetch_head_remote() { + local watch="$1" + + local head + + if ! head=$(fetch_head_smart_http "$watch"); then + if ! head=$(fetch_head_dumb_http "$watch"); then + return 1 + fi + fi + + echo "$head" + return 0 +} + +fetch_head_local() { + local watch="$1" + + local repository + local branch + local head + + repository=$(watch_to_repository "$watch") + branch=$(watch_to_branch "$watch") + + if [ -d "$repository/.git" ]; then + # "normal" repository + if ! head=$(< "$repository/.git/refs/heads/$branch"); then + return 1 + fi + else + # bare repository + if ! head=$(< "$repository/refs/heads/$branch"); then + return 1 + fi + fi + + echo "$head" + return 0 +} + +fetch_head() { + local url="$1" + + local repository + local branch + + local head + local fetch + + case "$url" in + "http://"*|"https://"*|"ftp://"*) + fetch=fetch_head_remote + ;; + + *) + fetch=fetch_head_local + ;; + esac + + if ! head=$("$fetch" "$url"); then + return 1 + fi + + echo "$head" + return 0 +} + +fetch_heads() { + declare -n dst="$1" + local watchlist=("${@:2}") + + local watch + + for watch in "${watchlist[@]}"; do + dst["$watch"]=$(fetch_head "$watch") + done + + return 0 +} + +send_notification() { + local endpoint="$1" + local topic="$2" + local watch="$3" + local ref="$4" + + local repository + local branch + local msg + + repository=$(watch_to_repository "$watch") + branch=$(watch_to_branch "$watch") + msg=$(foundry_msg_commit_new "$repository" "$branch" "$ref") + + if ! ipc_endpoint_publish "$endpoint" "$topic" "$msg"; then + return 1 + fi + + return 0 +} + +_watch() { + local topic="$1" + local interval="$2" + local watchlist=("${@:3}") + + local endpoint + declare -A old_heads + declare -A new_heads + + if ! endpoint=$(ipc_endpoint_open); then + return 1 + fi + + while inst_running; do + local watch + + inst_set_status "Checking ${#watchlist[@]} repositories for updates" + log_info "Checking ${#watchlist[@]} repositories for updates" + + fetch_heads new_heads "${watchlist[@]}" + + for watch in "${watchlist[@]}"; do + local old_head + local new_head + + old_head="${old_heads[$watch]}" + new_head="${new_heads[$watch]}" + + if [[ "$old_head" != "$new_head" ]]; then + log_info "HEAD has changed on $watch" + + if send_notification "$endpoint" "$topic" \ + "$watch" "$new_head"; then + old_heads["$watch"]="$new_head" + else + log_warn "Could not publish to $topic" + fi + fi + done + + inst_set_status "Sleeping for $interval seconds" + sleep "$interval" + done + + return 0 +} + +main() { + local watchlist + local interval + local publish_to + + opt_add_arg "r" "repository" "rv" "" \ + "Repository to watch for updates" \ + "" _add_to_watchlist + opt_add_arg "p" "publish-to" "v" "commits" \ + "Topic to publish notifications" + opt_add_arg "i" "interval" "v" 30 \ + "Update check interval" "^[0-9]+$" + opt_add_arg "n" "name" "rv" "" \ + "The name of this instance" + + if ! opt_parse "$@"; then + return 1 + fi + + publish_to=$(opt_get "publish-to") + interval=$(opt_get "interval") + + inst_start _watch "$publish_to" "$interval" "${watchlist[@]}" + + return 0 +} + +{ + if ! . toolbox.sh; then + exit 1 + fi + + if ! include "log" "opt" "inst" "ipc" "foundry/msg"; then + exit 1 + fi + + main "$@" + exit "$?" +} diff --git a/watchbot.sh b/watchbot.sh deleted file mode 100755 index a1317fe..0000000 --- a/watchbot.sh +++ /dev/null @@ -1,285 +0,0 @@ -#!/bin/bash - -_add_to_watchlist() { - local name="$1" - local value="$2" - - # assume master if user didn't specify a branch - if [[ "$value" != *"#"* ]]; then - value+="#master" - fi - - watchlist+=("$value") - return 0 -} - -watch_to_repository() { - local watch="$1" - - if [[ "$watch" == *"#"* ]]; then - echo "${watch%#*}" - else - echo "$watch" - fi - - return 0 -} - -watch_to_branch() { - local watch="$1" - - if [[ "$watch" == *"#"* ]]; then - echo "${watch##*#}" - else - echo "master" - fi - - return 0 -} - -fetch_head_smart_http() { - local watch="$1" - - local repository - local branch - local url - local re - local data - local ref - - repository=$(watch_to_repository "$watch") - branch=$(watch_to_branch "$watch") - - re="00[0-9a-f]{2}\\K[0-9a-f]{40} refs/heads/$branch" - url="$repository/info/refs?service=git-upload-pack" - - if ! data=$(curl --get --silent --location "$url" 2>/dev/null | - grep -oP "$re" --binary-files=text); then - return 1 - fi - - ref="${data%% *}" - echo "$ref" - return 0 -} - -fetch_head_dumb_http() { - local watch="$1" - - local repository - local branch - local info - local line - local re - - repository=$(watch_to_repository "$watch") - branch=$(watch_to_branch "$watch") - - # I don't know what it is that git puts between the hash - # and the "refs/heads" part, but it's not whitespaces - re="^([0-9a-fA-F]+).*refs/heads/$branch" - - if ! info=$(curl --get --silent --location "$repository/info/refs" 2>/dev/null); then - return 1 - fi - - if ! line=$(grep -m 1 "refs/heads/$branch$" <<< "$info"); then - return 1 - fi - - if ! [[ "$line" =~ $re ]]; then - return 1 - fi - - echo "${BASH_REMATCH[1]}" - return 0 -} - -fetch_head_remote() { - local watch="$1" - - local head - - if ! head=$(fetch_head_smart_http "$watch"); then - if ! head=$(fetch_head_dumb_http "$watch"); then - return 1 - fi - fi - - echo "$head" - return 0 -} - -fetch_head_local() { - local watch="$1" - - local repository - local branch - local head - - repository=$(watch_to_repository "$watch") - branch=$(watch_to_branch "$watch") - - if [ -d "$repository/.git" ]; then - # "normal" repository - if ! head=$(< "$repository/.git/refs/heads/$branch"); then - return 1 - fi - else - # bare repository - if ! head=$(< "$repository/refs/heads/$branch"); then - return 1 - fi - fi - - echo "$head" - return 0 -} - -fetch_head() { - local url="$1" - - local repository - local branch - - local head - local fetch - - case "$url" in - "http://"*|"https://"*|"ftp://"*) - fetch=fetch_head_remote - ;; - - *) - fetch=fetch_head_local - ;; - esac - - if ! head=$("$fetch" "$url"); then - return 1 - fi - - echo "$head" - return 0 -} - -fetch_heads() { - declare -n dst="$1" - local watchlist=("${@:2}") - - local watch - - for watch in "${watchlist[@]}"; do - dst["$watch"]=$(fetch_head "$watch") - done - - return 0 -} - -send_notification() { - local endpoint="$1" - local topic="$2" - local watch="$3" - local ref="$4" - - local repository - local branch - local msg - - repository=$(watch_to_repository "$watch") - branch=$(watch_to_branch "$watch") - msg=$(foundry_msg_commit_new "$repository" "$branch" "$ref") - - if ! ipc_endpoint_publish "$endpoint" "$topic" "$msg"; then - return 1 - fi - - return 0 -} - -_watch() { - local topic="$1" - local interval="$2" - local watchlist=("${@:3}") - - local endpoint - declare -A old_heads - declare -A new_heads - - if ! endpoint=$(ipc_endpoint_open); then - return 1 - fi - - while inst_running; do - local watch - - inst_set_status "Checking ${#watchlist[@]} repositories for updates" - log_info "Checking ${#watchlist[@]} repositories for updates" - - fetch_heads new_heads "${watchlist[@]}" - - for watch in "${watchlist[@]}"; do - local old_head - local new_head - - old_head="${old_heads[$watch]}" - new_head="${new_heads[$watch]}" - - if [[ "$old_head" != "$new_head" ]]; then - log_info "HEAD has changed on $watch" - - if send_notification "$endpoint" "$topic" \ - "$watch" "$new_head"; then - old_heads["$watch"]="$new_head" - else - log_warn "Could not publish to $topic" - fi - fi - done - - inst_set_status "Sleeping for $interval seconds" - sleep "$interval" - done - - return 0 -} - -main() { - local watchlist - local interval - local publish_to - - opt_add_arg "r" "repository" "rv" "" \ - "Repository to watch for updates" \ - "" _add_to_watchlist - opt_add_arg "p" "publish-to" "v" "commits" \ - "Topic to publish notifications" - opt_add_arg "i" "interval" "v" 30 \ - "Update check interval" "^[0-9]+$" - opt_add_arg "n" "name" "rv" "" \ - "The name of this instance" - - if ! opt_parse "$@"; then - return 1 - fi - - publish_to=$(opt_get "publish-to") - interval=$(opt_get "interval") - - inst_start _watch "$publish_to" "$interval" "${watchlist[@]}" - - return 0 -} - -{ - if ! . toolbox.sh; then - exit 1 - fi - - if ! include "log" "opt" "inst" "ipc" "foundry/msg"; then - exit 1 - fi - - main "$@" - exit "$?" -}