diff --git a/cmd/ops-runner/main.go b/cmd/ops-runner/main.go deleted file mode 100644 index f4b70af..0000000 --- a/cmd/ops-runner/main.go +++ /dev/null @@ -1,132 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "ops-assistant/internal/core/ops" -) - -func main() { - if len(os.Args) < 4 { - fmt.Println("usage: ops-runner ") - os.Exit(2) - } - dbPath := os.Args[1] - baseDir := os.Args[2] - cmd := os.Args[3] - - parts := strings.Fields(cmd) - if len(parts) < 2 { - fmt.Println("ERR: invalid command") - os.Exit(2) - } - - switch { - case len(parts) >= 2 && parts[0] == "/cf" && parts[1] == "dnsadd": - if len(parts) < 4 { - fmt.Println("ERR: /cf dnsadd [on|off] [type]") - os.Exit(2) - } - inputs := map[string]string{ - "name": parts[2], - "content": parts[3], - "type": "A", - "proxied": "false", - } - if len(parts) >= 5 { - switch strings.ToLower(parts[4]) { - case "on": - inputs["proxied"] = "true" - if len(parts) >= 6 { - inputs["type"] = parts[5] - } - case "off": - inputs["proxied"] = "false" - if len(parts) >= 6 { - inputs["type"] = parts[5] - } - case "true": - inputs["proxied"] = "true" - if len(parts) >= 6 { - inputs["type"] = parts[5] - } - case "false": - inputs["proxied"] = "false" - if len(parts) >= 6 { - inputs["type"] = parts[5] - } - default: - inputs["type"] = parts[4] - } - } - jobID, _, err := ops.RunOnce(dbPath, filepath.Clean(baseDir), cmd, "cf_dns_add", 1, inputs) - if err != nil { - fmt.Printf("ERR: %v\n", err) - os.Exit(1) - } - fmt.Printf("OK job=%d\n", jobID) - - case len(parts) >= 2 && parts[0] == "/cf" && parts[1] == "dnsproxy": - if len(parts) < 4 { - fmt.Println("ERR: /cf dnsproxy on|off") - os.Exit(2) - } - mode := strings.ToLower(parts[3]) - if mode != "on" && mode != "off" { - fmt.Println("ERR: /cf dnsproxy on|off") - os.Exit(2) - } - proxied := "false" - if mode == "on" { - proxied = "true" - } - target := parts[2] - inputs := map[string]string{ - "proxied": proxied, - "record_id": "__empty__", - "name": "__empty__", - } - if strings.Contains(target, ".") { - inputs["name"] = target - } else { - inputs["record_id"] = target - } - jobID, _, err := ops.RunOnce(dbPath, filepath.Clean(baseDir), cmd, "cf_dns_proxy", 1, inputs) - if err != nil { - fmt.Printf("ERR: %v\n", err) - os.Exit(1) - } - fmt.Printf("OK job=%d\n", jobID) - - case len(parts) >= 2 && parts[0] == "/cpa" && parts[1] == "status": - jobID, _, err := ops.RunOnce(dbPath, filepath.Clean(baseDir), cmd, "cpa_status", 1, map[string]string{}) - if err != nil { - fmt.Printf("ERR: %v\n", err) - os.Exit(1) - } - fmt.Printf("OK job=%d\n", jobID) - case len(parts) >= 3 && parts[0] == "/cpa" && parts[1] == "usage" && parts[2] == "backup": - jobID, _, err := ops.RunOnce(dbPath, filepath.Clean(baseDir), cmd, "cpa_usage_backup", 1, map[string]string{}) - if err != nil { - fmt.Printf("ERR: %v\n", err) - os.Exit(1) - } - fmt.Printf("OK job=%d\n", jobID) - case len(parts) >= 4 && parts[0] == "/cpa" && parts[1] == "usage" && parts[2] == "restore": - inputs := map[string]string{ - "backup_id": parts[3], - } - jobID, _, err := ops.RunOnce(dbPath, filepath.Clean(baseDir), cmd, "cpa_usage_restore", 1, inputs) - if err != nil { - fmt.Printf("ERR: %v\n", err) - os.Exit(1) - } - fmt.Printf("OK job=%d\n", jobID) - default: - fmt.Println("ERR: unsupported command") - os.Exit(2) - } -} diff --git a/dist/ops-assistant-v0.0.1-linux-amd64 b/dist/ops-assistant-v0.0.1-linux-amd64 deleted file mode 100755 index dd4b40c..0000000 Binary files a/dist/ops-assistant-v0.0.1-linux-amd64 and /dev/null differ diff --git a/dist/ops-assistant-v0.0.1-linux-amd64.sha256 b/dist/ops-assistant-v0.0.1-linux-amd64.sha256 deleted file mode 100644 index 2b3797f..0000000 --- a/dist/ops-assistant-v0.0.1-linux-amd64.sha256 +++ /dev/null @@ -1 +0,0 @@ -55bfe12944a42957532b9f63492d9ed8ca600419c4352ffa35344a62598bc019 dist/ops-assistant-v0.0.1-linux-amd64 diff --git a/docs/debug/cf-dnsproxy-dnsadd-20260319.md b/docs/debug/cf-dnsproxy-dnsadd-20260319.md deleted file mode 100644 index c9234fe..0000000 --- a/docs/debug/cf-dnsproxy-dnsadd-20260319.md +++ /dev/null @@ -1,79 +0,0 @@ -# CF DNS 命令修复与扩展记录(2026-03-19) - -## 背景 -用户要求: -- `/cf dnsproxy` 支持直接用域名,例如:`/cf dnsproxy ima.good.xx.kg on` -- `/cf dnsadd` 最后参数用 `on/off` 表示是否开启代理 - -线上报错: -- `yaml: line 8: did not find expected key` -- `/cf dnsproxy` 解析失败(bash: bad substitution) - -## 改动概览 -1) **命令解析** -- `internal/module/cf/commands.go` - - `/cf dnsproxy` 支持 `record_id|name` - - `/cf dnsadd` 支持 `on/off`(兼容 true/false;当未提供 on/off 时把第4参数视为类型) - -2) **帮助文案** -- `internal/module/cf/module.go` -- `internal/core/ops/service.go` - - 更新 `/cf dnsadd` 与 `/cf dnsproxy` 的参数示例 - -3) **runbook 修复** -- `runbooks/cf_dns_proxy.yaml` - - 解决 YAML 行内命令渲染与变量替换问题 - - 修复 `${env.INPUT_RECORD_ID}` 未替换导致 bash 报错 - - 加入占位值 `__empty__`,避免空变量导致替换缺失 - - `update_dns` 中 JSON 通过单引号包裹,避免 shell 分词/换行破坏 - -4) **ops-runner 支持** -- `cmd/ops-runner/main.go` - - 增加 `/cf dnsproxy` 支持 - - `/cf dnsadd` 参数改为 on/off - -## 问题与修复记录 -### 1. YAML 解析错误 -- 现象:`yaml: line 8: did not find expected key` -- 原因:runbook 中 command 复杂引号/换行组合导致 YAML 解析失败 -- 修复:重写 `cf_dns_proxy.yaml` command 区块 - -### 2. dnsproxy 变量替换失败 -- 现象:`bash: ${env.INPUT_RECORD_ID}: bad substitution` -- 原因:输入为空时,没有替换占位,shell 直接解析 `${env.INPUT_RECORD_ID}` -- 修复:InputsFn 总是注入 `record_id/name` 占位值,runbook 将 `__empty__` 转为空 - -### 3. dnsproxy update 失败(JSON 被 shell 吞掉) -- 现象:`bash: line 1: true,: command not found` -- 原因:`${steps.resolve_dns.output}` 未加引号,JSON 被 shell 拆分 -- 修复:`INPUT_JSON='${steps.resolve_dns.output}'` - -### 4. dnsadd on/off 支持 -- 现象:`DNS record type "on" is invalid` -- 原因:解析逻辑未识别 on/off,误当作类型 -- 修复:InputsFn 与 ops-runner 同步支持 `on/off` - -### 5. 测试记录创建失败(127.0.0.1) -- 现象:`Target 127.0.0.1 is not allowed for a proxied record` -- 处理:改用公网 IP 199.188.198.12 - -## 测试结果 -1) 新增测试记录 -``` -/cf dnsadd test001.good.xx.kg 199.188.198.12 on -``` -- 成功创建,proxied=true - -2) 代理切换 -``` -/cf dnsproxy ima.good.xx.kg on -``` -- 成功更新,proxied=true - -## 产物 -- 修复代码与 runbook -- 版本化二进制输出(dist/ 目录) - -## 注意事项 -- proxied=on 不能指向 127.0.0.1 等内网回环地址 -- runbook command 中 JSON 建议统一使用单引号包裹 diff --git a/internal/core/ops/service.go b/internal/core/ops/service.go deleted file mode 100644 index 7f5a219..0000000 --- a/internal/core/ops/service.go +++ /dev/null @@ -1,100 +0,0 @@ -package ops - -import ( - "fmt" - "strings" - - "gorm.io/driver/sqlite" - "gorm.io/gorm" - "ops-assistant/internal/core/command" - coremodule "ops-assistant/internal/core/module" - "ops-assistant/internal/core/registry" -) - -type Service struct { - dbPath string - baseDir string - registry *registry.Registry - db *gorm.DB -} - -func NewService(dbPath, baseDir string, reg *registry.Registry) *Service { - db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) - return &Service{dbPath: dbPath, baseDir: baseDir, registry: reg, db: db} -} - -func (s *Service) Handle(userID int64, text string) (bool, string) { - if !strings.HasPrefix(strings.TrimSpace(text), "/") { - return false, "" - } - cmd, _, err := command.ParseWithInputs(text) - if err != nil { - return false, "" - } - // 通用帮助 - if cmd.Module == "help" || cmd.Name == "/help" || cmd.Name == "/start" { - return true, s.helpText() - } - if cmd.Module == "ops" && (len(cmd.Args) == 0 || cmd.Args[0] == "help") { - return true, s.helpText() - } - if cmd.Module == "ops" && len(cmd.Args) > 0 && cmd.Args[0] == "modules" { - return true, s.modulesStatusText() - } - if cmd.Module != "" && cmd.Module != "ops" && s.db != nil { - if !coremodule.IsEnabled(s.db, cmd.Module) { - return true, fmt.Sprintf("[ERR_FEATURE_DISABLED] 模块未启用: %s(开关: enable_module_%s)", cmd.Module, cmd.Module) - } - } - out, handled, err := s.registry.Handle(userID, cmd) - if !handled { - return false, "" - } - if err != nil { - return true, "❌ OPS 执行失败: " + err.Error() - } - return true, out -} - -func (s *Service) helpText() string { - lines := []string{ - "🛠️ OPS 交互命令:", - "- /ops modules (查看模块启用状态)", - "- /cpa help", - "- /cpa status", - "- /cpa usage backup", - "- /cpa usage restore [--confirm YES_RESTORE] [--dry-run]", - "- /cf status (需要 enable_module_cf)", - "- /cf zones (需要 enable_module_cf)", - "- /cf dns list (需要 enable_module_cf)", - "- /cf dns update [ttl] [proxied:true|false] (需要 enable_module_cf)", - "- /cf dnsadd [on|off] [type] (需要 enable_module_cf)", - "- /cf dnsset [true] (需要 enable_module_cf)", - "- /cf dnsdel YES (需要 enable_module_cf)", - "- /cf dnsproxy on|off (需要 enable_module_cf)", - "- /cf workers list (需要 enable_module_cf)", - "- /mail status (需要 enable_module_mail)", - } - return strings.Join(lines, "\n") -} - -func (s *Service) modulesStatusText() string { - mods := s.registry.ListModules() - if len(mods) == 0 { - return "暂无已注册模块" - } - lines := []string{"🧩 模块状态:"} - for _, m := range mods { - enabled := false - if s.db != nil { - enabled = coremodule.IsEnabled(s.db, m) - } - state := "disabled" - if enabled { - state = "enabled" - } - lines = append(lines, fmt.Sprintf("- %s: %s", m, state)) - } - lines = append(lines, "\n可用命令:/ops modules") - return strings.Join(lines, "\n") -} diff --git a/internal/module/cf/commands.go b/internal/module/cf/commands.go deleted file mode 100644 index fbab11a..0000000 --- a/internal/module/cf/commands.go +++ /dev/null @@ -1,254 +0,0 @@ -package cf - -import ( - "fmt" - "strings" - - "ops-assistant/internal/core/ecode" - coremodule "ops-assistant/internal/core/module" -) - -func commandSpecs() []coremodule.CommandSpec { - return []coremodule.CommandSpec{ - { - Prefixes: []string{"/cf status"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_status", - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf status(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf status 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf status 执行失败: ", - }, - { - Prefixes: []string{"/cf zones"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_zones", - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf zones(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf zones 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf zones 执行失败: ", - }, - { - Prefixes: []string{"/cf dns list"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_dns_list", - InputsFn: func(_ string, parts []string) (map[string]string, error) { - if len(parts) < 4 { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数不足,示例:/cf dns list ")) - } - return map[string]string{"zone_id": parts[3]}, nil - }, - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf dns list (未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf dns list 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf dns list 执行失败: ", - ErrHint: "/cf dns list ", - }, - { - Prefixes: []string{"/cf dns update"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_dns_update", - InputsFn: func(_ string, parts []string) (map[string]string, error) { - if len(parts) < 8 { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数不足,示例:/cf dns update [ttl] [proxied]")) - } - inputs := map[string]string{ - "zone_id": parts[3], - "record_id": parts[4], - "type": parts[5], - "name": parts[6], - "content": parts[7], - } - if len(parts) >= 9 { - inputs["ttl"] = parts[8] - } - if len(parts) >= 10 { - inputs["proxied"] = parts[9] - } - return inputs, nil - }, - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf dns update(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf dns update 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf dns update 执行失败: ", - ErrHint: "/cf dns update [ttl] [proxied:true|false]", - }, - { - Prefixes: []string{"/cf dnsadd"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_dns_add", - InputsFn: func(_ string, parts []string) (map[string]string, error) { - if len(parts) < 4 { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数不足,示例:/cf dnsadd [on|off] [type]")) - } - name := parts[2] - content := parts[3] - proxied := "false" - recType := "A" - if len(parts) >= 5 { - switch strings.ToLower(parts[4]) { - case "on": - proxied = "true" - if len(parts) >= 6 { - recType = parts[5] - } - case "off": - proxied = "false" - if len(parts) >= 6 { - recType = parts[5] - } - case "true": - proxied = "true" - if len(parts) >= 6 { - recType = parts[5] - } - case "false": - proxied = "false" - if len(parts) >= 6 { - recType = parts[5] - } - default: - // treat as type when no on/off provided - recType = parts[4] - } - } - inputs := map[string]string{ - "name": name, - "content": content, - "type": strings.ToUpper(recType), - "proxied": strings.ToLower(proxied), - } - return inputs, nil - }, - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf dnsadd(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf dnsadd 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf dnsadd 执行失败: ", - ErrHint: "/cf dnsadd [on|off] [type]", - }, - { - Prefixes: []string{"/cf dnsset"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_dns_set", - InputsFn: func(_ string, parts []string) (map[string]string, error) { - if len(parts) < 4 { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数不足,示例:/cf dnsset [true]")) - } - proxied := "false" - if len(parts) >= 5 && strings.EqualFold(parts[4], "true") { - proxied = "true" - } - return map[string]string{ - "record_id": parts[2], - "content": parts[3], - "proxied": strings.ToLower(proxied), - }, nil - }, - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf dnsset(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf dnsset 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf dnsset 执行失败: ", - ErrHint: "/cf dnsset [true]", - }, - { - Prefixes: []string{"/cf dnsdel"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_dns_del", - InputsFn: func(_ string, parts []string) (map[string]string, error) { - if len(parts) < 4 { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数不足,示例:/cf dnsdel YES")) - } - if len(parts) < 4 || !strings.EqualFold(parts[3], "YES") { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "缺少确认词 YES,示例:/cf dnsdel YES")) - } - return map[string]string{ - "record_id": parts[2], - }, nil - }, - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: false, - }, - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf dnsdel 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf dnsdel 执行失败: ", - ErrHint: "/cf dnsdel YES", - }, - { - Prefixes: []string{"/cf dnsproxy"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_dns_proxy", - InputsFn: func(_ string, parts []string) (map[string]string, error) { - if len(parts) < 4 { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数不足,示例:/cf dnsproxy on|off")) - } - mode := strings.ToLower(parts[3]) - if mode != "on" && mode != "off" { - return nil, fmt.Errorf(ecode.Tag(ecode.ErrStepFailed, "参数无效,示例:/cf dnsproxy on|off")) - } - proxied := "false" - if mode == "on" { - proxied = "true" - } - inputs := map[string]string{ - "proxied": proxied, - "record_id": "__empty__", - "name": "__empty__", - } - target := parts[2] - if strings.Contains(target, ".") { - inputs["name"] = target - } else { - inputs["record_id"] = target - } - return inputs, nil - }, - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf dnsproxy(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf dnsproxy 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf dnsproxy 执行失败: ", - ErrHint: "/cf dnsproxy on|off", - }, - { - Prefixes: []string{"/cf workers list"}, - Template: coremodule.CommandTemplate{ - RunbookName: "cf_workers_list", - Gate: coremodule.Gate{ - NeedFlag: "enable_module_cf", - AllowDryRun: true, - }, - DryRunMsg: "🧪 dry-run: 将执行 /cf workers list(未实际执行)", - SuccessMsg: func(jobID uint) string { return fmt.Sprintf("✅ /cf workers list 已执行,job=%d", jobID) }, - }, - ErrPrefix: "/cf workers list 执行失败: ", - }, - } -} diff --git a/internal/module/cf/module.go b/internal/module/cf/module.go deleted file mode 100644 index 332af4b..0000000 --- a/internal/module/cf/module.go +++ /dev/null @@ -1,40 +0,0 @@ -package cf - -import ( - "strings" - - "ops-assistant/internal/core/command" - "ops-assistant/internal/core/ecode" - coremodule "ops-assistant/internal/core/module" - "ops-assistant/internal/core/runbook" - "gorm.io/gorm" -) - -type Module struct { - db *gorm.DB - exec *runbook.Executor - runner *coremodule.Runner -} - -func New(db *gorm.DB, exec *runbook.Executor) *Module { - return &Module{db: db, exec: exec, runner: coremodule.NewRunner(db, exec)} -} - -func (m *Module) Handle(userID int64, cmd *command.ParsedCommand) (string, error) { - text := strings.TrimSpace(cmd.Raw) - if text == "/cf" || strings.HasPrefix(text, "/cf help") { - return "CF 模块\n- /cf status [--dry-run]\n- /cf zones\n- /cf dns list \n- /cf dns update [ttl] [proxied:true|false]\n- /cf dnsadd [on|off] [type]\n- /cf dnsset [true]\n- /cf dnsdel YES\n- /cf dnsproxy on|off\n- /cf workers list", nil - } - specs := commandSpecs() - if sp, ok := coremodule.MatchCommand(text, specs); ok { - jobID, out, err := coremodule.ExecTemplate(m.runner, userID, cmd.Raw, sp.Template) - if err != nil { - return ecode.Tag(ecode.ErrStepFailed, coremodule.FormatExecError(sp, err)), nil - } - if out == "dry-run" { - return ecode.Tag("OK", coremodule.FormatDryRunMessage(sp.Template)), nil - } - return ecode.Tag("OK", coremodule.FormatSuccessMessage(sp.Template, jobID)), nil - } - return ecode.Tag(ecode.ErrStepFailed, "CF 模块已接入,当前支持:/cf status, /cf help"), nil -} diff --git a/ops-runner b/ops-runner deleted file mode 100755 index 6b79ff5..0000000 Binary files a/ops-runner and /dev/null differ diff --git a/runbooks/cf_dns_proxy.yaml b/runbooks/cf_dns_proxy.yaml deleted file mode 100644 index 61e423f..0000000 --- a/runbooks/cf_dns_proxy.yaml +++ /dev/null @@ -1,30 +0,0 @@ -version: 1 -name: cf_dns_proxy -description: 修改 DNS 代理开关(按 record_id 或 name) -steps: - - id: resolve_dns - action: ssh.exec - on_fail: stop - with: - target: hwsg - command: "CF_API_TOKEN=${env_cf_api_token} INPUT_RECORD_ID=${env.INPUT_RECORD_ID} INPUT_NAME=${env.INPUT_NAME} python3 - <<'PY'\nimport os,requests,json\nrec=os.getenv('INPUT_RECORD_ID','').strip()\nname=os.getenv('INPUT_NAME','').strip()\nif rec=='__empty__':\n rec=''\nif name=='__empty__':\n name=''\ntoken=os.getenv('CF_API_TOKEN','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\nresp=requests.get('https://api.cloudflare.com/client/v4/zones?per_page=200', headers=headers, timeout=15)\nresp.raise_for_status()\nfor z in resp.json().get('result',[]):\n zid=z.get('id')\n if rec:\n r=requests.get(f'https://api.cloudflare.com/client/v4/zones/{zid}/dns_records/{rec}', headers=headers, timeout=15)\n if r.status_code==200:\n data=r.json()\n if data.get('success') and data.get('result'):\n out=data.get('result')\n out['_zone_id']=zid\n print(json.dumps({'success':True,'result':out}))\n raise SystemExit(0)\n continue\n if name:\n r=requests.get(f'https://api.cloudflare.com/client/v4/zones/{zid}/dns_records', headers=headers, params={'name': name, 'per_page': 100}, timeout=15)\n if r.status_code==200:\n data=r.json()\n if data.get('success') and data.get('result'):\n rec0=data['result'][0]\n rec0['_zone_id']=zid\n print(json.dumps({'success':True,'result':rec0}))\n raise SystemExit(0)\nprint(json.dumps({'success':False,'errors':['record_not_found']}))\nPY" - - id: assert_resolve - action: assert.json - on_fail: stop - with: - source_step: resolve_dns - required_paths: - - "success" - - id: update_dns - action: ssh.exec - on_fail: stop - with: - target: hwsg - command: "CF_API_TOKEN=${env_cf_api_token} INPUT_PROXIED=${env.INPUT_PROXIED} INPUT_JSON='${steps.resolve_dns.output}' python3 - <<'PY'\nimport os,requests,json\nproxied=os.getenv('INPUT_PROXIED','false').lower()=='true'\ntoken=os.getenv('CF_API_TOKEN','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\nraw=os.getenv('INPUT_JSON','')\ntry:\n data=json.loads(raw)\nexcept Exception:\n data={}\nres=data.get('result') or {}\nzone_id=res.get('_zone_id')\nrec_id=res.get('id')\nif not zone_id or not rec_id:\n print(json.dumps({'success':False,'errors':['record_not_found']}))\n raise SystemExit(1)\npayload={\n 'type': res.get('type'),\n 'name': res.get('name'),\n 'content': res.get('content'),\n 'proxied': proxied\n}\nresp=requests.put(f'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec_id}', headers=headers, json=payload, timeout=15)\nprint(resp.text)\nPY" - - id: assert_update - action: assert.json - on_fail: stop - with: - source_step: update_dns - required_paths: - - "success"