194 lines
5.8 KiB
Bash
Executable File
194 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
# Secret이 셸 추적에 노출되지 않도록 호출자가 bash -x를 사용해도 끈다.
|
|
set +x
|
|
|
|
readonly NAMESPACE="object-storage"
|
|
readonly OBJECTSTORE="minio-aistor"
|
|
readonly ROOT_SECRET="aistor-root-configuration"
|
|
readonly S3_ENDPOINT="http://minio.object-storage.svc.cluster.local"
|
|
readonly MC_IMAGE="quay.io/minio/aistor/mc@sha256:c5ec777c080fd6292b7529309a0f311c58c3a92e268c9c57c0901da86881949b"
|
|
readonly PASS_LINE="AISTOR_S3_SMOKE_PASS bucket-create object-write object-read object-delete bucket-delete"
|
|
|
|
execute_requested=false
|
|
pod_name=""
|
|
|
|
fail() {
|
|
printf 'ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "$pod_name" ]]; then
|
|
kubectl --namespace "$NAMESPACE" delete pod "$pod_name" \
|
|
--ignore-not-found --wait=true --timeout=30s >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
bash scripts/validate/aistor-s3-smoke.sh --execute
|
|
|
|
Creates one restricted, short-lived AIStor Client Pod. It authenticates with
|
|
object-storage/aistor-root-configuration and performs:
|
|
bucket create -> object write -> SHA-256 read check
|
|
-> object delete -> bucket delete
|
|
|
|
The script never prints credentials. It removes the temporary bucket and Pod.
|
|
Type APPLY only when prompted because this performs temporary S3 writes.
|
|
USAGE
|
|
}
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--execute)
|
|
execute_requested=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
usage >&2
|
|
fail "unsupported argument: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[[ "$execute_requested" == true ]] || {
|
|
usage
|
|
fail "--execute is required"
|
|
}
|
|
|
|
for command_name in kubectl grep jq; do
|
|
command -v "$command_name" >/dev/null 2>&1 || \
|
|
fail "${command_name} is required"
|
|
done
|
|
|
|
[[ "$(
|
|
kubectl --namespace "$NAMESPACE" get objectstore "$OBJECTSTORE" \
|
|
--output=jsonpath='{.status.currentState}'
|
|
)" == "Initialized" ]] || fail "ObjectStore is not Initialized"
|
|
[[ "$(
|
|
kubectl --namespace "$NAMESPACE" get objectstore "$OBJECTSTORE" \
|
|
--output=jsonpath='{.status.healthStatus}'
|
|
)" == "green" ]] || fail "ObjectStore health is not green"
|
|
kubectl --namespace "$NAMESPACE" get secret "$ROOT_SECRET" >/dev/null
|
|
|
|
printf '%s\n' \
|
|
"Target: ${NAMESPACE}/${OBJECTSTORE}" \
|
|
"Endpoint: ${S3_ENDPOINT}" \
|
|
'Temporary resources: one bucket, one object, and one restricted client Pod' \
|
|
'No credential value will be printed.'
|
|
printf 'Type APPLY to run the authenticated S3 smoke test: '
|
|
IFS= read -r confirmation
|
|
[[ "$confirmation" == "APPLY" ]] || fail "confirmation did not match APPLY"
|
|
|
|
pod_name="aistor-s3-smoke-$(date +%H%M%S)-$$"
|
|
trap cleanup EXIT INT TERM
|
|
|
|
smoke_command='set -eu
|
|
. /run/aistor-root/config.env
|
|
bucket="platform-smoke-$(date +%s)"
|
|
cleanup_bucket() { mc rb --force "local/${bucket}" >/dev/null 2>&1 || true; }
|
|
trap cleanup_bucket EXIT
|
|
mc alias set local "'"$S3_ENDPOINT"'" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --api S3v4 >/dev/null
|
|
payload="aistor-authenticated-smoke-$(date +%s)"
|
|
expected="$(printf "%s" "$payload" | sha256sum | cut -d " " -f 1)"
|
|
mc mb "local/${bucket}" >/dev/null
|
|
printf "%s" "$payload" | mc pipe "local/${bucket}/probe.txt" >/dev/null
|
|
actual="$(mc cat "local/${bucket}/probe.txt" | sha256sum | cut -d " " -f 1)"
|
|
[ "$expected" = "$actual" ]
|
|
mc stat "local/${bucket}/probe.txt" >/dev/null
|
|
mc rm "local/${bucket}/probe.txt" >/dev/null
|
|
mc rb "local/${bucket}" >/dev/null
|
|
trap - EXIT
|
|
printf "%s\n" "'"$PASS_LINE"'"'
|
|
|
|
overrides="$(
|
|
jq -nc --arg command "$smoke_command" --arg image "$MC_IMAGE" '{
|
|
spec: {
|
|
automountServiceAccountToken: false,
|
|
securityContext: {
|
|
runAsNonRoot: true,
|
|
runAsUser: 1000,
|
|
runAsGroup: 1000,
|
|
fsGroup: 1000,
|
|
seccompProfile: {type: "RuntimeDefault"}
|
|
},
|
|
containers: [{
|
|
name: "aistor-s3-smoke",
|
|
image: $image,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["/bin/sh", "-ec"],
|
|
args: [$command],
|
|
securityContext: {
|
|
allowPrivilegeEscalation: false,
|
|
capabilities: {drop: ["ALL"]},
|
|
readOnlyRootFilesystem: true
|
|
},
|
|
env: [{name: "MC_CONFIG_DIR", value: "/tmp/mc"}],
|
|
volumeMounts: [
|
|
{name: "root-config", mountPath: "/run/aistor-root", readOnly: true},
|
|
{name: "tmp", mountPath: "/tmp"}
|
|
]
|
|
}],
|
|
volumes: [
|
|
{
|
|
name: "root-config",
|
|
secret: {secretName: "'"$ROOT_SECRET"'", defaultMode: 256}
|
|
},
|
|
{name: "tmp", emptyDir: {}}
|
|
]
|
|
}
|
|
}'
|
|
)"
|
|
|
|
kubectl --namespace "$NAMESPACE" run "$pod_name" \
|
|
--image="$MC_IMAGE" \
|
|
--restart=Never \
|
|
--labels='platform.hyeonworks.com/aistor-client=true,app.kubernetes.io/name=aistor-s3-smoke' \
|
|
--overrides="$overrides"
|
|
|
|
succeeded=false
|
|
for _ in $(seq 1 90); do
|
|
phase="$(
|
|
kubectl --namespace "$NAMESPACE" get pod "$pod_name" \
|
|
--output=jsonpath='{.status.phase}'
|
|
)"
|
|
if [[ "$phase" == "Succeeded" ]]; then
|
|
succeeded=true
|
|
break
|
|
fi
|
|
[[ "$phase" != "Failed" ]] || break
|
|
|
|
waiting_reason="$(
|
|
kubectl --namespace "$NAMESPACE" get pod "$pod_name" \
|
|
--output=jsonpath='{.status.containerStatuses[0].state.waiting.reason}' \
|
|
2>/dev/null || true
|
|
)"
|
|
case "$waiting_reason" in
|
|
CreateContainerConfigError|CrashLoopBackOff|ErrImagePull|ImagePullBackOff)
|
|
break
|
|
;;
|
|
esac
|
|
sleep 2
|
|
done
|
|
|
|
if [[ "$succeeded" != true ]]; then
|
|
kubectl --namespace "$NAMESPACE" logs "$pod_name" || true
|
|
kubectl --namespace "$NAMESPACE" describe pod "$pod_name" | tail -n 60
|
|
fail "authenticated S3 smoke test failed"
|
|
fi
|
|
|
|
log_output="$(kubectl --namespace "$NAMESPACE" logs "$pod_name")"
|
|
grep --fixed-strings --line-regexp --quiet "$PASS_LINE" <<<"$log_output" || \
|
|
fail "smoke Pod did not emit the exact success contract"
|
|
printf '%s\n' "$PASS_LINE"
|
|
printf '%s\n' \
|
|
"Client image: ${MC_IMAGE}" \
|
|
'Temporary bucket, object, and client Pod cleanup: complete'
|