Setting the file. One moment. Retrieve Captures · AKS Network Capture · microsoft/azure-skills · Skills Docs Number4.9scripts/retrieve-captures.sh
Shell·253 lines·8 KB
""
11
12usage() {
13 cat <<EOF
14Usage: $0 --name <capture-name> [options]
15
16Required:
17 --name <string> Capture name used during creation
18
19Options:
20 --output-path <path> Node hostPath base (default: ${OUTPUT_PATH})
21 --workspace-dir <path> Local workspace directory (default: ${WORKSPACE_DIR})
22 --namespace <string> Capture Job namespace (default: default)
23 --run-id <string> Exact run ID; required when multiple runs exist
24 -h, --help Show this help
25EOF
26}
27
28usage_error() {
29 echo "Error: $*" >&2
30 usage >&2
31 exit 2
32}
33
34die() {
35 echo "Error: $*" >&2
36 exit 1
37}
38
39require_value() {
40 [ "$#" -ge 2 ] && [ -n "$2" ] || usage_error "$1 requires a value"
41}
42
43valid_single_line() {
44 case "$1" in
45 *$'\r'*|*$'\n'*) return 1 ;;
46 esac
47}
48
49valid_rfc1123() {
50 valid_single_line "$1" && [ "${#1}" -le 63 ] \
51 && printf '%s' "$1" | grep -Eq '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
52}
53
54valid_node_name() {
55 valid_single_line "$1" && [ "${#1}" -le 253 ] \
56 && printf '%s' "$1" \
57 | grep -Eq '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
58}
59
60valid_hostpath() {
61 case "$1" in
62 /*) ;;
63 *) return 1 ;;
64 esac
65 case "$1" in
66 *..*|*[!A-Za-z0-9/_.-]*) return 1 ;;
67 esac
68}
69
70valid_run_id() {
71 local run_date="${1%%-*}"
72 local remainder="${1#*-}"
73 local run_time="${remainder%%-*}"
74 local run_token="${remainder#*-}"
75 case "$1" in
76 *[!0-9a-f-]*) return 1 ;;
77 esac
78 [ "${#run_date}" -eq 8 ] && [ "${#run_time}" -eq 6 ] \
79 && [ "${#run_token}" -eq 24 ] || return 1
80 case "${run_date}${run_time}${run_token}" in
81 *[!0-9a-f]*) return 1 ;;
82 esac
83}
84
85while [ "$#" -gt 0 ]; do
86 case "$1" in
87 --name)
88 require_value "$@"; CAPTURE_NAME="$2"; shift 2 ;;
89 --output-path)
90 require_value "$@"; OUTPUT_PATH="$2"; shift 2 ;;
91 --workspace-dir)
92 require_value "$@"; WORKSPACE_DIR="$2"; shift 2 ;;
93 --namespace)
94 require_value "$@"; NAMESPACE="$2"; shift 2 ;;
95 --run-id)
96 require_value "$@"; RETRIEVE_RUN_ID="$2"; shift 2 ;;
97 -h|--help)
98 usage; exit 0 ;;
99 *)
100 usage_error "unknown option: $1" ;;
101 esac
102done
103
104[ -n "$CAPTURE_NAME" ] || usage_error "--name is required"
105valid_rfc1123 "$CAPTURE_NAME" || usage_error "--name must be an RFC 1123 label"
106valid_hostpath "$OUTPUT_PATH" \
107 || usage_error "--output-path must be absolute and contain only letters, digits, '/', '_', '.', or '-'"
108valid_single_line "$WORKSPACE_DIR" || usage_error "--workspace-dir must be a single line"
109valid_rfc1123 "$NAMESPACE" || usage_error "--namespace must be an RFC 1123 label"
110[ -z "$RETRIEVE_RUN_ID" ] || valid_run_id "$RETRIEVE_RUN_ID" \
111 || usage_error "--run-id has an invalid format"
112
113command -v kubectl >/dev/null 2>&1 || die "kubectl is not installed or not on PATH"
114
115if [ -z "$RETRIEVE_RUN_ID" ]; then
116 if ! CAPTURE_RUNS="$(kubectl get jobs -n "$NAMESPACE" \
117 -l "capture-id=${CAPTURE_NAME}" \
118 -o jsonpath='{range .items[*]}{.metadata.labels.capture-run}{"\n"}{end}' \
119 | sed '/^$/d' | sort -u)"; then
120 die "could not list capture runs"
121 fi
122 RUN_COUNT="$(printf '%s\n' "$CAPTURE_RUNS" | sed '/^$/d' | wc -l | tr -d '[:space:]')"
123 case "$RUN_COUNT" in
124 0) die "no capture runs found for '$CAPTURE_NAME'" ;;
125 1) RETRIEVE_RUN_ID="$CAPTURE_RUNS" ;;
126 *) die "multiple capture runs found; specify --run-id" ;;
127 esac
128fi
129valid_run_id "$RETRIEVE_RUN_ID" || die "Kubernetes returned an invalid capture run identity"
130
131if ! CAPTURE_JOBS="$(kubectl get jobs -n "$NAMESPACE" \
132 -l "capture-id=${CAPTURE_NAME},capture-run=${RETRIEVE_RUN_ID}" \
133 -o jsonpath='{.items[*].metadata.name}')"; then
134 die "could not list capture Jobs"
135fi
136[ -n "$CAPTURE_JOBS" ] || die "no Jobs found for the requested capture run"
137read -r -a CAPTURE_JOB_NAMES <<<"$CAPTURE_JOBS"
138
139CAPTURES_DIR="${WORKSPACE_DIR}/network-captures"
140TIMESTAMP="$(date -u +%Y%m%d-%H%M%S)"
141CAPTURE_OUTPUT_DIR="${CAPTURES_DIR}/${CAPTURE_NAME}-${RETRIEVE_RUN_ID}-${TIMESTAMP}"
142mkdir -p "$CAPTURE_OUTPUT_DIR"
143
144TEMP_PODS=()
145cleanup_resources() {
146 local failed=0
147 local pod
148 if [ "${#TEMP_PODS[@]}" -gt 0 ]; then
149 for pod in "${TEMP_PODS[@]}"; do
150 if ! kubectl delete pod "$pod" -n "$NAMESPACE" --ignore-not-found >/dev/null; then
151 echo "Error: failed to delete retrieval pod '$pod'" >&2
152 failed=1
153 fi
154 done
155 fi
156 if ! kubectl delete jobs -n "$NAMESPACE" "${CAPTURE_JOB_NAMES[@]}" \
157 --ignore-not-found >/dev/null; then
158 echo "Error: failed to delete capture Jobs" >&2
159 failed=1
160 fi
161 return "$failed"
162}
163
164cleanup_on_exit() {
165 local status=$?
166 trap - EXIT
167 if ! cleanup_resources; then
168 status=1
169 fi
170 exit "$status"
171}
172trap cleanup_on_exit EXIT
173
174for job in "${CAPTURE_JOB_NAMES[@]}"; do
175 if ! NODE_NAME="$(kubectl get job "$job" -n "$NAMESPACE" \
176 -o jsonpath='{.spec.template.spec.nodeName}')"; then
177 die "could not read the node for Job '$job'"
178 fi
179 if ! RUN_ID="$(kubectl get job "$job" -n "$NAMESPACE" \
180 -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="RUN_ID")].value}')"; then
181 die "could not read the run identity for Job '$job'"
182 fi
183 valid_node_name "$NODE_NAME" || die "Job '$job' returned an invalid node name"
184 valid_run_id "$RUN_ID" || die "Job '$job' returned an invalid run identity"
185 [ "$RUN_ID" = "$RETRIEVE_RUN_ID" ] || die "Job '$job' does not match the requested run"
186
187 if ! POD_NAME="$(kubectl get pods -n "$NAMESPACE" -l "job-name=${job}" \
188 -o jsonpath='{.items[0].metadata.name}')"; then
189 die "could not find the capture pod for Job '$job'"
190 fi
191 [ -n "$POD_NAME" ] || die "no capture pod found for Job '$job'"
192 if ! POD_STATUS="$(kubectl get pod "$POD_NAME" -n "$NAMESPACE" \
193 -o jsonpath='{.status.phase}')"; then
194 die "could not read capture pod status"
195 fi
196 [ "$POD_STATUS" = "Succeeded" ] \
197 || die "capture pod '$POD_NAME' did not succeed; inspect its logs before retrying"
198
199 capture_slug="$(printf '%s' "$CAPTURE_NAME" | cut -c1-20)"
200 node_slug="$(printf '%s' "$NODE_NAME" | tr '._' '--' | cut -c1-20)"
201 if ! TEMP_POD_NAME="$(kubectl create -f - -o jsonpath='{.metadata.name}' <<EOF
202apiVersion: v1
203kind: Pod
204metadata:
205 generateName: retrieve-${capture_slug}-${node_slug}-
206 namespace: ${NAMESPACE}
207 labels: { app: aks-network-capture, capture-id: "${CAPTURE_NAME}", role: retrieval }
208spec:
209 hostNetwork: true
210 nodeName: "${NODE_NAME}"
211 restartPolicy: Never
212 containers:
213 - name: retrieve
214 image: mcr.microsoft.com/cbl-mariner/busybox:2.0@sha256:e4fb4d51fc9b70d6cdc1ce66a0af02ab40554d2ca632e1d188fabc760e432fdd
215 command: ["sh", "-c", "sleep 300"]
216 volumeMounts:
217 - name: capture-output
218 mountPath: /capture-root
219 volumes:
220 - name: capture-output
221 hostPath:
222 path: ${OUTPUT_PATH}/${CAPTURE_NAME}
223 type: Directory
224EOF
225)"; then
226 die "failed to create a retrieval pod on node '$NODE_NAME'"
227 fi
228 [ -n "$TEMP_POD_NAME" ] || die "Kubernetes did not return a retrieval pod name"
229 TEMP_PODS+=("$TEMP_POD_NAME")
230
231 kubectl wait -n "$NAMESPACE" --for=condition=Ready \
232 "pod/${TEMP_POD_NAME}" --timeout=60s
233
234 BUNDLE_NAME="capture-${CAPTURE_NAME}-${NODE_NAME}-${RUN_ID}.tar.gz"
235 RUN_DIR="/capture-root/${RUN_ID}"
236 kubectl cp -n "$NAMESPACE" "${TEMP_POD_NAME}:${RUN_DIR}/${BUNDLE_NAME}" \
237 "${CAPTURE_OUTPUT_DIR}/${BUNDLE_NAME}" -c retrieve
238 [ -s "${CAPTURE_OUTPUT_DIR}/${BUNDLE_NAME}" ] \
239 || die "the copied capture bundle '$BUNDLE_NAME' is empty"
240
241 # Positional arguments keep validated values out of the fixed remote command.
242 # shellcheck disable=SC2016
243 kubectl exec -n "$NAMESPACE" "$TEMP_POD_NAME" -c retrieve -- sh -c \
244 'rm -f "$1/$2" "$1/$3" && rmdir "$1"' sh \
245 "$RUN_DIR" "$BUNDLE_NAME" "capture-${CAPTURE_NAME}-${NODE_NAME}-${RUN_ID}.pcap"
246 kubectl delete pod "$TEMP_POD_NAME" -n "$NAMESPACE" --ignore-not-found
247done
248
249cleanup_resources || die "failed to clean up one or more capture resources"
250trap - EXIT
251
252echo "Capture bundles saved to: $CAPTURE_OUTPUT_DIR"
253ls -lh "$CAPTURE_OUTPUT_DIR"