#!/usr/bin/env bash

# Copyright 2018 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail

run_kubectl_config_set_tests() {
  set -o nounset
  set -o errexit

  create_and_use_new_namespace
  kube::log::status "Testing kubectl(v1:config set)"

  kubectl config set-cluster test-cluster --server="https://does-not-work"

  # Get the api cert and add a comment to avoid flag parsing problems
  cert_data=$(echo "#Comment" && cat "${TMPDIR:-/tmp}/apiserver.crt")

  kubectl config set clusters.test-cluster.certificate-authority-data "$cert_data" --set-raw-bytes
  r_written=$(kubectl config view --raw -o jsonpath='{.clusters[?(@.name == "test-cluster")].cluster.certificate-authority-data}')

  encoded=$(echo -n "$cert_data" | base64)
  kubectl config set clusters.test-cluster.certificate-authority-data "$encoded"
  e_written=$(kubectl config view --raw -o jsonpath='{.clusters[?(@.name == "test-cluster")].cluster.certificate-authority-data}')

  test "$e_written" == "$r_written"

  set +o nounset
  set +o errexit
}

run_kubectl_config_set_cluster_tests() {
  set -o nounset
  set -o errexit

  create_and_use_new_namespace
  kube::log::status "Testing kubectl config set-cluster"

  ca_file="${TMPDIR:-/tmp}/apiserver.crt"
  ca_data=$(base64 -w0 "$ca_file")

  # Set cert file
  kubectl config set-cluster test-cluster-1 --certificate-authority "$ca_file"
  expected="$ca_file"
  actual=$(kubectl config view --raw -o jsonpath='{.clusters[?(@.name == "test-cluster-1")].cluster.certificate-authority}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Certificate authority did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi

  # Embed cert from file
  kubectl config set-cluster test-cluster-2 --embed-certs --certificate-authority "$ca_file"
  expected="$ca_data"
  actual=$(kubectl config view --raw -o jsonpath='{.clusters[?(@.name == "test-cluster-2")].cluster.certificate-authority-data}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Certificate authority embedded from file did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi

  # Embed cert using process substitution
  kubectl config set-cluster test-cluster-3 --embed-certs --certificate-authority <(cat "$ca_file")
  expected="$ca_data"
  actual=$(kubectl config view --raw -o jsonpath='{.clusters[?(@.name == "test-cluster-3")].cluster.certificate-authority-data}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Certificate authority embedded using process substitution did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi

  set +o nounset
  set +o errexit
}

run_kubectl_config_set_credentials_tests() {
  set -o nounset
  set -o errexit

  create_and_use_new_namespace
  kube::log::status "Testing kubectl config set-credentials"

  cert_file="${TMPDIR:-/tmp}/test-client-certificate.crt"
  key_file="${TMPDIR:-/tmp}/test-client-key.crt"

  cat << EOF > "$cert_file"
-----BEGIN CERTIFICATE-----
[payload removed: 1172 characters in 19 lines]
-----END CERTIFICATE-----
EOF

  cat << EOF > "$key_file"
-----BEGIN PRIVATE KEY-----
[payload removed: 1624 characters in 26 lines]
-----END PRIVATE KEY-----
EOF

  cert_data=$(base64 -w0 "$cert_file")
  key_data=$(base64 -w0 "$key_file")

  # Set client certificate and client key files
  kubectl config set-credentials user1 --client-certificate="$cert_file" --client-key="$key_file"
  expected="$cert_file"
  actual=$(kubectl config view --raw -o jsonpath='{.users[?(@.name == "user1")].user.client-certificate}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Client certificate did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi
  expected="$key_file"
  actual=$(kubectl config view --raw -o jsonpath='{.users[?(@.name == "user1")].user.client-key}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Client key did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi

  # Embed client certificate and client key from files
  kubectl config set-credentials user2 --client-certificate="$cert_file" --client-key="$key_file" --embed-certs=true
  expected="$cert_data"
  actual=$(kubectl config view --raw -o jsonpath='{.users[?(@.name == "user2")].user.client-certificate-data}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Client certificate data embedded from file did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi
  expected="$key_data"
  actual=$(kubectl config view --raw -o jsonpath='{.users[?(@.name == "user2")].user.client-key-data}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Client key data embedded from file did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi

  # Embed client certificate and client key using process substitution
  kubectl config set-credentials user3 --client-certificate=<(cat "$cert_file") --client-key=<(cat "$key_file") --embed-certs=true
  expected="$cert_data"
  actual=$(kubectl config view --raw -o jsonpath='{.users[?(@.name == "user3")].user.client-certificate-data}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Client certificate data embedded using process substitution did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi
  expected="$key_data"
  actual=$(kubectl config view --raw -o jsonpath='{.users[?(@.name == "user3")].user.client-key-data}')
  if [ "$expected" != "$actual" ]; then
    kube::log::error "Client key data embedded using process substitution did not match the expected value (expected=$expected, actual=$actual)"
    exit 1
  fi

  set +o nounset
  set +o errexit
}

run_client_config_tests() {
  set -o nounset
  set -o errexit

  create_and_use_new_namespace
  kube::log::status "Testing client config"

  # Command
  # Pre-condition: kubeconfig "missing" is not a file or directory
  output_message=$(! kubectl get pod --context="" --kubeconfig=missing 2>&1)
  kube::test::if_has_string "${output_message}" "missing: no such file or directory"

  # Pre-condition: kubeconfig "missing" is not a file or directory
  # Command
  output_message=$(! kubectl get pod --user="" --kubeconfig=missing 2>&1)
  # Post-condition: --user contains a valid / empty value, missing config file returns error
  kube::test::if_has_string "${output_message}" "missing: no such file or directory"
  # Command
  output_message=$(! kubectl get pod --cluster="" --kubeconfig=missing 2>&1)
  # Post-condition: --cluster contains a "valid" value, missing config file returns error
  kube::test::if_has_string "${output_message}" "missing: no such file or directory"

  # Pre-condition: context "missing-context" does not exist
  # Command
  output_message=$(! kubectl get pod --context="missing-context" 2>&1)
  kube::test::if_has_string "${output_message}" 'context was not found for specified context: missing-context'
  # Post-condition: invalid or missing context returns error

  # Pre-condition: cluster "missing-cluster" does not exist
  # Command
  output_message=$(! kubectl get pod --cluster="missing-cluster" 2>&1)
  kube::test::if_has_string "${output_message}" 'no server found for cluster "missing-cluster"'
  # Post-condition: invalid or missing cluster returns error

  # Pre-condition: user "missing-user" does not exist
  # Command
  output_message=$(! kubectl get pod --user="missing-user" 2>&1)
  kube::test::if_has_string "${output_message}" 'auth info "missing-user" does not exist'
  # Post-condition: invalid or missing user returns error

  # test invalid config
  kubectl config view | sed -E "s/apiVersion: .*/apiVersion: v-1/g" > "${TMPDIR:-/tmp}"/newconfig.yaml
  output_message=$(! "${KUBE_OUTPUT_HOSTBIN}/kubectl" get pods --context="" --user="" --kubeconfig="${TMPDIR:-/tmp}"/newconfig.yaml 2>&1)
  kube::test::if_has_string "${output_message}" "error loading config file"

  output_message=$(! kubectl get pod --kubeconfig=missing-config 2>&1)
  kube::test::if_has_string "${output_message}" 'no such file or directory'

  set +o nounset
  set +o errexit
}

run_kubeconfig_impersonate_tests() {
  set -o nounset
  set -o errexit

  kube::log::status "Testing config with impersonation"

  # copy the existing kubeconfig over and add a new user entry for the admin user impersonating a different user.
  kubectl config view --raw > "${TMPDIR:-/tmp}"/impersonateconfig.yaml
  cat << EOF >> "${TMPDIR:-/tmp}"/impersonateconfig.yaml
users:
- name: admin-as-userb
  user:
    # token defined in hack/testdata/auth-tokens.csv
    token: admin-token
    # impersonated user
    as: userb
    as-uid: abc123
    as-groups:
    - group2
    - group1
    as-user-extra:
      foo:
      - bar
      - baz
- name: as-uid-without-as
  user:
    # token defined in hack/testdata/auth-tokens.csv
    token: admin-token
    # impersonated uid
    as-uid: abc123
EOF

  kubectl create -f hack/testdata/csr.yml --kubeconfig "${TMPDIR:-/tmp}"/impersonateconfig.yaml --user admin-as-userb
  kube::test::get_object_assert 'csr/foo' '{{.spec.username}}' 'userb'
  kube::test::get_object_assert 'csr/foo' '{{.spec.uid}}' 'abc123'
  kube::test::get_object_assert 'csr/foo' '{{range .spec.groups}}{{.}} {{end}}' 'group2 group1 system:authenticated '
  kube::test::get_object_assert 'csr/foo' '{{len .spec.extra}}' '1'
  kube::test::get_object_assert 'csr/foo' '{{range .spec.extra.foo}}{{.}} {{end}}' 'bar baz '
  kubectl delete -f hack/testdata/csr.yml

  output_message=$(! kubectl get pods --kubeconfig "${TMPDIR:-/tmp}"/impersonateconfig.yaml --user as-uid-without-as 2>&1)
  kube::test::if_has_string "${output_message}" 'without impersonating a user'

  set +o nounset
  set +o errexit
}