diff --git a/bin/compose-core b/bin/compose-core index 7e91366..36d7984 100755 --- a/bin/compose-core +++ b/bin/compose-core @@ -813,10 +813,6 @@ get_docker_compose_links() { fi master_service=$(get_top_master_service_for_service "$service") || return 1 - ## XXXvlab: yuck, this make the assumption that next function is cached, - ## and leverage the fact that the result is stored in a file. All this only - ## to catch a failure of ``get_compose_relations``, that would go out silently. - get_compose_relations "$service" >/dev/null || return 1 ## fetch cache and fail if necessary deps=() while read-0 _relation_name target_service _relation_config tech_dep; do master_target_service="$(get_top_master_service_for_service "$target_service")" || return 1 @@ -832,7 +828,7 @@ get_docker_compose_links() { ## as there's a circular dependency issue. We don't really want the full feature ## of depends_on, but just to add it as targets when doing an 'up' # deps+=("$(echo -en "$master_service:\n depends_on:\n - $master_target_service")") - done < <(get_compose_relations "$service") + done < <(get_service_relations "$service") merge_yaml_str "${deps[@]}" | tee "$cache_file" || return 1 if [ "${PIPESTATUS[0]}" != 0 ]; then rm "$cache_file" @@ -1204,7 +1200,7 @@ get_service_deps() { fi ( set -o pipefail - get_compose_relations "$service" | \ + get_service_relations "$service" | \ while read-0 relation_name target_service _relation_config tech_dep; do echo "$target_service" done | tee "$cache_file" @@ -1692,6 +1688,400 @@ get_compose_relations () { export -f get_compose_relations +get_service_relations () { + local service="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$1" \ + s rn ts rc td + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + if [ -z "$ALL_RELATIONS" ]; then + err "Can't access global \$ALL_RELATIONS" + return 1 + fi + + while read-0 s rn ts rc td; do + [[ "$s" == "$service" ]] || continue + printf "%s\0" "$rn" "$ts" "$rc" "$td" + done < <(cat "$ALL_RELATIONS") > "$cache_file" + + if [ "$?" != 0 ]; then + rm -f "$cache_file" ## no cache + return 1 + fi + cat "$cache_file" +} +export -f get_service_relations + + +get_service_relation() { + local service="$1" relation="$2" cache_file="$state_tmpdir/$FUNCNAME.cache.$1" \ + rn ts rc td + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + while read-0 rn ts rc td; do + [ "$relation" == "$rn" ] && { + printf "%s\0" "$ts" "$rc" "$td" + break + } + done < <(get_service_relations "$service") > "$cache_file" + if [ "$?" != 0 ]; then + rm -f "$cache_file" ## no cache + return 1 + fi + cat "$cache_file" +} + + +_get_charm_metadata_uses() { + local metadata="$1" cache_file="$CACHEDIR/$FUNCNAME.cache.$(printf "%s\0" "$@" | md5_compat)" + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" || return 1 + return 0 + fi + + printf "%s" "$metadata" | { shyaml key-values-0 uses 2>/dev/null || true; } | tee "$cache_file" +} +export -f _get_charm_metadata_uses + + +_get_service_metadata() { + local service="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$1" \ + charm + + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + charm="$(get_service_charm "$service")" || return 1 + charm.metadata "$charm" > "$cache_file" + if [ "$?" != 0 ]; then + rm -f "$cache_file" ## no cache + return 1 + fi + cat "$cache_file" +} +export -f _get_service_metadata + + +_get_service_uses() { + local service="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$1" \ + metadata + + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + metadata="$(_get_service_metadata "$service")" || return 1 + _get_charm_metadata_uses "$metadata" > "$cache_file" + if [ "$?" != 0 ]; then + rm -f "$cache_file" ## no cache + return 1 + fi + cat "$cache_file" +} +export -f _get_service_uses + + +_get_services_uses() { + local cache_file="$state_tmpdir/$FUNCNAME.cache.$(printf "%s\0" "$@" | md5_compat)" \ + service rn rd + + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + for service in "$@"; do + _get_service_uses "$service" | while read-0 rn rd; do + printf "%s\0" "$service" "$rn" "$rd" + done + [ "${PIPESTATUS[0]}" == 0 ] || { + return 1 + } + done > "${cache_file}.wip" + mv "${cache_file}"{.wip,} && + cat "$cache_file" || return 1 +} +export -f _get_services_uses + + +_get_services_provides() { + local cache_file="$state_tmpdir/$FUNCNAME.cache.$(printf "%s\0" "$@" | md5_compat)" \ + service rn rd + + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + ## YYY: replace the inner loop by a cached function + for service in "$@"; do + metadata="$(_get_service_metadata "$service")" || return 1 + + while read-0 rn rd; do + printf "%s\0" "$service" "$rn" "$rd" + done < <(printf "%s" "$metadata" | shyaml key-values-0 provides 2>/dev/null) + done > "$cache_file" + if [ "$?" != 0 ]; then + rm -f "$cache_file" ## no cache + return 1 + fi + cat "$cache_file" +} +export -f _get_services_provides + + +_get_services_providing() { + local cache_file="$state_tmpdir/$FUNCNAME.cache.$(printf "%s\0" "$@" | md5_compat)" \ + relation="$1" + shift ## services is "$@" + if [ -e "$cache_file" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "$cache_file" + return 0 + fi + + while read-0 service relation_name relation_def; do + [ "$relation_name" == "$relation" ] || continue + printf "%s\0" "$service" "$relation_def" + done < <(_get_services_provides "$@") > "$cache_file" + + if [ "$?" != 0 ]; then + rm -f "$cache_file" ## no cache + return 1 + fi + cat "$cache_file" +} +export -f _get_services_provides + + +get_all_relations () { + local cache_file="$state_tmpdir/$FUNCNAME.cache.$(printf "%s\0" "$@" | md5_compat)" \ + services + + if [ -e "${cache_file}" ]; then + #debug "$FUNCNAME: SESSION cache hit $1" + cat "${cache_file}" + return 0 + fi + + declare -A services + services_uses=() + ## XXXvlab: bwerk, leveraging cache to be able to get the errorlevel here. + _get_services_uses "$@" || return 1 + array_read-0 services_uses < <(_get_services_uses "$@") + services_provides=() + ## XXXvlab: bwerk, leveraging cache to be able to get the errorlevel here. + _get_services_provides "$@" || return 1 + array_read-0 services_provides < <(_get_services_provides "$@") + + for service in "$@"; do + services[$service]=1 + done + + all_services=("$@") + while [ "${#all_services[@]}" != 0 ]; do + array_pop all_services service + while read-0 relation_name ts relation_config tech_dep; do + printf "%s\0" "$service" "$relation_name" "$ts" "$relation_config" "$tech_dep" + + ## adding target services ? + [ "${services[$ts]}" ] && continue + array_read-0 services_uses < <(_get_services_uses "$ts") + all_services+=("$ts") + services[$ts]=1 + done < <(get_compose_relations "$service") + done > "${cache_file}.wip" + + while true; do + changed= + new_services_uses=() + summon=() + required=() + recommended=() + optional=() + while [ "${#services_uses[@]}" != 0 ]; do + service="${services_uses[0]}" + relation_name="${services_uses[1]}" + relation_def="${services_uses[2]}" + services_uses=("${services_uses[@]:3}") + + default_options=$(printf "%s" "$relation_def" | shyaml -y get-value "default-options" 2>/dev/null) + ## is this "use" declaration satisfied ? + found= + while read-0 s rn ts rc td; do + if [ -z "$found" -a "$service" == "$s" -a "$relation_name" == "$rn" ]; then + if [ "$default_options" ]; then + rc=$(merge_yaml_str "$default_options" "$rc") || return 1 + fi + found="$ts" + fi + printf "%s\0" "$s" "$rn" "$ts" "$rc" "$td" + done < "${cache_file}.wip" > "${cache_file}.wip.new" + mv "${cache_file}.wip.new" "${cache_file}.wip" + if [ "$found" ]; then ## this "use" declaration was satisfied + debug "${DARKYELLOW}$service${NORMAL} use declaration for relation " \ + "${DARKBLUE}$relation_name${NORMAL} is satisfied with ${DARKYELLOW}$found${NORMAL}" + continue + fi + + auto=$(echo "$relation_def" | shyaml get-value auto pair 2>/dev/null) + case "$auto" in + "pair") + service_list=() + array_read-0 service_list < <(array_keys_to_stdin services) + providers=() + array_read-0 providers providers_def < <(_get_services_providing "$relation_name" "${service_list[@]}") + if [ "${#providers[@]}" == 1 ]; then + ts="${providers[0]}" + rd_provider="${providers_def[0]}" + rc_provider=$(printf "%s" "$rd_provider" | shyaml -y get-value "default-options" 2>/dev/null) + ## YYYvlab: should be seen even in no debug mode no ? + debug "Auto-pairs ${DARKYELLOW}$service${NORMAL}" \ + "--${DARKBLUE}$relation_name${NORMAL}--> ${DARKYELLOW}$ts${NORMAL}" + rc=$(printf "%s" "$relation_def" | shyaml -y get-value "default-options" 2>/dev/null) + td=$(echo "$rd_provider" | shyaml get-value 'tech-dep' 2>/dev/null) + td=${td:-True} + rc=$(merge_yaml_str "$rc_provider" "$rc") || return 1 + printf "%s\0" "$service" "$relation_name" "$ts" "$rc" "$td" >> "${cache_file}.wip" + + ## Adding service + [ "${services[$ts]}" ] && continue + array_read-0 new_services_uses < <(_get_services_uses "$ts") + services[$ts]=1 + changed=1 + continue + elif [ "${#providers[@]}" -gt 1 ]; then + msg="" + warn "No auto-pairing ${DARKYELLOW}$service${NORMAL}" \ + "--${DARKBLUE}$relation_name${NORMAL}--> ($DARKYELLOW""${providers[@]}""$NORMAL)"\ + "(> 1 provider)." + continue + else + : ## Do nothing + fi + ;; + "summon") + summon+=("$service" "$relation_name" "$relation_def") + ;; + ""|null|disable|disabled) + : + ;; + *) + err "Invalid ${WHITE}auto${NORMAL} value '$auto'." + return 1 + ;; + esac + constraint=$(echo "$relation_def" | shyaml get-value constraint auto-pair 2>/dev/null) + case "$constraint" in + "required") + required+=("$service" "$relation_name" "$relation_def") + ;; + "recommended") + recommended+=("$service" "$relation_name" "$relation_def") + ;; + "optional") + optional+=("$service" "$relation_name" "$relation_def") + ;; + *) + err "Invalid ${WHITE}constraint${NORMAL} value '$contraint'." + return 1 + ;; + esac + new_services_uses+=("$service" "$relation_name" "$relation_def") ## re-queue it + done + services_uses=("${new_services_uses[@]}") + + if [ "$changed" ]; then + continue + fi + ## situation is stable + + if [ "${#summon[@]}" != 0 ]; then + die "Summon code not implemented yet" + continue + fi + [ "$NO_CONSTRAINT_CHECK" ] && break + if [ "${#required[@]}" != 0 ]; then + echo "$(_display_solves required)" | sed -r "s/^/${RED}||${NORMAL} /g" >&2 + err "Required relations not satisfied" + return 1 + fi + if [ "${#recommended[@]}" != 0 ]; then + ## make recommendation + echo "$(_display_solves recommended)" | sed -r "s/^/${YELLOW}||${NORMAL} /g" >&2 + fi + if [ "${#optional[@]}" != 0 ]; then + ## inform about options + echo "$(_display_solves optional)" | sed -r "s/^/${BLUE}||${NORMAL} /g" >&2 + fi + # if [ "${#required[@]}" != 0 ]; then + # err "Required relations not satisfied" + # return 1 + # fi + if [ "${#recommended[@]}" != 0 ]; then + warn "Recommended relations not satisfied" + fi + break + done + if [ "$?" != 0 ]; then + rm -f "${cache_file}"{,.wip,.wip.new} ## no cache + return 1 + fi + export ALL_RELATIONS="$cache_file" + mv "${cache_file}"{.wip,} + cat "$cache_file" +} +export -f get_all_relations + + +_display_solves() { + local array_name="$1" by_relation msg + ## inform about options + msg="" + declare -A by_relation + while read-0 service relation_name relation_def; do + solves=$(printf "%s" "$relation_def" | shyaml -y get-value solves 2>/dev/null); + auto=$(printf "%s" "$relation_def" | shyaml get-value auto 2>/dev/null); + if [ -z "$solves" ]; then + continue + fi + by_relation[$relation_name]+=$(printf "\n %s" "${DARKYELLOW}$service$NORMAL for:") + if [ "$auto" == "pair" ]; then + requirement="add provider in cluster to auto-pair" + else + requirement="add explicit relation" + fi + while read-0 name def; do + by_relation[$relation_name]+=$(printf "\n - ${DARKCYAN}%-15s${NORMAL} %s (%s)" "$name" "$def" "$requirement") + done < <(printf "%s" "$solves" | shyaml key-values-0) + done < <(array_values_to_stdin "$array_name") + + while read-0 relation_name message; do + msg+="$(printf "\n${DARKBLUE}%s$NORMAL provider is $array_name by%s" \ + "$relation_name" "$message" )" + done < <(array_kv_to_stdin by_relation) + + if [ "$msg" ]; then + printf "%s\n" "${msg:1}" + fi +} + + get_compose_relation_def() { local service="$1" relation="$2" relation_name target_service relation_config tech_dep while read-0 relation_name target_service relation_config tech_dep; do @@ -1736,7 +2126,7 @@ run_service_relations () { Wrap -d "Building $DARKYELLOW$subservice$NORMAL --$DARKBLUE$relation_name$NORMAL--> $DARKYELLOW$target_service$NORMAL" </dev/null)" == "container" ] && { + found="$relation_name" + break + } + done < <(_get_charm_metadata_uses "$metadata") + if [ -z "$found" ]; then + die "Charm $DARKPINK$charm$NORMAL is a subordinate but does not have any required relation declaration with" \ + "${WHITE}scope${NORMAL} set to 'container'." + fi + printf "%s" "$found" +} + _get_master_service_for_service_cached () { local service="$1" charm="$2" metadata="$3" cache_file="$CACHEDIR/$FUNCNAME.cache.$(echo "$*" | md5_compat)" \ @@ -1982,42 +2389,13 @@ _get_master_service_for_service_cached () { return 0 fi - ## fetch the container relation - requires="$(echo "$metadata" | shyaml get-value "requires" 2>/dev/null)" - if [ -z "$requires" ]; then - die "Charm $DARKPINK$charm$NORMAL is a subordinate but does not have any 'requires' " \ - "section." - fi - found= - while read-0 relation_name relation; do - [ "$(echo "$relation" | shyaml get-value "scope" 2>/dev/null)" == "container" ] && { - found=1 - break - } - done < <(echo "$requires" | shyaml key-values-0 2>/dev/null) - if [ -z "$found" ]; then - die "Charm $DARKPINK$charm$NORMAL is a subordinate but does not have any required relation declaration with" \ - " ${WHITE}scope${NORMAL} set to 'container'." - fi - - interface="$(echo "$relation" | shyaml get-value "interface" 2>/dev/null)" - if [ -z "$interface" ]; then - err "No ${WHITE}interface${NORMAL} set for relation $DARKBLUE$relation_name$NORMAL." - return 1 - fi - ## Action provided by relation ? - - found= - while read-0 relation_name target_service _relation_config _tech_dep; do - [ "$interface" == "$relation_name" ] && { - found=1 - break - } - done < <(get_compose_relations "$service") - if [ -z "$found" ]; then - err "Couldn't find ${WHITE}relations.$interface${NORMAL} in" \ + container_relation=$(_get_container_relation "$metadata") + read-0 target_service _ _ < <(get_service_relation "$service" "$container_relation") + if [ -z "$target_service" ]; then + err "Couldn't find ${WHITE}relations.${container_relation}${NORMAL} in" \ "${DARKYELLOW}$service$NORMAL compose definition." + err ${FUNCNAME[@]} return 1 fi echo "$target_service" | tee "$cache_file" @@ -2757,7 +3135,7 @@ _graph_service() { target_service="$candidate_target_service" break } - done < <(get_compose_relations "$service") + done < <(get_service_relations "$service") if [ -z "$target_service" ]; then err "Couldn't find ${WHITE}relations.$interface${NORMAL} in" \ "${DARKYELLOW}$service$NORMAL compose definition." @@ -2809,7 +3187,7 @@ _graph_edge_service() { # arrowhead = dotlicurve taillabel = "$relation_name" ]; EOF - done < <(get_compose_relations "$service") || return 1 + done < <(get_service_relations "$service") || return 1 } @@ -3162,27 +3540,44 @@ if [ -z "$is_docker_compose_action" -a "$action" ]; then fi else case "$action" in - ps) + ps|up) if [ "${#services_args[@]}" == 0 ]; then - services_args=($(printf "%s" "$COMPOSE_YML_CONTENT" | shyaml keys 2>/dev/null)) || true - fi - ;; - up) - if [ "${#services_args[@]}" == 0 ]; then - while read-0 service; do - type="$(get_service_type "$service")" || exit 1 - if [ "$type" != "run-once" ]; then - services_args+=("$service") - fi - done < <(printf "%s" "$COMPOSE_YML_CONTENT" | shyaml keys-0 2>/dev/null) + array_read-0 services_args < <(printf "%s" "$COMPOSE_YML_CONTENT" | shyaml keys-0 2>/dev/null) fi ;; config) services_args=("${action_posargs[@]}") ;; esac - if [ "$is_docker_compose_action" -a "${#services_args[@]}" -gt 0 ]; then - services=($(get_master_services "${services_args[@]}")) || exit 1 +fi + + +NO_CONSTRAINT_CHECK= +case "$action" in + ""|down|restart|logs|config|ps) + NO_CONSTRAINT_CHECK=True + ;; + *) + if [ "$is_service_action" ]; then + NO_CONSTRAINT_CHECK=True + fi + ;; +esac + + +get_all_relations "${services_args[@]}" >/dev/null || exit 1 + +if [ "$is_docker_compose_action" -a "${#services_args[@]}" -gt 0 ]; then + services=($(get_master_services "${services_args[@]}")) || exit 1 + if [ "$action" == "up" ]; then + ## remove run-once + for service in "${services_args[@]}"; do + type="$(get_service_type "$service")" || exit 1 + if [ "$type" != "run-once" ]; then + action_posargs+=("$service") + fi + done + else action_posargs+=("${services[@]}") fi fi diff --git a/test/base b/test/base index 7b54ba4..b4da5f5 100755 --- a/test/base +++ b/test/base @@ -547,6 +547,8 @@ EOF2 . "$tprog" _setup_state_dir +get_all_relations www >/dev/null || exit 3 + test "\$(get_master_service_for_service www)" == "www" ## -- subordinate @@ -556,9 +558,9 @@ mkdir -p $test_tmpdir/{www,mysql} touch $test_tmpdir/mysql/metadata.yml cat < $test_tmpdir/www/metadata.yml subordinate: true -requires: - a-label-for-relation: - interface: a-name-relation +uses: + a-name-relation: + constraint: required scope: container EOF2 @@ -575,6 +577,8 @@ EOF2 . "$tprog" _setup_state_dir +get_all_relations www >/dev/null || exit 3 + COMPOSE_YML_FILE=$test_tmpdir/compose.yml test "\$(get_master_service_for_service www)" == "mysql" EOF @@ -609,6 +613,7 @@ EOF2 . "$tprog" _setup_state_dir +get_all_relations www >/dev/null || exit 3 out=\$(_get_docker_compose_service_mixin www | shyaml get-value www.volumes) [[ "\$out" == "\ @@ -644,6 +649,8 @@ EOF2 _setup_state_dir COMPOSE_YML_FILE=$test_tmpdir/compose.yml +get_all_relations www >/dev/null || exit 3 + out="\$(_get_docker_compose_service_mixin www)" || exit 1 [ "\$out" == "www: @@ -671,9 +678,9 @@ data-resources: - /tmp/a config-resources: - /tmp/b -requires: +uses: a-name-relation: - interface: a-name-relation + constraint: required scope: container EOF2 @@ -690,6 +697,9 @@ EOF2 _setup_state_dir COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +get_all_relations www >/dev/null || exit 3 + out="\$(_get_docker_compose_service_mixin www)" || exit 1 expected="mysql: labels: @@ -730,6 +740,7 @@ EOF2 . "$tprog" _setup_state_dir +get_all_relations www >/dev/null || exit 3 out=\$(get_docker_compose www) echo "OUT:" echo "\$out" @@ -775,8 +786,11 @@ EOF2 COMPOSE_YML_FILE=$test_tmpdir/compose.yml _setup_state_dir +get_all_relations web_site >/dev/null || exit 3 + -out=\$(get_docker_compose www | shyaml get-value services.www.volumes) +out=\$(get_docker_compose www) || exit 4 +out=\$(printf "%s" "\$out" | shyaml get-value services.www.volumes) || exit 5 test "\$out" == "\\ - /www/tmp/a:/tmp/a:rw - /www/tmp/b:/tmp/b:rw" || { @@ -784,7 +798,8 @@ test "\$out" == "\\ exit 1 } -out=\$(get_docker_compose_links web_site | shyaml get-value web_site.links) +out=\$(get_docker_compose_links web_site) || exit 6 +out=\$(printf "%s" "\$out" | shyaml get-value web_site.links) || exit 7 test "\$out" == "- mysql" || { echo -e "** get_docker_compose_links web_site:\n\$out" exit 1 @@ -827,9 +842,9 @@ data-resources: - /tmp/a config-resources: - /tmp/b -requires: - my-db-connection: - interface: db-connection +uses: + db-connection: + constraint: required scope: container EOF2 @@ -856,6 +871,7 @@ EOF2 COMPOSE_YML_FILE=$test_tmpdir/compose.yml _setup_state_dir +get_all_relations web_site >/dev/null || exit 3 # should fail because of missing relations ! get_docker_compose www || exit 1 @@ -882,9 +898,9 @@ data-resources: - /tmp/a config-resources: - /tmp/b -requires: - my-db-connection: - interface: db-connection +uses: + db-connection: + constraint: required scope: container docker-compose: volumes: @@ -918,6 +934,7 @@ EOF2 COMPOSE_YML_FILE=$test_tmpdir/compose.yml _setup_state_dir +get_all_relations web_site >/dev/null || exit 3 # should fail because of missing relations #! get_docker_compose www || exit 1 @@ -1049,9 +1066,9 @@ EOF # - /tmp/a # config-resources: # - /tmp/b -# requires: -# my-db-connection: -# interface: db-connection +# uses: +# db-connection: +# constraint: required # scope: container # EOF2 @@ -1125,6 +1142,7 @@ EOF2 . "$tprog" COMPOSE_YML_FILE=$test_tmpdir/compose.yml _setup_state_dir +get_all_relations app >/dev/null || exit 3 out=\$(get_docker_compose_links "app") test "\$out" == "app: @@ -1158,6 +1176,7 @@ EOF2 . "$tprog" COMPOSE_YML_FILE=$test_tmpdir/compose.yml _setup_state_dir +get_all_relations web_site >/dev/null || exit 3 out=\$(get_charm_relation_def "www" "web-proxy") || exit 1 test "\$out" == "tech-dep: reversed" || { @@ -1200,9 +1219,9 @@ data-resources: - /tmp/a config-resources: - /tmp/b -requires: - my-db-connection: - interface: db-connection +uses: + db-connection: + constraint: required scope: container docker-compose: volumes: diff --git a/test/relations b/test/relations new file mode 100755 index 0000000..593c44e --- /dev/null +++ b/test/relations @@ -0,0 +1,533 @@ +#!/usr/bin/env bash-shlib +# -*- mode: shell-script -*- + +include shunit + +depends sed grep git mkdir readlink + +export -f matches +export grep + +tmp=/tmp +tprog="../bin/compose-core" +tprog=$(readlink -f $tprog) + + +export PATH=".:$PATH" +short_tprog=$(basename "$tprog") + + +## +## Convenience function +## + +init_test() { + test_tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX) + cd "$test_tmpdir" + export CACHEDIR="$test_tmpdir/.cache" + export VARDIR="$test_tmpdir/.var" + mkdir -p "$CACHEDIR" +} + + + +tear_test() { + rm -rf "$test_tmpdir" +} + + +## +## Tests +## + + +function test_all_relations_simple { + + init_test + + export DISABLE_SYSTEM_CONFIG_FILE=true + + assert_list < $test_tmpdir/www/metadata.yml +EOF2 + +touch $test_tmpdir/mysql/metadata.yml + +cat < $test_tmpdir/compose.yml +www: + charm: www + relations: + mysql-db: + mysql: + label: value +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www mysql | tr '\0' '\n') || exit 3 +expected="www +mysql-db +mysql +label: value + +True" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + +## -- simple case, 1 uses, already present + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +touch $test_tmpdir/mysql/metadata.yml + +cat < $test_tmpdir/compose.yml +www: + charm: www + relations: + mysql-db: + mysql: + label: value +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www mysql | tr '\0' '\n') || exit 3 +expected="www +mysql-db +mysql +label: value +label2: from-default-options +True" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo --- OUT: >&2 + echo "\$out" >&2 + exit 1 +} + + +## -- simple case, 1 uses, optional, not present + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +touch $test_tmpdir/mysql/metadata.yml + +cat < $test_tmpdir/compose.yml +www: + charm: www +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + + +out=\$(set -o pipefail + get_all_relations www mysql 2>&1 >/dev/null) || exit 3 +expected="" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + + +out=\$(set -o pipefail + get_all_relations www mysql | tr '\0' '\n') || exit 3 +expected="" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + +## -- simple case, 1 uses, optional with solves, not present + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + solves: + missing-feature: foo + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +touch $test_tmpdir/mysql/metadata.yml + +cat < $test_tmpdir/compose.yml +www: + charm: www +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + + +out=\$(set -o pipefail + get_all_relations www mysql 2>&1 >/dev/null) || exit 3 +expected="Notice" + +[[ "\$out" == *"\$expected"* ]] || { + echo "doesn't contain: \$expected" >&2 + echo "\$out" + exit 1 +} + + + + +## -- simple case, 1 uses, auto-pair, present + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + auto: pair + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +touch $test_tmpdir/mysql/metadata.yml + +cat < $test_tmpdir/compose.yml +www: + charm: www + relations: + mysql-db: + mysql: + label: value +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www mysql | tr '\0' '\n') || exit 3 +expected="www +mysql-db +mysql +label: value +label2: from-default-options +True" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + + +## -- simple case, 1 uses, auto-pair, not present, no provider + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + auto: pair + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +touch $test_tmpdir/mysql/metadata.yml + +cat < $test_tmpdir/compose.yml +www: + charm: www +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www mysql | tr '\0' '\n') || exit 3 +expected="" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + +## -- simple case, 1 uses, auto-pair, not present, 1 provider + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + auto: pair + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +cat < $test_tmpdir/mysql/metadata.yml +provides: + mysql-db: +EOF2 + +cat < $test_tmpdir/compose.yml +www: + charm: www +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www mysql | tr '\0' '\n') || exit 3 +expected="www +mysql-db +mysql +label: from-default-options +label2: from-default-options +True" + +[[ "\$out" == "\$expected" ]] || { + echo "--- EXPECTED" >&2 + echo "\$expected" >&2 + echo "--- OUT" >&2 + echo "\$out" >&2 + + exit 1 +} + + +## -- simple case, 1 uses, auto-pair, not present, 2 providers + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql,mysql2} +cat < $test_tmpdir/www/metadata.yml +uses: + mysql-db: + constraint: optional + auto: pair + default-options: + label: from-default-options + label2: from-default-options +EOF2 + +cat < $test_tmpdir/mysql/metadata.yml +provides: + mysql-db: +EOF2 + +cat < $test_tmpdir/mysql2/metadata.yml +provides: + mysql-db: +EOF2 + +cat < $test_tmpdir/compose.yml +www: + charm: www +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www mysql mysql2 2>&1 >/dev/null) || exit 3 + +expected="> 1" + +[[ "\$out" == *"\$expected"* ]] || { + echo "doesn't contain: \$expected" >&2 + echo "\$out" + exit 1 +} + +# Remember, it is cached +out=\$(set -o pipefail + get_all_relations www mysql mysql2 | tr '\0' '\n') || exit 3 +expected="" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + +EOF + + tear_test +} + + + +function test_all_relations_missing { + + init_test + + export DISABLE_SYSTEM_CONFIG_FILE=true + + assert_list < $test_tmpdir/www/metadata.yml +EOF2 + +cat < $test_tmpdir/mysql/metadata.yml +EOF2 + +cat < $test_tmpdir/compose.yml +www: + relations: + mysql-db: mysql +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www | tr '\0' '\n') || exit 3 +expected="www +mysql-db +mysql + +True" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "OUT:" >&2 + echo "\$out" >&2 + exit 1 +} + + +## -- 1 service, connection to another that auto-pair with first + +export CHARM_STORE=$test_tmpdir +mkdir -p $test_tmpdir/{www,mysql} +cat < $test_tmpdir/www/metadata.yml +provides: + www-proxy: +EOF2 + +cat < $test_tmpdir/mysql/metadata.yml +uses: + www-proxy: + constraint: optional + auto: pair +EOF2 + +cat < $test_tmpdir/compose.yml +www: + relations: + mysql-db: mysql +EOF2 + +. "$tprog" + +_setup_state_dir +COMPOSE_YML_FILE=$test_tmpdir/compose.yml + +out=\$(set -o pipefail + get_all_relations www | tr '\0' '\n') || exit 3 +expected="\ +www +mysql-db +mysql + +True +mysql +www-proxy +www + +True" + +[[ "\$out" == "\$expected" ]] || { + echo "DIFF:" >&2 + diff -u <(echo "\$expected") <(echo "\$out") >&2 + exit 1 +} + + + + + +EOF + + tear_test +} + + + + + + +continue_on_error="0" testbench $* diff --git a/test/uses b/test/uses new file mode 100755 index 0000000..63543ee --- /dev/null +++ b/test/uses @@ -0,0 +1,200 @@ +#!/usr/bin/env bash-shlib +# -*- mode: shell-script -*- + +include shunit + +depends sed grep git mkdir readlink + +export -f matches +export grep + +tmp=/tmp +tprog="../bin/compose-core" +tprog=$(readlink -f $tprog) + + +export PATH=".:$PATH" +short_tprog=$(basename "$tprog") + + +## +## Convenience function +## + +init_test() { + test_tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX) + cd "$test_tmpdir" + export CACHEDIR="$test_tmpdir/.cache" + export VARDIR="$test_tmpdir/.var" + mkdir -p "$CACHEDIR" +} + + + +tear_test() { + rm -rf "$test_tmpdir" +} + + +## +## Tests +## + + +function test_uses { + + init_test + + export CHARM_STORE=$test_tmpdir + mkdir -p $test_tmpdir/www/actions + cat < $test_tmpdir/www/metadata.yml +docker-image: bar ## required as we want relation to know the base image +uses: + myrelation: myrelationdef +EOF2 + + cat < $test_tmpdir/compose.yml +web_site: + charm: www +EOF2 + + export DISABLE_SYSTEM_CONFIG_FILE=true + + assert_list <&2 + echo "\$out" + exit 1 +} + +## -- service not defined in compose, but has charm + +cd "$test_tmpdir" +. "$tprog" || exit 1 + +_setup_state_dir + +out=\$(_get_services_uses www | tr '\0' ':') || exit 2 +expected="www:myrelation:myrelationdef:" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + +## -- service not defined in compose, nor has charm should fail + +cd "$test_tmpdir" +. "$tprog" || exit 1 + +_setup_state_dir + +_get_services_uses xxx || exit 0 + +echo "Expected it to fail" +exit 1 + +EOF + + tear_test +} + + +function test_provides { + + init_test + + export CHARM_STORE=$test_tmpdir + mkdir -p $test_tmpdir/www/actions + cat < $test_tmpdir/www/metadata.yml +docker-image: bar ## required as we want relation to know the base image +provides: + myrelation: myrelationdef +EOF2 + + cat < $test_tmpdir/compose.yml +web_site: + charm: www +EOF2 + + export DISABLE_SYSTEM_CONFIG_FILE=true + + assert_list <&2 + echo "\$out" + exit 1 +} + + +## -- service not defined in compose, but has charm + +cd "$test_tmpdir" +. "$tprog" || exit 1 + +_setup_state_dir + + +out=\$(_get_services_provides www | tr '\0' ':') +expected="www:myrelation:myrelationdef:" + +[[ "\$out" == "\$expected" ]] || { + echo "doesn't end with: \$expected" >&2 + echo "\$out" + exit 1 +} + + +## -- service not defined in compose, nor has charm should fail + +cd "$test_tmpdir" +. "$tprog" || exit 1 + +_setup_state_dir + +out=\$(set -o pipefail + _get_provides_uses xxx | tr '\0' :) || exit 0 + +echo "Expected it to fail" +exit 1 + + +EOF + + tear_test +} + + + + +continue_on_error="0" testbench $*