From e3533fd4c2fc90969b77d2ddaccbda649dd74973 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 9 Aug 2018 00:42:46 +0300 Subject: Implement submit-git --- INSTALL | 3 +- brep/submit/.gitignore | 4 +- brep/submit/buildfile | 9 +- brep/submit/submit-dir.in | 57 +- brep/submit/submit-git.bash.in | 331 ++++++++++ brep/submit/submit-git.in | 498 +++++++++++++-- brep/submit/submit.bash.in | 149 ++++- doc/manual.cli | 29 +- etc/brep-module.conf | 7 + libbrep/common.hxx | 4 +- mod/mod-submit.cxx | 429 ++++++++++--- mod/options.cli | 8 + tests/submit/0f6b1460b3ec/libhello-0.1.0.tar.gz | Bin 0 -> 3033 bytes tests/submit/0f6b1460b3ec/package.manifest | 10 + tests/submit/0f6b1460b3ec/request.manifest | 10 + tests/submit/0f6b1460b3ec/result.manifest | 4 + tests/submit/README | 30 + tests/submit/buildfile | 16 + tests/submit/data.test | 35 ++ tests/submit/hello.tar.gz | Bin 0 -> 102400 bytes tests/submit/submit-dir.test | 90 +++ tests/submit/submit-git.test | 765 ++++++++++++++++++++++++ 22 files changed, 2299 insertions(+), 189 deletions(-) create mode 100644 brep/submit/submit-git.bash.in create mode 100644 tests/submit/0f6b1460b3ec/libhello-0.1.0.tar.gz create mode 100644 tests/submit/0f6b1460b3ec/package.manifest create mode 100644 tests/submit/0f6b1460b3ec/request.manifest create mode 100644 tests/submit/0f6b1460b3ec/result.manifest create mode 100644 tests/submit/README create mode 100644 tests/submit/buildfile create mode 100644 tests/submit/data.test create mode 100644 tests/submit/hello.tar.gz create mode 100644 tests/submit/submit-dir.test create mode 100644 tests/submit/submit-git.test diff --git a/INSTALL b/INSTALL index b978c7d..8c679ca 100644 --- a/INSTALL +++ b/INSTALL @@ -240,8 +240,7 @@ example: $ cp install/share/brep/www/submit.xhtml config/ $ edit config/submit.xhtml # Add custom form fields, adjust CSS style, etc. -For an example of the submission handler see the brep/submit/submit.in bash -script. +For sample submission handler implementations see brep/submit/. Here we assume you have setup an appropriate Apache2 virtual server. Open the corresponding Apache2 .conf file and add the following inside VirtualHost (you diff --git a/brep/submit/.gitignore b/brep/submit/.gitignore index 8df3374..ef91424 100644 --- a/brep/submit/.gitignore +++ b/brep/submit/.gitignore @@ -1,3 +1,5 @@ +submit.bash +submit-git.bash + brep-submit-dir brep-submit-git - diff --git a/brep/submit/buildfile b/brep/submit/buildfile index e3711f8..50f9615 100644 --- a/brep/submit/buildfile +++ b/brep/submit/buildfile @@ -5,5 +5,10 @@ import mods = libbutl.bash%bash{manifest-parser} import mods += libbutl.bash%bash{manifest-serializer} -exe{brep-submit-dir}: in{submit-dir} bash{submit} $mods -bash{submit}: in{submit} $mods # @@ Currently doesn't depend on manifest-parser. +./: exe{brep-submit-dir} exe{brep-submit-git} + +exe{brep-submit-dir}: in{submit-dir} bash{submit} +exe{brep-submit-git}: in{submit-git} bash{submit-git} bash{submit} + +bash{submit}: in{submit} $mods +bash{submit-git}: in{submit-git} bash{submit} diff --git a/brep/submit/submit-dir.in b/brep/submit/submit-dir.in index 31ae85d..4bcbe5f 100644 --- a/brep/submit/submit-dir.in +++ b/brep/submit/submit-dir.in @@ -13,70 +13,69 @@ # usage="usage: $0 " -verbose=true +verbose= #true trap "{ exit 1; }" ERR set -o errtrace # Trap ERR in functions. -@import libbutl/manifest-parser@ -@import libbutl/manifest-serializer@ - @import brep/submit/submit@ -# Submission data directory (last argument). +if [ "$#" != 1 ]; then + error "$usage" +fi + +# Submission data directory (last and the only argument). # -dir="${!#/}" +data_dir="${!#/}" -if [ -z "$dir" ]; then +if [ -z "$data_dir" ]; then error "$usage" fi -if [ ! -d "$dir" ]; then - error "'$dir' does not exist or is not a directory" +if [ ! -d "$data_dir" ]; then + error "'$data_dir' does not exist or is not a directory" fi +reference="$(basename "$data_dir")" + # Parse the submission request manifest and obtain the archive path as well # as the simulate value. # -trace "parsing $dir/request.manifest" -butl_manifest_parser_start "$dir/request.manifest" +manifest_parser_start "$data_dir/request.manifest" archive= simulate= -while IFS=: read -ru "$butl_manifest_parser_ofd" -d '' n v; do +while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do case "$n" in - archive) archive="$v" ;; + archive) archive="$v" ;; simulate) simulate="$v" ;; esac done -butl_manifest_parser_finish +manifest_parser_finish if [ -z "$archive" ]; then error "archive manifest value expected" fi if [ -n "$simulate" -a "$simulate" != "success" ]; then - trace "unrecognized simulation outcome '$simulate'" - result_manifest 400 "unrecognized simulation outcome" - exit 0 + exit_with_manifest 400 "unrecognized simulation outcome '$simulate'" fi -manifest="$dir/package.manifest" - -extract_package_manifest "$dir/$archive" "$manifest" +m="$data_dir/package.manifest" +extract_package_manifest "$data_dir/$archive" "$m" -# Parse the package manifest and obtain the package name and version. +# Parse the package manifest and obtain the package name, version, and +# project. # -trace "parsing $manifest" -butl_manifest_parser_start "$manifest" +manifest_parser_start "$m" name= version= project= -while IFS=: read -ru "$butl_manifest_parser_ofd" -d '' n v; do +while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do case "$n" in name) name="$v" ;; version) version="$v" ;; @@ -84,14 +83,14 @@ while IFS=: read -ru "$butl_manifest_parser_ofd" -d '' n v; do esac done -butl_manifest_parser_finish +manifest_parser_finish if [ -z "$name" ]; then - error "name manifest values expected" + error "name manifest value expected" fi if [ -z "$version" ]; then - error "version manifest values expected" + error "version manifest value expected" fi if [ -z "$project" ]; then @@ -99,10 +98,10 @@ if [ -z "$project" ]; then fi if [ -n "$simulate" ]; then - rm -r "$dir" + rm -r "$data_dir" trace "$name/$version submission is simulated" else trace "$name/$version submission is queued" fi -result_manifest 200 "$name/$version submission is queued" "$reference" +exit_with_manifest 200 "$name/$version submission is queued" "$reference" diff --git a/brep/submit/submit-git.bash.in b/brep/submit/submit-git.bash.in new file mode 100644 index 0000000..8d5cc84 --- /dev/null +++ b/brep/submit/submit-git.bash.in @@ -0,0 +1,331 @@ +# file : brep/submit/submit-git.bash.in +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Utility functions for the submit-git handler. + +if [ "$brep_submit_git" ]; then + return 0 +else + brep_submit_git=true +fi + +@import brep/submit/submit@ + +# If the section is mapped to a directory in the repository configuration then +# return this directory path and empty string otherwise. +# +function section_dir () #
+{ + trace_func "$@" + + local sec="$1" + local rep="$2" + + local owners # Unused but is declared to avoid polluting the global space. + local -A sections + + run source "$rep/submit.config.bash" + + local r="${sections[$sec]}" + if [ -z "$r" ]; then + r="${sections['*']}" + fi + + echo "$r" +} + +# If the owners directory is set in the repository configuration then return +# this directory path prefixed with the repository directory path and the +# empty string otherwise. +# +function owners_dir () # +{ + local rep="$1" + + local owners + local -A sections # Is declared to avoid polluting the global space. + + run source "$rep/submit.config.bash" + + local r= + if [ -n "$owners" ]; then + r="$rep/$owners" + fi + + echo "$r" +} + +# Check if a repository already contains the package. Respond with the +# 'duplicate submission' result manifest and exit if that's the case. +# +function check_package_duplicate () # +{ + trace_func "$@" + + local nam="$1" + local ver="$2" + local rep="$3" + + local owners # Unused but is declared to avoid polluting the global space. + local -A sections + + run source "$rep/submit.config.bash" + + # Check for duplicate package in all sections. Use -.* + # without .tar.gz in case we want to support more archive types later. + # + local s + for s in "${!sections[@]}"; do + local d="$rep/${sections[$s]}" + + if [ -d "$d" ]; then + local f + f="$(run find "$d" -name "$nam-$ver.*")" + + if [ -n "$f" ]; then + trace "found: $f" + exit_with_manifest 422 "duplicate submission" + fi + fi + done +} + +# Serialize the project or package owner manifest (they have the same set of +# values) to the specified manifest file. +# +function create_owner_manifest () # +{ + trace_func "$@" + + local nam="$1" + local eml="$2" + local ctl="$3" + local man="$4" + + if [ -f "$man" ]; then + error "'$man' already exists" + fi + + manifest_serializer_start "$man" + + manifest_serialize "" "1" # Start of manifest. + manifest_serialize "name" "$nam" + manifest_serialize "email" "$eml" + manifest_serialize "control" "$ctl" + + manifest_serializer_finish +} + +# Strip the query part and the leaf path component from the repository URL. +# +function repository_base () # +{ + # First, strip the URL query part, then component. + # + sed -n \ +-e 's%^\([^?]*\).*$%\1%' \ +-e 's%^\(.*\)/[^/]\{1,\}/\{0,1\}$%\1%p' \ +<<<"$1" +} + +# Authenticate the project name owner. Make sure that the control manifest +# value is specified unless authentication is disabled. +# +# Possible return values: +# +# - 'project' if the project belongs to the submitter +# - 'unknown' if the project name is not yet known +# - 'disabled' if the owners directory is not configured +# - result manifest describing the authentication error +# +# Note that the authentication error result always starts with ':'. +# +function auth_project () # +{ + trace_func "$@" + + local prj="$1" + local ctl="${2%/}" + local rep="$3" + + local d + d="$(owners_dir "$rep")" + + if [ -z "$d" ]; then + echo "disabled" + return + fi + + if [ -z "$ctl" ]; then + exit_with_manifest 400 "control manifest value expected" + fi + + local r="unknown" + local m="$d/$prj/project-owner.manifest" + + # If the project owner manifest exists then parse it and try to authenticate + # the submitter as the project owner. + # + if [ -f "$m" ]; then + ctl="$(repository_base "$ctl")" + + # Parse the project owner manifest. + # + manifest_parser_start "$m" + + local n v + while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do + if [ "$n" == "control" -a "${v%/}" == "$ctl" ]; then + r="project" + break + fi + done + + manifest_parser_finish + + if [ "$r" != "project" ]; then + exit_with_manifest 401 "project owner authentication failed" + fi + fi + + echo "$r" +} + +# Authenticate the package name owner. Make sure that the control manifest +# value is specified unless authentication is disabled. It is assumed that the +# project ownership is already authenticated (possibly by another repository). +# +# Possible return values: +# +# - 'package' if the package belongs to the submitter +# - 'unknown' if the package name is not taken in the project +# - 'disabled' if the owners directory is not configured +# - result manifest describing the authentication error +# +# Note that the authentication error result always starts with ':'. +# +function auth_package () # +{ + trace_func "$@" + + local prj="$1" + local pkg="$2" + local ctl="${3%/}" + local rep="$4" + + local d + d="$(owners_dir "$rep")" + + if [ -z "$d" ]; then + echo "disabled" + return + fi + + if [ -z "$ctl" ]; then + exit_with_manifest 400 "control manifest value expected" + fi + + local r="unknown" + local m="$d/$prj/$pkg/package-owner.manifest" + + # If the package owner manifest exists then parse it and try to authenticate + # the submitter as the package owner. + # + if [ -f "$m" ]; then + + # Parse the package owner manifest. + # + manifest_parser_start "$m" + + local n v + while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do + if [ "$n" == "control" -a "${v%/}" == "$ctl" ]; then + r="package" + break + fi + done + + manifest_parser_finish + + if [ "$r" != "package" ]; then + exit_with_manifest 401 "package owner authentication failed" + fi + fi + + echo "$r" +} + +# Check that the package name is unknown to the repository. Owners directory +# is expected to be configured. +# +function auth_package_unknown () # +{ + trace_func "$@" + + local pkg="$1" + local rep="$2" + + local d + d="$(owners_dir "$rep")" + + # Sanity check that the owners directory configured for the repository. + # + if [ -z "$d" ]; then + error "no owners directory configured for '$rep'" + fi + + # While configured, the owners directory may not yet exist. + # + if [ -d "$d" ]; then + local f + f="$(run find "$d" -path "$d/*/$pkg/package-owner.manifest")" + + if [ -n "$f" ]; then + trace "found: $f" + exit_with_manifest 401 "package owner authentication failed" + fi + fi +} + +# Return lower-case URL scheme or empty string if the argument doesn't look +# like a URL. +# +function url_scheme () # +{ + sed -n -e 's%^\(.*\)://.*$%\L\1%p' <<<"$1" +} + +# Checks that the repository properly responds to the probing request before +# the timeout (in seconds). Noop for protocols other than HTTP(S). +# +function check_connectivity () # +{ + trace_func "$@" + + local url="$1" + local tmo="$2" + + local s + s="$(url_scheme "$url")" + + if [ "$s" == "http" -o "$s" == "https" ]; then + local u q + + u="$(sed -n -e 's%^\([^?]*\).*$%\1%p' <<<"$url")" # Strips query part. + q="$(sed -n -e 's%^[^?]*\(.*\)$%\1%p' <<<"$url")" # Query part. + + if [ -z "$q" ]; then + u="$u/info/refs?service=git-upload-pack" + else + u="$u/info/refs$q&service=git-upload-pack" + fi + + # This function is called on repositories other than ours (e.g., control) + # so we don't want a failure to be logged. + # + if ! run_silent curl -S -s --max-time "$tmo" "$u" >/dev/null; then + exit_with_manifest 503 "submission service temporarily unavailable" + fi + fi +} diff --git a/brep/submit/submit-git.in b/brep/submit/submit-git.in index fbb69de..c27edab 100644 --- a/brep/submit/submit-git.in +++ b/brep/submit/submit-git.in @@ -14,8 +14,8 @@ # # The handler also implements the project/package name ownership verification # by performing the submitter authentication/authorization based on the -# control repository mechanism describe in bdep-publish(1). This functionality -# is optional. +# control repository mechanism described in bdep-publish(1). This +# functionality is optional. # # The handler can operate with a single git repository, called "target", or # with two git repositories, in which case the first is the target and the @@ -64,20 +64,20 @@ # following structure: # # / -# ├── / -# │ ├── / -# │ │ └── package-owner.manifest -# │ ├── / -# │ │ └── package-owner.manifest -# │ ├── ... -# │ └── project-owner.manifest -# ├── / -# │ └── ... -# └──... +# |-- / +# | |-- / +# | | `-- package-owner.manifest +# | |-- / +# | | `-- package-owner.manifest +# | |-- ... +# | `-- project-owner.manifest +# |-- / +# | `-- ... +# `-- ... # # If the submitted project name is not yet known, then the handler script # creates a new project subdirectory and saves project-owner.manifest. The -# project owner manifest contain the following values in the specified order: +# project owner manifest contains the following values in the specified order: # # name: # email: @@ -95,7 +95,7 @@ # # Similarly, if the submitted package name is not yet known, then the handler # script creates a new package subdirectory and saves package-owner.manifest. -# The package owner manifest contain the following values in the specified +# The package owner manifest contains the following values in the specified # order: # # name: @@ -131,42 +131,464 @@ # add, commit, and push to reference and then remove, commit, and push to # target. # -# On the handler side, before adding a package or new ownership for a -# project/package name, the script re-checks the reference repository for -# updated information. +# On the handler side, the script acts in the opposite order cloning the +# target prior pulling the reference in order not to get into the situation +# where it misses the ownership info that is not in the reference yet but no +# longer in the target. Note that if some move happens after the cloning, +# then the script will be unable to push the target modification and will +# re-try the whole authentication procedure from scratch. +# +# - Filesystem entries that exist or are created in the data directory: +# +# -.tar.gz saved by brep (could be other archives in the future) +# request.manifest created by brep +# package.manifest extracted by the handler +# target/ cloned by the handler +# control/ cloned by the handler +# result.manifest saved by brep # usage="usage: $0 [] " +# Diagnostics. +# +verbose= #true + +# Git network operations timeout (seconds). +# +# Note that we don't cover protocols other than HTTP(S) since for them git +# doesn't support any timeouts (though we may be able to cobble something +# up for SSH). +# +git_timeout=10 + +# The reference repository lock timeout (seconds). +# +ref_lock_timeout=30 + trap "{ exit 1; }" ERR set -o errtrace # Trap ERR in functions. -# Implementation notes: +@import brep/submit/submit@ +@import brep/submit/submit-git@ + +if [ "$#" -lt 2 -o "$#" -gt 3 ]; then + error "$usage" +fi + +# Target repository URL. # -# - Check for duplicate package archive in all the sections. Before auth. Use -# -.* instead of .tar.gz in case we support other formats -# later. +tgt_repo="$1" +shift + +if [ -z "$tgt_repo" ]; then + error "$usage" +fi + +# Reference repository directory. # -# - Push permission for target repo (add www-data to scm group)? +# Note that the last argument is always the submission data directory. # -# - Network errors/timeouts on git pull for ref repo? What is the error (try -# again)? I think also let's not assume target repo is local. +ref_repo= -# Workflow: -# -# 0. The same steps as submit-dir. +if [ "$#" -gt 1 ]; then + ref_repo="$1" + shift + + if [ -z "$ref_repo" ]; then + error "$usage" + fi + + if [ ! -d "$ref_repo" ]; then + error "'$ref_repo' does not exist or is not a directory" + fi +fi + +# Submission data directory. # -# 1. If ref-repo specified, lock, pull, and check: -# - duplicate -# - auth (read-only) +data_dir="$1" +shift + +if [ -z "$data_dir" ]; then + error "$usage" +fi + +if [ ! -d "$data_dir" ]; then + error "'$data_dir' does not exist or is not a directory" +fi + +reference="$(basename "$data_dir")" + +# Git verbosity options. +# +# Note that not all git commands support the -q/-v options. Also note that +# these variable expansions should not be quoted. +# +if [ "$verbose" ]; then + gqo= + gvo="-v" +else + gqo="-q" + gvo= +fi + +# Git doesn't support the connection timeout option. The options we use are +# just an approximation of the former, that, in particular, don't cover the +# connection establishing. To work around this problem, before running a git +# command that assumes the remote repository communication we manually check +# connectivity with the remote repository. +# +git_http_timeout=("-c" "http.lowSpeedLimit=1" \ + "-c" "http.lowSpeedTime=$git_timeout") + +# Parse the submission request manifest and obtain the required values. # -# 2. Clone tgt-repo, check: -# - duplicate -# - auth (read-write) -# ? if fully auth'd by ref-repo, should we skip it here? +manifest_parser_start "$data_dir/request.manifest" + +archive= +sha256sum= +section= +email= +control= +simulate= + +while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do + case "$n" in + archive) archive="$v" ;; + sha256sum) sha256sum="$v" ;; + section) section="$v" ;; + email) email="$v" ;; + control) control="${v%/}" ;; + simulate) simulate="$v" ;; + esac +done + +manifest_parser_finish + +if [ -z "$archive" ]; then + error "archive manifest value expected" +fi + +if [ -z "$sha256sum" ]; then + error "sha256sum manifest value expected" +fi + +if [ -n "$simulate" -a "$simulate" != "success" ]; then + exit_with_manifest 400 "unrecognized simulation outcome '$simulate'" +fi + +# Note: checking for section, email, and control later. + +m="$data_dir/package.manifest" +extract_package_manifest "$data_dir/$archive" "$m" + +# Parse the package manifest and obtain the package name and version. # -# 3. Clone control branch and authorize. +manifest_parser_start "$m" + +name= +version= +project= + +while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do + case "$n" in + name) name="$v" ;; + version) version="$v" ;; + project) project="$v" ;; + esac +done + +manifest_parser_finish + +if [ -z "$name" ]; then + error "name manifest value expected" +fi + +if [ -z "$version" ]; then + error "version manifest value expected" +fi + +if [ -z "$project" ]; then + project="$name" +fi + +function git_add () # ... +{ + local d="$1" + shift + + run git -C "$d" add $gvo "$@" >&2 +} + +function git_clone () # ... +{ + local url="$1" + shift + + local dir="$1" + shift + + check_connectivity "$url" "$git_timeout" + run git "${git_http_timeout[@]}" clone $gqo $gvo "$@" "$url" "$dir" >&2 +} + +# Dor now we make 10 re-tries to add the package and push to target. Push can +# fail due to the target-to-reference information move race (see the above +# notes for details) or because concurrent submissions. We may want to make it +# configurable in the future. # -# 4. Copy archive, commit and push. If push fails, remove clone and -# restart from 1 (yes, from ref-repo). -# - put submission manifest into commit message for record? +pkg_added= + +for i in {1..11}; do + + # Clone the target repository. + # + tgt_dir="$data_dir/target" + git_clone "$tgt_repo" "$tgt_dir" --single-branch --depth 1 + + check_package_duplicate "$name" "$version" "$tgt_dir" + + # Check for duplicates and try to authenticate the package ownership using + # information in the reference repository, if specified. + # + if [ -n "$ref_repo" ]; then + + remote_url=$(git -C "$ref_repo" config --get remote.origin.url) + + # Open the reading file descriptor and lock the repository. Fail if unable + # to lock before timeout. + # + l="$ref_repo/submit.config.bash" + trace "+ exec {fd}<$l" + exec {fd}<"$l" + + if ! run flock -w "$ref_lock_timeout" "$fd"; then + exit_with_manifest 503 "submission service temporarily unavailable" + fi + + # Pull the reference repository. + # + check_connectivity "$remote_url" "$git_timeout" + run git "${git_http_timeout[@]}" -C "$ref_repo" pull $gqo $gvo >&2 + + # Check the package duplicate. + # + check_package_duplicate "$name" "$version" "$ref_repo" + + # Authenticate the project ownership. + # + auth="$(auth_project "$project" "$control" "$ref_repo")" + + # Try to authenticate the package ownership if the project ownership was + # authenticated successfully. + # + if [ "$auth" == "project" ]; then + a="$(auth_package "$project" "$name" "$control" "$ref_repo")" + + # If the package is unknown to this project, we will try to authenticate + # the package name with the target repository later and so we keep the + # 'project' auth state. + # + if [ "$a" != "unknown" ]; then + auth="$a" + fi + fi + + trace "reference auth: $auth" + + if [ "${auth:0:1}" == ":" ]; then # Authentication error? + echo "$auth" + exit 0 + fi + + # If the package is not present in the specified project then we need to + # make sure it is also not present in any other project. + # + if [ "$auth" == "project" -o "$auth" == "unknown" ]; then + auth_package_unknown "$name" "$ref_repo" + fi + + trace "+ exec {fd}<&-" + exec {fd}<&- # Close the file descriptor and unlock the repository. + else + auth="disabled" + fi + + ref_auth="$auth" + + # Now try to authenticate the package ownership using information in the + # target repository unless already authenticated with reference. + # + if [ "$auth" != "package" ]; then + + # Don't authenticate the project ownership if this is already done with + # the reference repository. + # + if [ "$auth" != "project" ]; then + auth="$(auth_project "$project" "$control" "$tgt_dir")" + fi + + # Try to authenticate the package ownership if the project ownership was + # authenticated successfully. + # + if [ "$auth" == "project" ]; then + auth="$(auth_package "$project" "$name" "$control" "$tgt_dir")" + fi + + trace "target auth: $auth" + + if [ "${auth:0:1}" == ":" ]; then # Authentication error? + echo "$auth" + exit 0 + fi + fi + + trace "resulting auth: $auth" + + # Sanity check the auth variable value. + # + case "$auth" in + package) ;; + unknown) ;; + disabled) ;; + + *) error "unexpected resulting auth '$auth'";; + esac + + # Establish ownership of the package name unless already done. + # + if [ "$auth" == "unknown" ]; then + + # Check that the necessary request manifest values are specified. + # + if [ -z "$email" ]; then + exit_with_manifest 400 "email manifest value expected" + fi + + # Check that the package doesn't belong yet to some other project. + # + auth_package_unknown "$name" "$tgt_dir" + + # First the project name. + # + # Note that owners_dir() shouldn't return an empty string at this stage. + # + d="$(owners_dir "$tgt_dir")/$project" + + # Establish ownership of the project name unless already done. Note that + # it can only be owned by the submitter at this stage. + # + prj_man="$d/project-owner.manifest" + + if [ ! -f "$prj_man" ]; then + run mkdir -p "$d" # Also creates the owners directory if not exist. + + ctl="$(repository_base "$control")" + create_owner_manifest "$project" "$email" "$ctl" "$prj_man" + + # Add the project owners manifest file to git repository using the path + # relative to the repository directory. + # + git_add "$tgt_dir" "${prj_man#$tgt_dir/}" + fi + + # Now the package name. + # + d="$d/$name" + run mkdir "$d" + + pkg_man="$d/package-owner.manifest" + create_owner_manifest "$name" "$email" "$control" "$pkg_man" + + # Add the package owners manifest file using path relative to the + # repository directory. + # + git_add "$tgt_dir" "${pkg_man#$tgt_dir/}" + + auth="package" + fi + + # Respond with the 'unauthorized' manifest if we failed to authenticate the + # submitter as the package owner, unless both the reference and target + # repositories have the ownership authentication disabled. In the latter + # case no authorization is required. + # + if [ "$auth" != "disabled" -o "$ref_auth" != "disabled" ]; then + + # Respond with the 'unauthorized' manifest if not the package owner. + # + if [ "$auth" != "package" ]; then + if [ "$auth" == "project" -o "$ref_auth" == "project" ]; then + exit_with_manifest 401 "package owner authentication failed" + else + exit_with_manifest 401 "project owner authentication failed" + fi + fi + + # Authorize the submission. + # + ctl_dir="$data_dir/control" + + git_clone "$control" "$ctl_dir" --single-branch --depth 1 \ + --branch "build2-control" + + if [ ! -f "$ctl_dir/submit/${sha256sum:0:16}" ]; then + exit_with_manifest 401 "package publishing authorization failed" + fi + fi + + # Add the package archive to the target repository. + # + s="$(section_dir "$section" "$tgt_dir")" + + if [ -z "$s" ]; then + exit_with_manifest 400 "unrecognized section '$section'" + fi + + d="$tgt_dir/$s/$project" + run mkdir -p "$d" # Create all the parent directories as well. + + # We copy the archive rather than move it since we may need it for a re-try. + # + a="$d/$archive" + run cp "$data_dir/$archive" "$a" + + git_add "$tgt_dir" "${a#$tgt_dir/}" + + run git -C "$tgt_dir" commit $gqo $gvo -F - <&2 +Add $name/$version to $s/$project + +$(cat "$data_dir/request.manifest") +EOF + + check_connectivity "$tgt_repo" "$git_timeout" + + # Try to push the target modifications, unless simulating. If this succeeds + # then we are done. Otherwise, drop the target directory and re-try the + # whole authentication/authorization procedure, unless we are out of + # attempts. + # + if [ -z "$simulate" ]; then + if run_silent git "${git_http_timeout[@]}" -C "$tgt_dir" push >&2; then + pkg_added=true + break + else + run rm -r -f "$tgt_dir" "$ctl_dir" + fi + fi +done + +if [ ! "$pkg_added" ]; then + exit_with_manifest 503 "submission service temporarily unavailable" +fi + +# Remove the no longer needed submission data directory. # +run rm -r -f "$data_dir" + +if [ -n "$simulate" ]; then + trace "$name/$version submission is simulated" +else + trace "$name/$version submission is queued" +fi + +exit_with_manifest 200 "$name/$version submission is queued" "$reference" diff --git a/brep/submit/submit.bash.in b/brep/submit/submit.bash.in index 8315315..babf081 100644 --- a/brep/submit/submit.bash.in +++ b/brep/submit/submit.bash.in @@ -2,22 +2,24 @@ # copyright : Copyright (c) 2014-2018 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file +# Utility functions useful for implementing submission handlers. + if [ "$brep_submit" ]; then return 0 else brep_submit=true fi +@import libbutl/manifest-parser@ @import libbutl/manifest-serializer@ # Diagnostics. # - # We expect the user to set the verbose variable either to true or empty # (false). # if [ ! -v verbose ]; then - echo "variable 'verbose' is not set" 2>&1 + echo "error: variable 'verbose' is not set" >&2 exit 1 fi @@ -26,10 +28,13 @@ fi # [Mon Jul 23 17:48:46.945079 2018] [brep:error] [pid 123:tid 456] [brep::submit::init]: error description # # We will use the (almost) same format for our diagnostics (redirected to the -# Apache's error_log) so it can easily be attributed to the brep module. +# Apache's error_log), so it can easily be attributed to the brep module. # info_self="$(basename $0)" -info_ref="$(basename "${!#/}")" # Last argument is the submission directory. + +if [ "$#" -gt 0 ]; then + info_ref="$(basename "${!#/}")" # Last argument is the submission directory. +fi function info () # { @@ -47,52 +52,146 @@ function info () # } function error () { info "error" "$*"; exit 1; } + function trace () { if [ "$verbose" ]; then info "info" "$*"; fi } +# Trace a command line, quoting empty arguments as well as those that contain +# spaces. +# +function trace_cmd () # ... +{ + if [ "$verbose" ]; then + local s="+" + while [ $# -gt 0 ]; do + if [ -z "$1" -o -z "${1##* *}" ]; then + s="$s '$1'" + else + s="$s $1" + fi + + shift + done + + info "info" "$s" + fi +} + +# Trace the current function name and arguments. +# +function trace_func () # ... +{ + trace_cmd "${FUNCNAME[1]}" "$@" +} + +# Trace and run a command. +# +function run () # ... +{ + trace_cmd "$@" + "$@" +} + +# Same as above but also redirect the command stderr to /dev/null, unless +# running in the verbose mode. +# +# Note that we don't redirect stdout, so it can still be captured. +# +function run_silent () # ... +{ + trace_cmd "$@" + + if [ "$verbose" ]; then + "$@" + else + "$@" 2>/dev/null + fi +} + +# Wrap libbutl manifest parsing/serializing functions to shorten names and to +# add tracing. +# +function manifest_parser_start () # [] +{ + trace_func "$@" + butl_manifest_parser_start "$@" + + manifest_parser_ofd="$butl_manifest_parser_ofd" +} + +function manifest_parser_finish () +{ + trace_func + butl_manifest_parser_finish +} + +function manifest_serializer_start () # [] +{ + trace_func "$@" + butl_manifest_serializer_start "$@" + + manifest_serializer_ifd="$butl_manifest_serializer_ifd" +} + +function manifest_serializer_finish () +{ + trace_func + butl_manifest_serializer_finish +} + # Serialize one manifest name/value pair. # -function serialize () # +function manifest_serialize () # { - printf "%s:%s\0" "$1" "$2" >&"$butl_manifest_serializer_ifd" +# trace "$1: $2" + printf "%s:%s\0" "$1" "$2" >&"$manifest_serializer_ifd" } -# Serialize the submission result manifest to stdout. +# Serialize the submission result manifest to stdout and exit the (sub-)shell +# with the zero status. # -function result_manifest () # [] +function exit_with_manifest () # [] { + trace_func "$@" + local sts="$1" local msg="$2" local ref="$3" - trace "serializing result manifest" - butl_manifest_serializer_start + manifest_serializer_start - serialize "" "1" # Start of manifest. - serialize "status" "$sts" - serialize "message" "$msg" + manifest_serialize "" "1" # Start of manifest. + manifest_serialize "status" "$sts" + manifest_serialize "message" "$msg" if [ -n "$ref" ]; then - serialize "reference" "$ref" + if [ "$sts" != "200" ]; then + error "reference for code $sts" + fi + + manifest_serialize "reference" "$ref" + elif [ "$sts" == "200" ]; then + error "no reference for code $sts" fi - butl_manifest_serializer_finish + manifest_serializer_finish + run exit 0 } -# Verify the archive is a valid bpkg package and extract its manifest file. +# Verify archive is a valid package and extract its manifest into +# file. # function extract_package_manifest () # { local arc="$1" local man="$2" - # Should we remove the submission directory with an invalid package? - # Probably it's better to leave it for potential investigation. Note that we - # can always grep for such directories based on the result.manifest file - # they contain. - # - if ! bpkg pkg-verify --manifest "$arc" >"$man" 2>/dev/null; then - trace "$arc is not a valid package" - result_manifest 400 "archive is not a valid package (run bpkg pkg-verify for details)" - exit 0 + if ! run_silent bpkg pkg-verify --manifest "$arc" >"$man"; then + # Perform the sanity check to make sure that bpkg is runnable. + # + if ! run bpkg --version >/dev/null; then + error "unable to run bpkg" + fi + + exit_with_manifest 400 "archive is not a valid package (run bpkg pkg-verify for details)" fi } diff --git a/doc/manual.cli b/doc/manual.cli index 0a798bb..eba170d 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -75,10 +75,7 @@ temporary subdirectory next to the archive.| Move/rename the temporary submission subdirectory to \c{submit-data} as an atomic operation using the 12-character abbreviated checksum as its new -name. If such a directory already exist, then this is a duplicate submission. - -Note also that once the directory is successfully moved, it is never removed -by \c{brep}, even in case of a subsequent error.| +name. If such a directory already exist, then this is a duplicate submission.| \li|Invoke the submission handler program. @@ -90,11 +87,27 @@ The handler program is expected to write the submission result manifest to \c{stdout} and terminate with the zero exit status. A non-zero exit status is treated as an internal error. The handler program's \c{stderr} is logged. +Note that the handler program should report temporary server errors (service +overload, network connectivity loss, etc.) via the submission result manifest +status values in the [500-599] range (HTTP server error) rather than via a +non-zero exit status. + The handler program assumes ownership of the submission directory and can -move/remove it (for example, in case of an invalid submission). If after the -handler program terminates the submission directory still exists, the -submission result manifest is saved as \c{result.manifest} into this -directory, next to the request manifest and archive. +move/remove it. If after the handler program terminates the submission +directory still exists, then it is handled by \c{brep} depending on the +handler process exit status and the submission result manifest status value. +If the process has terminated abnormally or with a non-zero exit status or the +result manifest status is in the [500-599] range (HTTP server error), then the +directory is saved for troubleshooting by appending a numeric extension to its +name. Otherwise, if the status is in the [400-499] range (HTTP client error), +then the directory is removed. If the directory is left in place by the +handler or is saved for troubleshooting, then the submission result manifest +is saved as \c{result.manifest} into this directory, next to the request +manifest and archive. + +If \c{submit-handler-timeout} is configured and the handler program does not +exit in the alloted time, then it is killed and its termination is treated as +abnormal. If the handler program is not specified, then the following submission result manifest is implied (note that it is not saved): diff --git a/etc/brep-module.conf b/etc/brep-module.conf index 0612969..e5770fb 100644 --- a/etc/brep-module.conf +++ b/etc/brep-module.conf @@ -258,6 +258,13 @@ menu About=?about # submit-handler-argument +# The handler program timeout in seconds. If specified and the handler does +# not exit in the alloted time, then it is killed and its termination is +# treated as abnormal. +# +# submit-handler-timeout 60 + + # Trace verbosity. Disabled by default. # # verbosity 0 diff --git a/libbrep/common.hxx b/libbrep/common.hxx index df838a7..5b172ec 100644 --- a/libbrep/common.hxx +++ b/libbrep/common.hxx @@ -104,8 +104,8 @@ namespace brep #pragma db map type(dir_path) as(string) \ to((?).string ()) from(brep::dir_path (?)) - // Ensure that timestamp can be represented in nonoseconds without loss of - // accuracy, so the following ODB mapping is adequate. + // Make sure that timestamp can be represented in nonoseconds without loss + // of accuracy, so the following ODB mapping is adequate. // static_assert( std::ratio_greater_equal -#include // strtoul() +#include // timeval +#include + +#include // ratio_greater_equal +#include +#include // strtoul() #include +#include +#include // static_assert +#include // error_code, generic_category() #include #include @@ -501,10 +509,56 @@ handle (request& rq, response& rs) } // Given that the submission data is now successfully persisted we are no - // longer in charge of removing it, even in case of a subsequent error. + // longer in charge of removing it, except for the cases when the submission + // handler terminates with an error (see below for details). // tdr.cancel (); + // If the handler terminates with non-zero exit status or specifies 5XX + // (HTTP server error) submission result manifest status value, then we + // stash the submission data directory for troubleshooting. Otherwise, if + // it's the 4XX (HTTP client error) status value, then we remove the + // directory. + // + // Note that leaving the directory in place in case of a submission error + // would have prevent the user from re-submitting until we research the + // issue and manually remove the directory. + // + auto stash_submit_dir = [&dd, error] () + { + try + { + if (!dir_exists (dd)) + return; + + for (size_t n (1); true; ++n) // Eventually we should find the free one. + { + string ext ('.' + to_string (n)); + dir_path d (dd + ext); + + if (!dir_exists (d)) + try + { + mvdir (dd, d); + break; + } + catch (const system_error& e) + { + int ec (e.code ().value ()); + if (ec != ENOTEMPTY && ec != EEXIST) // Note: there can be a race. + throw; + } + } + } + catch (const system_error& e) + { + // Not much we can do here. Let's just log the issue and bail out + // leaving the directory in place. + // + error << "unable to rename directory '" << dd << "': " << e; + } + }; + auto print_args = [&trace, this] (const char* args[], size_t n) { l2 ([&]{trace << process_args {args, n};}); @@ -518,13 +572,44 @@ handle (request& rq, response& rs) // containing at least the status value. Thus, an empty cache indicates that // the handler is not configured. // - status_code sc; + status_code sc (200); vector rvs; if (options_->submit_handler_specified ()) { + // For the sake of the documentation we will call the handler's normal + // exit with 0 code "successful termination". + // + // To make sure the handler process execution doesn't exceed the specified + // timeout we set the non-blocking mode for the process stdout-reading + // stream, try to read from it with the 10 milliseconds timeout and check + // the process execution time between the reads. We then kill the process + // if the execution time is exceeded. + // + using namespace chrono; + + using time_point = system_clock::time_point; + using duration = system_clock::duration; + + // Make sure that the system clock has at least milliseconds resolution. + // + static_assert( + ratio_greater_equal::value, + "The system clock resolution is too low"); + + optional timeout; + + if (options_->submit_handler_timeout_specified ()) + timeout = milliseconds (options_->submit_handler_timeout () * 1000); + const path& handler (options_->submit_handler ()); + // Note that due to the non-blocking mode we cannot just pass the stream + // to the manifest parser constructor. So we buffer the data in the string + // stream and then parse that. + // + stringstream ss; + for (;;) // Breakout loop. try { @@ -542,86 +627,177 @@ handle (request& rq, response& rs) dd)); pipe.out.close (); - try + auto kill = [&pr, &warn, &handler, &ac] () { - ifdstream is (move (pipe.in)); - - // Parse and verify the manifest. Obtain the HTTP status code (must go - // first) and cache it for the subsequent responding to the client. - // - parser p (is, "handler"); - manifest_name_value nv (p.next ()); - - auto bad_value ([&p, &nv] (const string& d) { - throw parsing (p.name (), nv.value_line, nv.value_column, d);}); - - if (nv.empty ()) - bad_value ("empty manifest"); - - const string& n (nv.name); - const string& v (nv.value); - - // The format version pair is verified by the parser. - // - assert (n.empty () && v == "1"); - - // Cache the format version pair. - // - rvs.push_back (move (nv)); - - // Get and verify the HTTP status. - // - nv = p.next (); - if (n != "status") - bad_value ("no status specified"); - - char* e (nullptr); - unsigned long c (strtoul (v.c_str (), &e, 10)); // Can't throw. - - assert (e != nullptr); - - if (!(*e == '\0' && c >= 100 && c < 600)) - bad_value ("invalid http status '" + v + "'"); - - // Cache the HTTP status. + // We may still end up well (see below), thus this is a warning. // - sc = static_cast (c); - rvs.push_back (move (nv)); + warn << "ref " << ac << ": process " << handler + << " execution timeout expired"; - // Cache the remaining name/value pairs. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - rvs.push_back (move (nv)); + pr.kill (); + }; - // Cache end of manifest. + try + { + ifdstream is (move (pipe.in), fdstream_mode::non_blocking); + + const size_t nbuf (8192); + char buf[nbuf]; + + while (is.is_open ()) + { + time_point start; + milliseconds wd (10); // Max time to wait for the data portion. + + if (timeout) + { + start = system_clock::now (); + + if (*timeout < wd) + wd = *timeout; + } + + timeval tm {wd.count () / 1000 /* seconds */, + wd.count () % 1000 * 1000 /* microseconds */}; + + fd_set rd; + FD_ZERO (&rd); + FD_SET (is.fd (), &rd); + + int r (select (is.fd () + 1, &rd, nullptr, nullptr, &tm)); + + if (r == -1) + { + // Don't fail if the select() call was interrupted by the signal. + // + if (errno != EINTR) + throw io_error ("select failed", + error_code (errno, generic_category ())); + } + else if (r != 0) // Is data available? + { + assert (FD_ISSET (is.fd (), &rd)); + + // The only leagal way to read from non-blocking ifdstream. + // + streamsize n (is.readsome (buf, nbuf)); + + // Close the stream (and bail out) if the end of the data is + // reached. Otherwise cache the read data. + // + if (is.eof ()) + is.close (); + else + { + // The data must be available. + // + // Note that we could keep reading until the readsome() call + // returns 0. However, this way we could potentially exceed the + // timeout significantly for some broken handler that floods us + // with data. So instead, we will be checking the process + // execution time after every data chunk read. + // + assert (n != 0); + + ss.write (buf, n); + } + } + else // Timeout occured. + { + // Normally, we don't expect timeout to occur on the pipe read + // operation if the process has terminated successfully, as all its + // output must already be buffered (including eof). However, there + // can be some still running handler's child that has inherited + // the parent's stdout. In this case we assume that we have read + // all the handler's output, close the stream, log the warning and + // bail out. + // + if (pr.exit) + { + // We keep reading only upon successful handler termination. + // + assert (*pr.exit); + + is.close (); + + warn << "ref " << ac << ": process " << handler + << " stdout is not closed after termination (possibly " + << "handler's child still running)"; + } + } + + if (timeout) + { + time_point now (system_clock::now ()); + + // Assume we have waited the full amount if the time adjustment is + // detected. + // + duration d (now > start ? now - start : wd); + + // If the timeout is not fully exhausted, then decrement it and + // try to read some more data from the handler' stdout. Otherwise, + // kill the process, if not done yet. + // + // Note that it may happen that we are killing an already + // terminated process, in which case kill() just sets the process + // exit information. On the other hand it's guaranteed that the + // process is terminated after the kill() call, and so the pipe is + // presumably closed on the write end (see above for details). + // Thus, if the process terminated successfully, we will continue + // reading until eof is reached or read timeout occurred. Yes, it + // may happen that we end up with a successful submission even + // with the kill. + // + if (*timeout > d) + *timeout -= duration_cast (d); + else if (!pr.exit) + { + kill (); + + assert (pr.exit); + + // Close the stream (and bail out) if the process hasn't + // terminate successfully. + // + if (!*pr.exit) + is.close (); + + *timeout = milliseconds::zero (); + } + } + } + + assert (!is.is_open ()); + + if (!timeout) + pr.wait (); + + // If the process is not terminated yet, then wait for its termination + // for the remaining time. Kill it if the timeout has been exceeded + // and the process still hasn't terminate. // - rvs.push_back (move (nv)); + else if (!pr.exit && !pr.timed_wait (*timeout)) + kill (); - is.close (); + assert (pr.exit); // The process must finally be terminated. - if (pr.wait ()) + if (*pr.exit) break; // Get out of the breakout loop. - assert (pr.exit); - error << "process " << handler << " " << *pr.exit; - - // Fall through. - } - catch (const parsing& e) - { - if (pr.wait ()) - error << "unable to parse handler's output: " << e; + error << "ref " << ac << ": process " << handler << " " << *pr.exit; // Fall through. } catch (const io_error& e) { if (pr.wait ()) - error << "unable to read handler's output: " << e; + error << "ref " << ac << ": unable to read handler's output: " << e; // Fall through. } + stash_submit_dir (); return respond_error (); } // Handle process_error and io_error (both derive from system_error). @@ -629,6 +805,73 @@ handle (request& rq, response& rs) catch (const system_error& e) { error << "unable to execute '" << handler << "': " << e; + + stash_submit_dir (); + return respond_error (); + } + + try + { + // Parse and verify the manifest. Obtain the HTTP status code (must go + // first) and cache it for the subsequent response to the client. + // + parser p (ss, "handler"); + manifest_name_value nv (p.next ()); + + auto bad_value ([&p, &nv] (const string& d) { + throw parsing (p.name (), nv.value_line, nv.value_column, d);}); + + if (nv.empty ()) + bad_value ("empty manifest"); + + const string& n (nv.name); + const string& v (nv.value); + + // The format version pair is verified by the parser. + // + assert (n.empty () && v == "1"); + + // Cache the format version pair. + // + rvs.push_back (move (nv)); + + // Get and verify the HTTP status. + // + nv = p.next (); + if (n != "status") + bad_value ("no status specified"); + + char* e (nullptr); + unsigned long c (strtoul (v.c_str (), &e, 10)); // Can't throw. + + assert (e != nullptr); + + if (!(*e == '\0' && c >= 100 && c < 600)) + bad_value ("invalid HTTP status '" + v + "'"); + + // Cache the HTTP status. + // + sc = static_cast (c); + rvs.push_back (move (nv)); + + // Cache the remaining name/value pairs. + // + for (nv = p.next (); !nv.empty (); nv = p.next ()) + rvs.push_back (move (nv)); + + // Cache end of manifest. + // + rvs.push_back (move (nv)); + } + catch (const parsing& e) + { + error << "ref " << ac << ": unable to parse handler's output: " << e; + + // It appears the handler had misbehaved, so let's stash the submission + // directory for troubleshooting. + // + stash_submit_dir (); + return respond_error (); } } @@ -637,7 +880,7 @@ handle (request& rq, response& rs) // serialization error log the error description and return false, on the // stream error pass through the io_error exception, otherwise return true. // - auto rsm = [&rvs, &error] (ostream& os) -> bool + auto rsm = [&rvs, &error, &ac] (ostream& os) -> bool { assert (!rvs.empty ()); @@ -651,30 +894,52 @@ handle (request& rq, response& rs) } catch (const serialization& e) { - error << "unable to serialize handler's output: " << e; + error << "ref " << ac << ": unable to serialize handler's output: " << e; return false; } }; - // Save the result manifest, if generated, into the submission directory - // if it still exists (note that the handler could move or remove it). + // If the submission data directory still exists then perform an appropriate + // action on it, depending on the submission result status. Note that the + // handler could move or remove the directory. // - path rsf (dd / "result.manifest"); - - if (!rvs.empty () && dir_exists (dd)) - try + if (dir_exists (dd)) { - ofdstream os (rsf); - bool r (rsm (os)); - os.close (); + // Remove the directory if the client error is detected. + // + if (sc >= 400 && sc < 500) + rmdir_r (dd); - if (!r) - return respond_error (); // The error description is already logged. - } - catch (const io_error& e) - { - error << "unable to write to '" << rsf << "': " << e; - return respond_error (); + // Otherwise, save the result manifest, if generated, into the directory. + // Also stash the directory for troubleshooting in case of the server + // error. + // + else + { + path rsf (dd / "result.manifest"); + + if (!rvs.empty ()) + try + { + ofdstream os (rsf); + + // Not being able to stash the result manifest is not a reason to + // claim the submission failed. The error is logged nevertheless. + // + rsm (os); + + os.close (); + } + catch (const io_error& e) + { + // Not fatal (see above). + // + error << "unable to write to '" << rsf << "': " << e; + } + + if (sc >= 500 && sc < 600) + stash_submit_dir (); + } } // Send email, if configured, and the submission is not simulated. diff --git a/mod/options.cli b/mod/options.cli index 992ffc4..59aceb6 100644 --- a/mod/options.cli +++ b/mod/options.cli @@ -458,6 +458,14 @@ namespace brep (see \cb{submit-handler} for details). Repeat this option to specify multiple arguments." } + + size_t submit-handler-timeout + { + "", + "The handler program timeout in seconds. If specified and the handler + does not exit in the alloted time, then it is killed and its + termination is treated as abnormal." + } }; class repository_root: handler diff --git a/tests/submit/0f6b1460b3ec/libhello-0.1.0.tar.gz b/tests/submit/0f6b1460b3ec/libhello-0.1.0.tar.gz new file mode 100644 index 0000000..604a536 Binary files /dev/null and b/tests/submit/0f6b1460b3ec/libhello-0.1.0.tar.gz differ diff --git a/tests/submit/0f6b1460b3ec/package.manifest b/tests/submit/0f6b1460b3ec/package.manifest new file mode 100644 index 0000000..6fc36e7 --- /dev/null +++ b/tests/submit/0f6b1460b3ec/package.manifest @@ -0,0 +1,10 @@ +: 1 +name: libhello +version: 0.1.0 +project: hello +summary: hello library +license: TODO +url: https://example.org/hello +email: user@example.org +depends: * build2 >= 0.8.0- +depends: * bpkg >= 0.8.0- diff --git a/tests/submit/0f6b1460b3ec/request.manifest b/tests/submit/0f6b1460b3ec/request.manifest new file mode 100644 index 0000000..1173210 --- /dev/null +++ b/tests/submit/0f6b1460b3ec/request.manifest @@ -0,0 +1,10 @@ +: 1 +archive: libhello-0.1.0.tar.gz +sha256sum: 0f6b1460b3ec479499ae26841322af7a8a4312fcc5a6e890f3dbba91a83b38cc +timestamp: 2018-08-20T04:57:05Z +client-ip: fe80::56e1:adff:fe83:82f5 +user-agent: bdep/0.8.0-a.0.20180815130917 (GNU/Linux; +https://build2.org)\ + libbpkg/0.8.0-a.0.0f50af28d1cf libbutl/0.8.0-a.0.3e0db12932d5 curl +section: alpha +email: user@example.org +control: http://example.org/hello.git diff --git a/tests/submit/0f6b1460b3ec/result.manifest b/tests/submit/0f6b1460b3ec/result.manifest new file mode 100644 index 0000000..93a3555 --- /dev/null +++ b/tests/submit/0f6b1460b3ec/result.manifest @@ -0,0 +1,4 @@ +: 1 +status: 200 +message: libhello/0.1.0 submission is queued +reference: 0f6b1460b3ec diff --git a/tests/submit/README b/tests/submit/README new file mode 100644 index 0000000..7286c83 --- /dev/null +++ b/tests/submit/README @@ -0,0 +1,30 @@ +Prepare the test data with the following instructions. + +In an empty directory run: + +$ bdep new -t empty -C @cfg hello +$ BDEP_EMAIL=user@example.org bdep new --package -t lib libhello -d hello +$ bdep init -d hello/libhello + +Edit hello/libhello/manifest setting version to 0.1.0. + +$ mkdir hello.git +$ git -C hello.git/ init --bare + +$ git -C hello remote add origin "$(pwd)/hello.git" +$ git -C hello add '*' +$ git -C hello commit -m "Create" +$ git -C hello push --set-upstream origin master + +tar cf hello.tar.gz hello.git/ + +Move the archive into brep/tests/submit/ directory. + +Locally run brep server configured to use submit-dir handler. + +$ bdep publish --control http://example.org/hello.git \ + --email user@example.org --repository http://localhost/pkg --yes \ + -d hello + +Replace the submission data directory in brep/tests/submit/ with the one +produced with the above command. diff --git a/tests/submit/buildfile b/tests/submit/buildfile new file mode 100644 index 0000000..6606153 --- /dev/null +++ b/tests/submit/buildfile @@ -0,0 +1,16 @@ +# file : tests/submit/buildfile +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +define common: file +common{*}: extension = test + +dir = ../../brep/submit/ + +commons = data + +./: test{* -{$commons}} common{$commons} {*/ -test/}{**} \ + $dir/exe{brep-submit-dir} $dir/exe{brep-submit-git} + +test{submit-dir}@./: test = $out_base/$dir/brep-submit-dir +test{submit-git}@./: test = $out_base/$dir/brep-submit-git diff --git a/tests/submit/data.test b/tests/submit/data.test new file mode 100644 index 0000000..938d6b8 --- /dev/null +++ b/tests/submit/data.test @@ -0,0 +1,35 @@ +# file : tests/submit/data.test +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Pre-created submission data directory that will be copied by subsequent +# tests and scope setup commands. The common approach will be that group +# scopes copy and modify the parent scope submission directory as required by +# the nested tests and scopes. Tests will also clone the parent scope +# submission data directory to optionally modify it, use and cleanup at the +# end. Note that configuration can not be shared between multiple submission +# handler processes. Also we need to make sure that submission data +# directories are not cloned while being used by submission handler scripts. +# +data_dir = $regex.replace($path_search('*/request.manifest', $src_base), \ + '(.*)/.*', \ + '\1') + +checksum = "$data_dir" + +# Copy the original submission data directory to the root scope. +# ++cp -r $src_base/$data_dir ./ + +root_data_dir = $~/$data_dir + +# The most commonly used submission data directory cloning command that copies +# it from the parent scope working directory. +# +clone_data = cp --no-cleanup -r ../$data_dir ./ +clone_data_clean = cp --no-cleanup -r ../$data_dir ./ &$data_dir/*** + +# Clones the original submission data directory. +# +clone_root_data = cp --no-cleanup -r $root_data_dir ./ +clone_root_data_clean = cp --no-cleanup -r $root_data_dir ./ &$data_dir/*** diff --git a/tests/submit/hello.tar.gz b/tests/submit/hello.tar.gz new file mode 100644 index 0000000..67baca7 Binary files /dev/null and b/tests/submit/hello.tar.gz differ diff --git a/tests/submit/submit-dir.test b/tests/submit/submit-dir.test new file mode 100644 index 0000000..97f7edd --- /dev/null +++ b/tests/submit/submit-dir.test @@ -0,0 +1,90 @@ +# file : tests/submit/submit-dir.test +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include data.test + +: args +{ + : none + : + $* 2>>~%EOE% != 0 + %\[.+\] \[brep:error\] \[ref \] \[brep-submit-dir\]: usage: .+brep-submit-dir % + EOE + + : not-exist + : + $* $~/dir 2>>~%EOE% != 0 + %\[.+\] \[brep:error\] \[ref dir\] \[brep-submit-dir\]: '.+dir' does not exist or is not a directory% + EOE +} + +: success +: +{ + test.arguments += $checksum + + : simulate + : + { + $clone_root_data; + + echo "simulate: success" >+$checksum/request.manifest; + + $* >>"EOO"; + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + + test -d $checksum != 0 + } + + : for-real + : + { + $clone_root_data_clean; + + $* >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } +} + +: failure +: +{ + test.arguments += $checksum + + : bad-archive + : + { + $clone_root_data_clean; + + echo "junk" >=$checksum/libhello-0.1.0.tar.gz; + + $* >>EOO + : 1 + status: 400 + message: archive is not a valid package (run bpkg pkg-verify for details) + EOO + } + + : bad-simulate + : + { + $clone_root_data_clean; + + echo "simulate: fly" >+$checksum/request.manifest; + + $* >>"EOO" + : 1 + status: 400 + message: unrecognized simulation outcome 'fly' + EOO + } +} diff --git a/tests/submit/submit-git.test b/tests/submit/submit-git.test new file mode 100644 index 0000000..cf499b9 --- /dev/null +++ b/tests/submit/submit-git.test @@ -0,0 +1,765 @@ +# file : tests/submit/submit-git.test +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include data.test + +# Prior to running testscript with -v for debugging purposes assign true to +# the verbosity variable in the brep/submit/submit-git.in handler script and +# uncomment the following line. +# +# test.redirects += 2>! + +g = git 2>! >&2 + +# Create and clone the reference repository. +# +root_ref = $~/ref.git +root_ref_dir = $~/ref + +clone_root_ref = cp --no-cleanup -r $root_ref ./ &ref.git/*** + ++mkdir --no-cleanup $root_ref ++$g -C $root_ref init --bare &ref.git/*** + ++$g clone $root_ref $root_ref_dir &ref/*** + ++cat <=$root_ref_dir/submit.config.bash + sections[alpha]=1/alpha + sections[beta]=1/beta + sections[stable]=1/testing + + owners=owners + EOI + ++$g -C $root_ref_dir add '*' ++$g -C $root_ref_dir commit -m 'Add submit.config.bash' ++$g -C $root_ref_dir push + +# Create the target repository. +# +root_tgt = $~/tgt.git +root_tgt_url = "file:///$~/tgt.git" + ++cp -r $root_ref $root_tgt + +clone_root_tgt = cp --no-cleanup -r $root_tgt ./ &tgt.git/*** + +# Extract the package repository. +# ++tar -C $~ -xf $src_base/hello.tar.gz &hello.git/*** + +# Adjust the request manifest control value to point to the package repository. +# +prj_ctl="file://$~" +pkg_ctl="$prj_ctl/hello.git" + ++sed -i -e "s%^\(control:\) .+\$%\\1 $pkg_ctl%" $data_dir/request.manifest + +: args +{ + : none + : + $* 2>>~%EOE% != 0 + %\[.+\] \[brep:error\] \[ref \] \[brep-submit-git\]: usage: .+brep-submit-git \[\] % + EOE + + : dir-only + : + $* $~/dir 2>>~%EOE% != 0 + %\[.+\] \[brep:error\] \[ref dir\] \[brep-submit-git\]: usage: .+brep-submit-git \[\] % + EOE + + : ref-not-exist + : + $* "$root_tgt_url" ref $~/dir 2>>~%EOE% != 0 + %\[.+\] \[brep:error\] \[ref dir\] \[brep-submit-git\]: 'ref' does not exist or is not a directory% + EOE +} + +: success +: +{ + : ref-unknown-tgt-aquire-prj-pkg + : + : Test that on the first package submission the project and package names + : ownership is successfully aquired. Authentication is enabled on both the + : reference and target repos. + : + { + $clone_root_data; + + $clone_root_tgt; + tgt_url = "file:///$~/tgt.git"; + + $* "$tgt_url" $root_ref_dir $data_dir >>"EOO"; + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + + # Check that the ownership information and the package are commited into + # the target repository. + # + $g clone "$tgt_url" &tgt/***; + + # Note that some manifest values may well wrap over several lines. + # + cat tgt/owners/hello/project-owner.manifest >>~%EOO%; + : 1 + name: hello + email: user@example.org + %control: file:///.+% + %.* + EOO + + cat tgt/owners/hello/libhello/package-owner.manifest >>~%EOO%; + : 1 + name: libhello + email: user@example.org + %control: file:///.+% + %.* + EOO + + test -f tgt/1/alpha/hello/libhello-0.1.0.tar.gz; + + git -C tgt log -1 >>~%EOO% + %commit .+% + %Author: .+% + %Date: .+% + + Add libhello/0.1.0 to 1/alpha/hello + % % + : 1 + archive: libhello-0.1.0.tar.gz + % sha256sum: .{64}% + % timestamp: ....-..-..T..:..:..Z% + % client-ip: .+% + % user-agent: bdep/.+% + %. + section: alpha + email: user@example.org + % control: file:///.+% + %.* + EOO + } + : ref-disabled-tgt-aquire-prj-pkg + : + : Test that on the first package submit the project and package names + : ownership is successfully aquired. Authentication is disabled for the + : reference repo. + : + { + $clone_root_data; + + $clone_root_ref; + $g clone ref.git &ref/***; + + cat <=ref/submit.config.bash; + sections[alpha]=1/alpha + sections[beta]=1/beta + sections[stable]=1/testing + + # owners=owners + EOI + + $g -C ref commit -am 'Disable ownership'; + $g -C ref push; + + $clone_root_tgt; + + $* "file:///$~/tgt.git" ref $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : ref-absent-tgt-aquire-prj-pkg + : + : Test that on the first package submit the project and package names + : ownership is successfully aquired. Reference repo is absent. + : + { + $clone_root_data; + $clone_root_tgt; + + $* "file:///$~/tgt.git" $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : ref-unknown-tgt-auth-prj-pkg + : + : Test that the project and package ownership is authenticated by the target + : repository. + : + { + $clone_root_data; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + mkdir -p tgt/owners/hello/libhello; + + cat <<"EOI" >=tgt/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl/ + EOI + + cat <<"EOI" >=tgt/owners/hello/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $pkg_ctl + EOI + + $g -C tgt add owners; + $g -C tgt commit -m 'Add ownership info'; + $g -C tgt push; + + $* "file:///$~/tgt.git" $root_ref_dir $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : ref-auth-prj-tgt-auth-pkg + : + : Test that the project ownersip is authenticated by the reference + : repository and the package ownersip is authenticated by the target + : repository. + : + { + $clone_root_data; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hello; + + cat <<"EOI" >=ref/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl + EOI + + $g -C ref add owners; + $g -C ref commit -m 'Add ownership info'; + $g -C ref push; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + mkdir -p tgt/owners/hello/libhello; + + cat <<"EOI" >=tgt/owners/hello/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $pkg_ctl + EOI + + $g -C tgt add owners; + $g -C tgt commit -m 'Add ownership info'; + $g -C tgt push; + + $* "file:///$~/tgt.git" ref $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : ref-auth-prj-pkg + : + : Test that the project and package ownership is authenticated by the + : reference repository. + : + { + $clone_root_data; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hello/libhello; + + cat <<"EOI" >=ref/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl + EOI + + cat <<"EOI" >=ref/owners/hello/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $pkg_ctl + EOI + + $g -C ref add owners; + $g -C ref commit -m 'Add ownership info'; + $g -C ref push; + + $clone_root_tgt; + + $* "file:///$~/tgt.git" ref $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : ref-auth-prj-tgt-aquire-pkg + : + : Test that the project ownersip is authenticated by the reference + : repository and the package ownersip is aquired. + : + { + $clone_root_data; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hello/libhello; + + cat <<"EOI" >=ref/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl + EOI + + $g -C ref add owners; + $g -C ref commit -m 'Add ownership info'; + $g -C ref push; + + $clone_root_tgt; + + $* "file:///$~/tgt.git" ref $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : ref-absent-tgt-disabled + : + : Test the package ownership authentication when reference is unspecified and + : the target ownership handling is disabled. + : + { + $clone_root_data; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + cat <=tgt/submit.config.bash; + sections[alpha]=1/alpha + sections[beta]=1/beta + sections[stable]=1/testing + + # owners=owners + EOI + + $g -C tgt commit -am 'Disable ownership'; + $g -C tgt push; + + $* "file:///$~/tgt.git" $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } + + : section-fallback + : + { + $clone_root_data; + sed -i -e "s%^\(section:\) .+\$%\\1 delta%" $data_dir/request.manifest; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + cat <=tgt/submit.config.bash; + sections[alpha]=1/alpha + sections[beta]=1/beta + sections[stable]=1/testing + sections['*']=1/junk + + owners=owners + EOI + + $g -C tgt commit -am "Add section name fallback"; + $g -C tgt push; + + $* "file:///$~/tgt.git" $root_ref_dir $data_dir >>"EOO" + : 1 + status: 200 + message: libhello/0.1.0 submission is queued + reference: $checksum + EOO + } +} + +: failure +: +{ + : ref-dup-pkg + : + : Test the duplicate submission due presence of the package archive in the + : reference repo. + : + { + $clone_root_data_clean; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/1/alpha/hello; + cp $data_dir/libhello-0.1.0.tar.gz ref/1/alpha/hello/; + + $g -C ref add 1/; + $g -C ref commit -m 'Add libhello-0.1.0.tar.gz'; + $g -C ref push; + + $* "$root_tgt_url" $~/ref $data_dir >>EOO + : 1 + status: 422 + message: duplicate submission + EOO + } + + : ref-used-pkg + : + : Test the package ownership authentication failure using the reference + : repo. The package name is already used in other project. + : + { + $clone_root_data_clean; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hi/libhello; + + cat <<"EOI" >=ref/owners/hi/project-owner.manifest; + : 1 + name: hi + email: user@example.org + control: $prj_ctl + EOI + + cat <<"EOI" >=ref/owners/hi/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $prj_ctl/foo + EOI + + $g -C ref add owners; + $g -C ref commit -m 'Add ownership info'; + $g -C ref push; + + $* "$root_tgt_url" $~/ref $data_dir >>EOO + : 1 + status: 401 + message: package owner authentication failed + EOO + } + + : ref-auth-prj + : + : Test the project ownership authentication failure using the reference + : repo. + : + { + $clone_root_data_clean; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hello; + cat <=ref/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: https://example.com/foo + EOI + + $g -C ref add owners/hello/project-owner.manifest; + $g -C ref commit -m 'Add project ownership info'; + $g -C ref push; + + $* "$root_tgt_url" $~/ref $data_dir >>EOO + : 1 + status: 401 + message: project owner authentication failed + EOO + } + + : ref-auth-pkg + : + : Test the package ownership authentication failure using the reference + : repo. + : + { + $clone_root_data_clean; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hello/libhello; + + cat <<"EOI" >=ref/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl + EOI + + cat <<"EOI" >=ref/owners/hello/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $prj_ctl/foo + EOI + + $g -C ref add owners; + $g -C ref commit -m 'Add ownership info'; + $g -C ref push; + + $* "$root_tgt_url" $~/ref $data_dir >>EOO + : 1 + status: 401 + message: package owner authentication failed + EOO + } + + : ref-absent-tgt-dup-pkg + : + : Test the duplicate submission due presence of the package archive in the + : target repo. + : + { + $clone_root_data_clean; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + mkdir -p tgt/1/alpha/hello; + cp $data_dir/libhello-0.1.0.tar.gz tgt/1/alpha/hello/; + + $g -C tgt add 1/; + $g -C tgt commit -m 'Add libhello-0.1.0.tar.gz'; + $g -C tgt push; + + $* "file:///$~/tgt.git" $data_dir >>EOO + : 1 + status: 422 + message: duplicate submission + EOO + } + + : ref-absent-tgt-auth-pkg + : + : Test the package ownership authentication failure using the target repo. + : + { + $clone_root_data_clean; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + mkdir -p tgt/owners/hello/libhello; + + cat <<"EOI" >=tgt/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl + EOI + + cat <<"EOI" >=tgt/owners/hello/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $prj_ctl/foo + EOI + + $g -C tgt add owners; + $g -C tgt commit -m 'Add ownership info'; + $g -C tgt push; + + $* "file:///$~/tgt.git" $data_dir >>EOO + : 1 + status: 401 + message: package owner authentication failed + EOO + } + + : ref-unknown-tgt-disabled + : + : Test the project ownership authentication failure when no project + : ownership information is present in the reference and the target ownership + : handling is disabled. + : + { + $clone_root_data_clean; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + cat <=tgt/submit.config.bash; + sections[alpha]=1/alpha + sections[beta]=1/beta + sections[stable]=1/testing + + # owners=owners + EOI + + $g -C tgt commit -am 'Disable ownership'; + $g -C tgt push; + + $* "file:///$~/tgt.git" $root_ref_dir $data_dir >>EOO + : 1 + status: 401 + message: project owner authentication failed + EOO + } + + : ref-prj-tgt-disabled + : + : Test the project ownership authentication failure when no package + : ownership information is present in the reference and the target ownership + : handling is disabled. + : + { + $clone_root_data_clean; + + $clone_root_ref; + $g clone ref.git &ref/***; + + mkdir -p ref/owners/hello/libhello; + + cat <<"EOI" >=ref/owners/hello/project-owner.manifest; + : 1 + name: hello + email: user@example.org + control: $prj_ctl + EOI + + $g -C ref add owners; + $g -C ref commit -m 'Add project ownership info'; + $g -C ref push; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + cat <=tgt/submit.config.bash; + sections[alpha]=1/alpha + sections[beta]=1/beta + sections[stable]=1/testing + + # owners=owners + EOI + + $g -C tgt commit -am 'Disable ownership'; + $g -C tgt push; + + $* "file:///$~/tgt.git" ref $data_dir >>EOO + : 1 + status: 401 + message: package owner authentication failed + EOO + } + + : ref-absent-tgt-used-pkg + : + : Test the package ownership authentication failure using the target repo. + : The package name is already used in other project. + : + { + $clone_root_data_clean; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + mkdir -p tgt/owners/hi/libhello; + + cat <<"EOI" >=tgt/owners/hi/project-owner.manifest; + : 1 + name: hi + email: user@example.org + control: $prj_ctl + EOI + + cat <<"EOI" >=tgt/owners/hi/libhello/package-owner.manifest; + : 1 + name: libhello + email: user@example.org + control: $prj_ctl/foo + EOI + + $g -C tgt add owners; + $g -C tgt commit -m 'Add ownership info'; + $g -C tgt push; + + $* "file:///$~/tgt.git" $data_dir >>EOO + : 1 + status: 401 + message: package owner authentication failed + EOO + } + + : authorization + : + : Test the package submission authorization failure due to the archive + : abbreviated checksum mismatch. + : + { + $clone_root_data_clean; + + sed -i -e "s%^\(sha256sum:\) .+\$%\\1 59941e842667%" \ + $data_dir/request.manifest; + + $clone_root_tgt; + $g clone tgt.git &tgt/***; + + $* "file:///$~/tgt.git" $data_dir >>EOO + : 1 + status: 401 + message: package publishing authorization failed + EOO + } + + : section-unknown + : + { + $clone_root_data_clean; + sed -i -e "s%^\(section:\) .+\$%\\1 delta%" $data_dir/request.manifest; + + $clone_root_tgt; + + $* "file:///$~/tgt.git" $root_ref_dir $data_dir >>"EOO" + : 1 + status: 400 + message: unrecognized section 'delta' + EOO + } +} -- cgit v1.1