Продвинутая работа с очередями
В этом примере показано, как собирать сложные сценарии на базе Queue
:
- разбивать процесс на этапы и добавлять задачи динамически;
- реагировать на выбор пользователя и результат задач;
- обрабатывать ошибки и повторные попытки без выхода из TUI.
Все примеры используют публичный API пакета ziva
. Их можно поместить в отдельный пакет examples
и запускать по мере необходимости.
Шаблон многоэтапного сценария
// DeployCluster запускает последовательный сценарий, который спрашивает настройки
// у оператора, отображает прогресс и сохраняет результаты для последующей логики.
func DeployCluster() error {
summary := ziva.NewFuncTask(
"Инициализация окружения",
runPreflightChecks,
ziva.WithSummaryFunction(func() []string {
return []string{"DNS: ok", "Сеть: ok"}
}),
ziva.WithStopOnError(true),
)
topology := ziva.NewSingleSelectTask(
"Топология",
[]string{
"single-node::Один узел",
"multi-node::Кластер из нескольких узлов",
"ha::Высокая доступность",
},
).WithDefaultItem("multi-node")
nodes := ziva.NewInputTask(
"Количество узлов",
"Сколько рабочих узлов разворачиваем?",
).
WithInputType(ziva.InputTypeNumber).
WithValidator(ziva.DefaultValidators.Range(1, 20)).
WithTimeout(30*time.Second, "3")
queue := ziva.NewQueue("Развертывание Ziva").
WithAppName("Ziva CLI").
WithTasksNumbered(true, "[%02d]")
queue.AddTasks(summary, topology, nodes)
if err := queue.Run(); err != nil {
return err
}
// В зависимости от выбранной топологии добавим дополнительные задачи.
followUp := ziva.NewQueue("Дополнительные шаги")
switch topology.GetSelected() {
case "single-node":
followUp.AddTasks(planBackupTask(), confirmMetricsTask())
case "multi-node":
followUp.AddTasks(configureIngressTask(), scaleOutTask(nodes))
case "ha":
followUp.AddTasks(haPrecheckTask(), configureQuorumTask(nodes))
}
return followUp.Run()
}
Ключевые идеи
- Два запуска очереди. Первая очередь собирает ввод и сохраняет задачи для повторного использования. Вторая очередь формируется динамически.
- Summary-функция.
FuncTask
с WithSummaryFunction
позволяет отобразить итог прямо под задачей и лучше информировать пользователя.
- Таймауты.
WithTimeout
гарантирует, что сценарий не зависнет в headless-среде.
Повторные попытки с подтверждением пользователя
func RunWithRetries(label string, fn func() error, maxAttempts int) error {
attempt := 1
for {
task := ziva.NewFuncTask(
fmt.Sprintf("%s · попытка %d", label, attempt),
fn,
ziva.WithStopOnError(false),
)
queue := ziva.NewQueue(label)
queue.AddTasks(task)
if err := queue.Run(); err == nil {
return nil
}
if attempt >= maxAttempts {
return fmt.Errorf("достигнут лимит попыток (%d)", maxAttempts)
}
retry := ziva.NewYesNoTask(
"Повторить попытку",
fmt.Sprintf("Попытка %d завершилась ошибкой. Повторить?", attempt),
).WithDefaultYesAndTimeout(10 * time.Second)
confirm := ziva.NewQueue("Решение о повторе")
confirm.AddTasks(retry)
confirm.Run()
if !retry.IsYes() {
return fmt.Errorf("операция отменена пользователем на попытке %d", attempt)
}
attempt++
}
}
Когда пригодится
- интеграция с нестабильными сервисами;
- обновления прошивок на устройствах с малонадежной связью;
- выполнение длительных действий, которые стоит перезапустить по согласию пользователя.
Построение задач на лету
func ConfigureServers() error {
count := ziva.NewInputTask("Количество серверов", "Сколько серверов конфигурируем?").
WithInputType(ziva.InputTypeNumber).
WithValidator(ziva.DefaultValidators.Range(1, 8))
baseQueue := ziva.NewQueue("Параметры конфигурации")
baseQueue.AddTasks(count)
if err := baseQueue.Run(); err != nil {
return err
}
n, _ := strconv.Atoi(count.GetValue())
var tasks []ziva.Task
for i := 1; i <= n; i++ {
prefix := fmt.Sprintf("Сервер %d", i)
host := ziva.NewInputTask(prefix+" · хост", "Введите FQDN")
host.WithValidator(ziva.DefaultValidators.Domain())
port := ziva.NewInputTask(prefix+" · порт", "Введите порт")
port.WithInputType(ziva.InputTypeNumber).
WithValidator(ziva.DefaultValidators.Port())
tasks = append(tasks, host, port)
}
servers := ziva.NewQueue("Настройка серверов").
WithTasksNumbered(true, "[%02d]")
servers.AddTasks(tasks...)
return servers.Run()
}
При генерации большого количества задач учитывайте возможности устройства. Если сценарий может создавать сотни шагов, разбивайте их на отдельные очереди и запускайте блоками.
Мини-чеклист для сложных сценариев
- Сохраняйте ссылки на задачи, чьи результаты понадобятся позже.
- Используйте отдельные очереди для разных этапов — это упрощает логику и вывод.
- Вызывайте
AutoConfigure()
при старте CLI, чтобы Ziva подобрал безопасные настройки.
- Для встроенных устройств комбинируйте
EnableEmbeddedMode()
и WithOutResultLine()
— так вывод остаётся компактным.
- Помните, что очередь последовательно выполняет задачи; если нужна «параллельность», запускайте отдельные очереди из
FuncTask
.
Следующий пример показывает, как использовать валидаторы для надёжных форм ввода.