306 lines
8.0 KiB
Go
306 lines
8.0 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/maximotejeda/ddns/internal/adapter/checker"
|
|
prov "github.com/maximotejeda/ddns/internal/adapter/provider"
|
|
"github.com/maximotejeda/ddns/internal/application/core/domain"
|
|
"github.com/maximotejeda/ddns/internal/application/core/domain/cf"
|
|
"github.com/maximotejeda/ddns/internal/application/core/helpers"
|
|
"github.com/maximotejeda/ddns/internal/port"
|
|
)
|
|
|
|
type Application struct {
|
|
ctx context.Context
|
|
log *slog.Logger
|
|
client port.Provider
|
|
publicIP *net.IP
|
|
zoneRecords []cf.Result
|
|
Domains map[string]domain.DomainRecords
|
|
}
|
|
|
|
// NewApplication
|
|
// Returns an instance to a new app with a provider client
|
|
// Needs some info from the account to work on
|
|
// Params:
|
|
//
|
|
// provider: provider client to interact with
|
|
// toke: token to authorize actions on provider
|
|
// id: id of user or zoner where to work with
|
|
func NewApplication(ctx context.Context, log *slog.Logger, provider, token, id string) (app *Application, err error) {
|
|
log.Debug("creating new appication")
|
|
var pClient port.Provider
|
|
switch provider {
|
|
case "cf":
|
|
pClient = prov.NewCloudflareClient(ctx, log, id, token)
|
|
}
|
|
logApp := log.With("location", "[Application]")
|
|
app = &Application{
|
|
ctx: ctx,
|
|
log: logApp,
|
|
client: pClient,
|
|
}
|
|
return app, nil
|
|
}
|
|
|
|
// Show
|
|
// Print in stdout the dns records for zone
|
|
func (app *Application) Show(name string) {
|
|
app.log.Debug("calling show")
|
|
fmt.Printf("%22s %5s \t%14s \t%s\n\n", "name", "type", "ip", "ID")
|
|
for _, it := range app.zoneRecords {
|
|
subDomainName := strings.ReplaceAll(strings.ReplaceAll(it.Name, name, ""), ".", "")
|
|
if subDomainName == "" {
|
|
subDomainName = "*root*"
|
|
}
|
|
fmt.Printf("%+22s %5s \t%14s \t%s\n", subDomainName, it.Type, it.Content, it.ID)
|
|
}
|
|
checkClient := checker.NewClient(app.log, "cf")
|
|
ip, err := checkClient.Do()
|
|
if err != nil {
|
|
app.log.Error("[checker] ip", "error", err)
|
|
}
|
|
fmt.Printf("\nrequester IP => %s\n", ip)
|
|
}
|
|
|
|
// Details ...
|
|
// Print details on a given record
|
|
func (app *Application) Details(id string) {
|
|
app.log.Debug("calling details")
|
|
resp, err := app.client.Details(id)
|
|
if err != nil {
|
|
app.log.Error("[datails]", "error", err)
|
|
}
|
|
fmt.Printf(`
|
|
Zone Name: %s - ID: %s - Domain Name: %s
|
|
Type: %s - IP: %s - Proxied: %t
|
|
Comment: %s
|
|
Created On: %s
|
|
Modified On: %s
|
|
|
|
Meta: %#v
|
|
Tags: %#v
|
|
TTL: %d
|
|
|
|
`,
|
|
resp.Result.ZoneName, resp.Result.ID, resp.Result.Name, resp.Result.Type,
|
|
resp.Result.Content, resp.Result.Proxied, resp.Result.Comment,
|
|
resp.Result.CreatedOn, resp.Result.ModifiedOn, resp.Result.Meta,
|
|
resp.Result.Tags, resp.Result.TTL)
|
|
}
|
|
|
|
// List
|
|
func (app *Application) List() {
|
|
resp, err := app.client.List()
|
|
if err != nil {
|
|
app.log.Error("[Show]", "error", err)
|
|
panic(err)
|
|
}
|
|
app.zoneRecords = resp.Result
|
|
}
|
|
|
|
// Update
|
|
func (app *Application) Update(re *cf.Result, rBody cf.RequestBody) {
|
|
rBody.Comment = "updating from app cli tool"
|
|
res, err := app.client.Update(re.ID, &rBody)
|
|
if err != nil {
|
|
app.log.Error("[Update]", "error", err)
|
|
}
|
|
app.log.Debug("updating", "record", re.Name, "IP", rBody.Content, "Type", re.Type, "ID", "re.ID")
|
|
app.log.Info("response", "success", res.Success, "errors", res.Errors)
|
|
}
|
|
|
|
// Create
|
|
func (app *Application) Create(rBody cf.RequestBody) (res *cf.DetailsResult, err error) {
|
|
rBody.Comment = "Creating from app cli tool"
|
|
res, err = app.client.Create(&rBody)
|
|
if err != nil {
|
|
app.log.Error("[Create]", "error", err)
|
|
}
|
|
return res, err
|
|
}
|
|
|
|
// Overwrite
|
|
func (app *Application) Overwrite(re *cf.Result, rBody *cf.RequestBody) {
|
|
rBody.Comment = rBody.Comment + " Overwrite from app cli tool"
|
|
res, err := app.client.Overwrite(*rBody, re.ID)
|
|
if err != nil {
|
|
app.log.Error("[Overwrite]", "error", err)
|
|
}
|
|
fmt.Println("Overwrite:")
|
|
fmt.Printf("%v", res)
|
|
}
|
|
|
|
// Delete
|
|
func (app Application) Delete(re *cf.Result) {
|
|
res, err := app.client.Delete(re.ID)
|
|
if err != nil {
|
|
app.log.Error("[Delete]", "error", err)
|
|
}
|
|
fmt.Println("Delete: ")
|
|
fmt.Printf("%v", res)
|
|
}
|
|
|
|
// Operation
|
|
// expose required
|
|
func (app *Application) Operation(op string, name, tipo, ipSTR,rID, comment string, proxied bool) {
|
|
var (
|
|
res *checker.Response
|
|
err error
|
|
rBody *cf.RequestBody
|
|
)
|
|
// check current ip
|
|
func() {
|
|
checker := checker.NewClient(app.log, "cf")
|
|
res, err = checker.Do()
|
|
if err != nil {
|
|
app.log.Error("checking public ip", "error", err)
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
if ipSTR == "" {
|
|
app.publicIP = res.IP
|
|
tipo = res.Type
|
|
|
|
} else {
|
|
ip1 := net.ParseIP(ipSTR)
|
|
app.publicIP = &ip1
|
|
if ip1.To4() != nil {
|
|
tipo = "A"
|
|
} else if ip1.To16() != nil {
|
|
tipo = "AAA"
|
|
}
|
|
}
|
|
// populate struct with dns records
|
|
// and populate subdomains inside each domain
|
|
app.List()
|
|
app.Domains, _ = helpers.SubdomainIdentify(name, app.zoneRecords)
|
|
|
|
dn := helpers.SelectDomain(name, app.Domains) // will always be available if subdomain is valid
|
|
rs := app.SelectRecord(dn, tipo)
|
|
if rs == nil {
|
|
app.log.Debug("record not selected", "ip", ipSTR, "name", name, "type", tipo)
|
|
}
|
|
//fmt.Println(dn, rs, app.zoneRecords)
|
|
app.Domains, _ = helpers.SubdomainIdentify(name, app.zoneRecords)
|
|
if res == nil {
|
|
fmt.Printf("%#v", app.Domains)
|
|
panic("record does not exist")
|
|
}
|
|
|
|
switch op {
|
|
case "update":
|
|
if name == "" {
|
|
panic("name cant be empty for op update")
|
|
}
|
|
if rs == nil {
|
|
if dn == "*" {
|
|
for _, rec := range app.zoneRecords {
|
|
if rec.Content != app.publicIP.String() {
|
|
rBody = app.GenerateReqBody(rec.Name, res.Type, app.publicIP.String(), comment, proxied)
|
|
app.Update(&rec, *rBody)
|
|
} else {
|
|
app.log.Info("same ip for", "record", rec.Name, "DstIP", rec.Content, "NewIP", app.publicIP)
|
|
}
|
|
}
|
|
} else {
|
|
app.log.Error(fmt.Sprintf("record is not created or active create first: %s -> %s", name, dn))
|
|
}
|
|
} else {
|
|
if rs.Content != app.publicIP.String() {
|
|
rBody = app.GenerateReqBody(name, res.Type, app.publicIP.String(),comment, proxied)
|
|
app.Update(rs, *rBody)
|
|
} else {
|
|
app.log.Error("same ip on dns record")
|
|
}
|
|
}
|
|
case "create":
|
|
// as we can have more than one subdomain/with the same name and diff ip
|
|
if name == "" {
|
|
panic("name cant be empty for op create")
|
|
}
|
|
fmt.Printf("domain name: %s", dn)
|
|
rBody = app.GenerateReqBody(dn, tipo, app.publicIP.String(),comment, proxied)
|
|
app.Create(*rBody)
|
|
case "delete":
|
|
if name == "" {
|
|
panic("name cant be empty for op delete")
|
|
}
|
|
if rs == nil {
|
|
app.log.Error(fmt.Sprintf("could not find record: %s -> %s", name, dn), "operation", "delete", "rs", rs)
|
|
} else {
|
|
app.Delete(rs)
|
|
|
|
}
|
|
case "overwrite":
|
|
if name == "" {
|
|
app.log.Error("name cant be empty for op overwrite")
|
|
}
|
|
if rs == nil {
|
|
|
|
app.log.Error(fmt.Sprintf("could not find record: %s -> %s", name, dn), "operation", "overwrite")
|
|
} else {
|
|
rBody = app.GenerateReqBody(dn, tipo, app.publicIP.String(), comment, proxied)
|
|
app.Overwrite(rs, rBody)
|
|
}
|
|
case "details":
|
|
if name == "" {
|
|
app.log.Error("name cant be empty for op details")
|
|
}
|
|
|
|
if rs != nil {
|
|
rBody = app.GenerateReqBody(dn, tipo, app.publicIP.String(), comment, proxied)
|
|
app.Details(rs.ID)
|
|
}
|
|
|
|
//rBody := app.GenerateReqBody(name, tipo, app.publicIP.String(), proxied)
|
|
if rs == nil {
|
|
app.log.Error("record does not exist")
|
|
}
|
|
|
|
case "show":
|
|
app.Show(name)
|
|
default:
|
|
app.log.Error("unknown operation", "operation", op)
|
|
panic("unknown operation")
|
|
}
|
|
}
|
|
|
|
// SelectRecord
|
|
func (app *Application) SelectRecord(name, tipo string) (res *cf.Result) {
|
|
|
|
res, _ = app.SelectNameAndType(name, tipo)
|
|
return res
|
|
}
|
|
|
|
// SelectNameAndType
|
|
func (app *Application) SelectNameAndType(name, tipo string) (rec *cf.Result, err error) {
|
|
i := slices.IndexFunc(app.zoneRecords, func(r cf.Result) bool {
|
|
return name == r.Name && strings.ToUpper(tipo) == r.Type
|
|
})
|
|
if i >= 0 {
|
|
return &app.zoneRecords[i], nil
|
|
} else {
|
|
return nil, errors.New("record not found")
|
|
}
|
|
}
|
|
|
|
func (app *Application) GenerateReqBody(name, tipo, ipSTR, comment string, proxied bool) (rBody *cf.RequestBody) {
|
|
rBody = &cf.RequestBody{}
|
|
rBody.Comment = comment
|
|
rBody.DomainName = name
|
|
rBody.Type = tipo
|
|
rBody.Content = ipSTR
|
|
rBody.Proxied = proxied
|
|
rBody.TTL = 3600
|
|
return rBody
|
|
}
|