Welcome to the final chapter of our Restore Hooks series!
In the previous chapter, Exec Hooks Implementation, we watched Velero act as a "remote control," waiting for a Pod to start and then executing commands inside it.
But before Velero can press the buttons on that remote control, it needs to read the manual. It needs to look at the messy, handwritten "Sticky Notes" (Annotations) you put on your Pod and convert them into a strict, structured plan.
In this chapter, we will look at the code logic that parses these annotations. We will specifically focus on how Velero handles defaults—filling in the blanks when you forget to write instructions.
Imagine you are filling out a form at a doctor's office.
The receptionist doesn't panic. They have Default Rules:
The Problem: Users often provide the bare minimum configuration. They might say "Run this command," but forget to say how long to wait or what to do if it fails. The Solution: The parsing code acts like the receptionist. It reads what exists and applies sensible Defaults for what is missing.
You want to run a cleanup script. You write this annotation:
post.hook.restore.velero.io/command: '["/bin/sh", "-c", "cleanup.sh"]'
You did not specify:
We will walk through the code that reads this single line and expands it into a full configuration object.
To convert text into a Go Object, we need three logic steps:
Velero looks at the Pod's metadata map. It searches for specific "Keys" (strings defined as constants in the code).
The command is stored as a JSON string (e.g., ["ls", "-la"]). Go cannot execute a string directly; it needs a "Slice" (a list). We use a standard library tool called json.Unmarshal to convert the text into a list.
This is the logic that says: "If the value is empty, use X." This is crucial for stability. If we didn't have defaults, a missing timeout value could cause Velero to wait forever (infinity).
Before looking at the code, let's visualize the flow of data from the raw Pod to the final Hook Object.
Let's look at simplified versions of the actual functions Velero uses to parse these annotations. These are typically utility functions found in Velero's codebase.
First, we need to extract the command. Since it is stored as a JSON array string, we must parse it.
// Simplified Go Code
func getCommand(annotations map[string]string) []string {
// 1. Get the raw string from the map
val := annotations["post.hook.restore.velero.io/command"]
// 2. Prepare a list to hold the result
var command []string
// 3. Convert JSON text to Go List
// Example: '["ls", "-l"]' -> [ls, -l]
json.Unmarshal([]byte(val), &command)
return command
}
Explanation: If val contains valid JSON, command will become a usable list. If the user wrote invalid JSON, this function would return an error (omitted here for simplicity).
Next, we figure out where to run the command. If the user didn't specify a container, what do we do?
func getContainer(annotations map[string]string, pod *v1.Pod) string {
// 1. Check if the user specified a container
name := annotations["post.hook.restore.velero.io/container"]
// 2. Logic: If provided, use it.
if name != "" {
return name
}
// 3. Defaulting Logic:
// If blank, default to the FIRST container in the Pod.
return pod.Spec.Containers[0].Name
}
Explanation: This logic ensures the hook always has a target. It assumes that if you didn't ask for a specific container, the main application (usually the first one) is the intended target.
Timeouts are strings like "30s" or "5m". We need to convert them into a computer-readable duration.
import "time"
func getTimeout(annotations map[string]string) time.Duration {
val := annotations["post.hook.restore.velero.io/timeout"]
// 1. Defaulting Logic: If empty, pick a safe number
if val == "" {
return 30 * time.Second // Default
}
// 2. Parse the string (e.g., "5m")
duration, err := time.ParseDuration(val)
if err != nil {
return 30 * time.Second // Fallback if typo
}
return duration
}
Explanation: This function protects Velero from hanging. Whether the user forgot the timeout or wrote "55zz" (typo), the code ensures we always return a valid time.Duration.
Finally, what happens if the script fails? Valid options are usually Continue or Fail.
func getOnError(annotations map[string]string) string {
val := annotations["post.hook.restore.velero.io/on-error"]
// 1. Defaulting Logic
if val == "" {
// By default, don't crash the restore if a cleanup script fails
return "Continue"
}
// 2. Return user preference
return val
}
Explanation: Defaults act as "Safe Mode." For Exec hooks, Velero usually prefers Continue so that a minor script failure doesn't mark the whole database restore as "Failed."
When we combine these snippets, we get a full Hook Object.
Input (User Annotation):
command: '["/bin/echo", "hello"]'
# Container, Timeout, and OnError are MISSING
Internal Logic Execution:
getCommand -> ["/bin/echo", "hello"]getContainer -> Returns "main-app" (First container)getTimeout -> Returns 30s (Default)getOnError -> Returns Continue (Default)Output (Final Object used in Chapter 6):
Hook{
Command: ["/bin/echo", "hello"],
Container: "main-app",
Timeout: 30 * time.Second,
OnError: "Continue",
}
Congratulations! You have completed the Velero Restore Hooks tutorial series.
We started with a simple problem—files restored, but applications broken—and journeyed all the way to the internal code that parses text annotations.
Recap of your journey:
You now possess a deep understanding of how Velero manipulates Kubernetes workloads to ensure your restores are not just a pile of files, but fully functional applications.
Happy Restoring!
Generated by Code IQ