From 62ecbf633d20b0fc79c60a497a91029171e1d97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louren=C3=A7o=20Vales?= <133565059+lourencovales@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:13:12 +0200 Subject: [PATCH] engine: resources: Add Cloudflare DNS resource --- engine/resources/cloudflare_dns.go | 128 +++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 engine/resources/cloudflare_dns.go diff --git a/engine/resources/cloudflare_dns.go b/engine/resources/cloudflare_dns.go new file mode 100644 index 00000000..a738b736 --- /dev/null +++ b/engine/resources/cloudflare_dns.go @@ -0,0 +1,128 @@ +// Mgmt +// Copyright (C) James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this program, or any covered work, by linking or combining it +// with embedded mcl code and modules (and that the embedded mcl code and +// modules which link with this program, contain a copy of their source code in +// the authoritative form) containing parts covered by the terms of any other +// license, the licensors of this program grant you additional permission to +// convey the resulting work. Furthermore, the licensors of this program grant +// the original author, James Shubin, additional permission to update this +// additional permission if he deems it necessary to achieve the goals of this +// additional permission. + +package resources + +import ( + "context" + "fmt" + + "github.com/cloudflare/cloudflare-go/v6" + "github.com/purpleidea/mgmt/engine" + "github.com/purpleidea/mgmt/engine/traits" +) + +func init() { + engine.RegisterResource("cloudflare:dns", func() engine.Res { return &CloudflareDNSRes{} }) +} + +// TODO: description of cloudflare_dns resource +type CloudflareDNSRes struct { + traits.Base + init *engine.Init + + APIToken string `lang:"apitoken"` + + Name string `lang:"name"` + + TTL int `lang:"ttl"` + + Type string `lang:"type"` + + Zone string `lang:"zone"` + + client *cloudflare.API +} + +func (obj *CloudflareDNSRes) Validate() error { + if obj.Name == "" { + return fmt.Errorf("record name is required") + } + + if obj.APIToken == "" { + return fmt.Errorf("API token is required") + } + + if obj.Type == "" { + return fmt.Errorf("record type is required") + } + + if obj.TTL < 60 || obj.TTL > 86400 { // API requirement + return fmt.Errorf("TTL must be between 60 and 86400 seconds") + } + + if obj.Zone == "" { + return fmt.Errof("zone name is required") + } + + return nil +} + +func (obj *CloudflareDNSRes) Init(init *engine.Init) error { + obj.init = init + + api, err := cloudflare.NewWithAPIToken(obj.APIToken) + if err != nil { + return fmt.Errorf("failed to init Cloudflare API client") + } + + obj.client = api + + return nil +} + +func (obj *CloudflareDNSRes) Cleanup() error { + obj.APIToken = "" + obj.client = nil + return nil +} + +// Watch isn't implemented for this resource, since the Cloudflare API does not +// provide any event stream. Instead, always use polling. +func (obj *CloudflareDNSRes) Watch(context.Context) error { + return fmt.Errorf("invalid Watch call: requires poll metaparam") +} + +func (obj *CloudflareDNSRes) CheckApply(ctx context.Context, apply bool) (bool, error) { + zone, err := obj.client.Zones.List(ctx, zones.ZoneListParams{ + Name: cloudflare.F(obj.Zone), + }) + if err != nil { + return false, fmt.Errorf(err) + } + + if len(zone.Result) == 0 { + return false, fmt.Errorf("there's no zone registered with name %s", obj.Zone) + } + + if len(zone.Result) > 1 { + return false, fmt.Errorf("there's more than one zone with name %s", obj.Zone) + } + +}