Skip to content

Instantly share code, notes, and snippets.

@ashb
Created May 10, 2017 08:50
Show Gist options
  • Save ashb/b4790d919049f4d700e64f1af7e9fee4 to your computer and use it in GitHub Desktop.
Save ashb/b4790d919049f4d700e64f1af7e9fee4 to your computer and use it in GitHub Desktop.

Revisions

  1. ashb created this gist May 10, 2017.
    162 changes: 162 additions & 0 deletions hacky.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    ensure_kube_apiserver_config() {
    local dry_run="$2"
    # If we want to make changes to the apiserver manifest we do it via an
    # Azure CustomLinuxCommand extension
    # https://github.com/Azure/custom-script-extension-linux which will run the
    # command for us without having to log in. The down side to this is that it
    # takes a while to "deploy" the extension, which is espeically a pain if
    # the config is already correct.
    #
    # To speed it up and only deploy the extension if something is changed we
    # look at the spec of the API server using `kubectl`.

    local client_id
    client_id="$(jq -r -e '.parameters.authOIDCClientId.value' "$customization_params_file")" || {
    info "Skipping APIServer config: no authOIDCClientId.value found in customization file"
    return
    }

    desired_args=(
    "--oidc-client-id" "$client_id"
    "--oidc-issuer-url" "https://login.microsoftonline.com/<creds>/v2.0"
    "--oidc-username-claim" "preferred_username"
    "--oidc-groups-claim" "groups"
    )

    info "Checking kube-apiserver config..."
    local current_config master_vms

    list_deployment_vms --master-only --names <<<"$1"
    master_vms=$(list_deployment_vms --master-only --names <<<"$1") || {
    err "Error getting master VMs."
    exit 1
    }
    for vm_id in $master_vms
    do
    # There might not be a kube-apiserver running (yet) on that node, so we
    # can't pipe the output directly into filter_spec_command_flags.
    current_config="$(kubectl --namespace=kube-system get "pods/kube-apiserver-$(tr '[:upper:]' '[:lower:]' <<<"$vm_id")" -o json)" || true
    local status=${PIPESTATUS[0]}
    if [[ $status != 0 ]] || <<<"$current_config" filter_spec_command_flags --are-changes-needed -- "${desired_args[@]}"; then
    [[ -n "$dry_run" ]] && {
    info "API Server config on $vm_id needs updating (dry run)"
    continue
    }

    info "Updating API Server config on $vm_id"
    az vm extension set \
    -g "$rg_name" \
    --vm-name "$vm_id" \
    --publisher Microsoft.Azure.Extensions \
    -n CustomScript \
    --protected-settings "$(extension_json_params "${desired_args[@]}")"
    fi
    done
    }

    extension_json_params() {
    # Build the json parameters we feed into the custom extension. We could
    # upload a file to an azure storage blob to make this nicer, but we haven't
    # done that anywhere else so for now it's a big-massive-log bash -c ''
    # command.

    # NOTE: This uses wildducktheories/y2j. If any environment blocks pulling
    # images from the docker hub this might fail!

    {
    echo '#!/bin/bash
    set -ex -o pipefail
    '
    # Now include the function body, and the flags we want
    # `type` will tell us what it is, and print the definition. We want just the definition
    type filter_spec_command_flags | tail -n +2
    echo "desired_args=("
    printf " %q" "$@"
    echo ")"

    # Then the rest of our script
    # shellcheck disable=SC1004
    echo '
    apiserver_manifest="$(</etc/kubernetes/manifests/kube-apiserver.yaml docker run --rm -i wildducktheories/y2j y2j)"
    if filter_spec_command_flags --are-changes-needed -- "${desired_args[@]}" <<<"${apiserver_manifest}"; then
    echo "Changes needed in kube-apiserver.yaml"
    tmpfile=$(mktemp)
    filter_spec_command_flags -- "${desired_args[@]}" <<<"${apiserver_manifest}" \
    | docker run --rm -i wildducktheories/y2j j2y > $tmpfile
    mv $tmpfile /etc/kubernetes/manifests/kube-apiserver.yaml
    echo "Restarting kubelet"
    service kubelet restart
    else
    echo "No change needed in kube-apiserver.yaml"
    fi
    '
    } | jq --slurp -R '{
    commandToExecute: "/bin/bash -c \(. | @sh)"
    }'
    }

    # Expects as input on stdin a pod spec IN JSON (not the default Yaml) and takes
    # arguments of the list of options we want to see in the command line. Returns
    # the new pod spec also in JSON.
    filter_spec_command_flags() {
    # Check for cmd flags first

    # Save stdout so we can re-open it later
    exec 3>&1

    local final_filter="" filters="" jq_args=()
    while [[ "$#" -gt 0 ]]; do
    case "${1:-}" in
    --are-changes-needed)
    # Exit with 0 exit code if the command needs to be updated
    filters='. as $orig | '
    final_filter=' | . != $orig'
    jq_args=(-e)
    # We won't want any output from this function
    exec >/dev/null
    ;;
    --)
    shift
    break
    ;;
    *)
    >&2 echo "Unknown mode to filter_spec_command_flags: \"$1\""
    exit 2
    ;;
    esac
    shift
    done

    [[ $(($# % 2)) -eq 0 ]] || {
    echo >&2 "filter_spec_command_flags needs an even number of remaining arguments!"
    return 1
    }
    while [[ $# -gt 0 ]]; do
    filters+=".spec.containers[].command |= update_or_append(\"$1\"; \"$2\") | "
    shift
    shift
    done

    # Trim off the " | " from the end using bash parameter substitution.
    filters="${filters% | }"

    jq "${jq_args[@]}" '
    def update_or_append($flag;$value):
    # Do we already have a setting of this value, or do we need to add a new one
    if any(startswith("\($flag)=")) then
    map(
    if startswith($flag) then
    "\($flag)=\($value)"
    else
    .
    end)
    else
    . + ["\($flag)=\($value)"]
    end;
    '"$filters$final_filter"
    local status=$?
    exec 1>&3 3>&-
    return $status
    }