Skip to content

Commit

Permalink
Added entities for work with DNS v2 API. (#249)
Browse files Browse the repository at this point in the history
Added selectel_domains_zone_v2 resource.
Added selectel_domains_rrset_v2 resource.
  • Loading branch information
dchudik authored Feb 26, 2024
1 parent 50ebb21 commit 3ee9b6a
Show file tree
Hide file tree
Showing 19 changed files with 1,434 additions and 17 deletions.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ require (
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1
github.com/selectel/craas-go v0.3.0
github.com/selectel/dbaas-go v0.10.0
github.com/selectel/domains-go v0.5.0
github.com/selectel/domains-go v1.0.2
github.com/selectel/go-selvpcclient/v3 v3.1.1
github.com/selectel/mks-go v0.12.0
github.com/stretchr/testify v1.7.2
github.com/stretchr/testify v1.8.4
)

require (
Expand Down Expand Up @@ -51,6 +51,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/tagparser v0.1.1 // indirect
Expand Down
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
Expand Down Expand Up @@ -153,8 +153,8 @@ github.com/selectel/craas-go v0.3.0 h1:tXiw3LNN+ZVV0wZdeBBXX6u8kMuA5PV/5W1uYqV0y
github.com/selectel/craas-go v0.3.0/go.mod h1:9RAUn9PdMITP4I3GAade6v2hjB2j3lo3J2dDlG5SLYE=
github.com/selectel/dbaas-go v0.10.0 h1:iY2Q7PY9ICoWBDtni+6oWGR2uAWodER0K2zchNLIOl4=
github.com/selectel/dbaas-go v0.10.0/go.mod h1:uyPhqmcvdmKBt9yWhogoSQgWkcZ9QgVlbgaERdSdAfk=
github.com/selectel/domains-go v0.5.0 h1:RCrWY/9KHVtfdA+X8M+DDzsjILxFChhY70HnJEu1Y2U=
github.com/selectel/domains-go v0.5.0/go.mod h1:AhXhwyMSTkpEWFiBLUvzFP76W+WN+ZblwmjLJLt7y58=
github.com/selectel/domains-go v1.0.2 h1:Si6iGaMnTFJxwiJVI50DOdZnwcxc87kqaWrVQYW0a4U=
github.com/selectel/domains-go v1.0.2/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
github.com/selectel/go-selvpcclient/v3 v3.1.1 h1:C1q2LqqosiapoLpnGITGmysg0YCSQYDo2Gh69CioevM=
github.com/selectel/go-selvpcclient/v3 v3.1.1/go.mod h1:NM7IXhh1IzqZ88DOw1Qc5Ez3tULLViXo95l5+rKPuyQ=
github.com/selectel/mks-go v0.12.0 h1:nLWHK8BXkhFlXvjFqf7WRrdAfvmrOhQzDSLx7BGa6aM=
Expand All @@ -164,12 +164,18 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
Expand Down
157 changes: 157 additions & 0 deletions selectel/domains_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package selectel

import (
"context"
"errors"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
domainsV2 "github.com/selectel/domains-go/pkg/v2"
)

var ErrProjectIDNotSetupForDNSV2 = errors.New("env variable SEL_PROJECT_ID or variable project_id must be set for the dns v2")

func getDomainsV2Client(d *schema.ResourceData, meta interface{}) (domainsV2.DNSClient[domainsV2.Zone, domainsV2.RRSet], error) {
config := meta.(*Config)
projectID := d.Get("project_id").(string)
selvpcClient, err := config.GetSelVPCClientWithProjectScope(projectID)
if err != nil {
return nil, fmt.Errorf("can't get selvpc client for domains v2: %w", err)
}

httpClient := &http.Client{}
userAgent := "terraform-provider-selectel"
defaultAPIURL := "https://api.selectel.ru/domains/v2"
hdrs := http.Header{}
hdrs.Add("X-Auth-Token", selvpcClient.GetXAuthToken())
hdrs.Add("User-Agent", userAgent)
domainsClient := domainsV2.NewClient(defaultAPIURL, httpClient, hdrs)

return domainsClient, nil
}

func getZoneByName(ctx context.Context, client domainsV2.DNSClient[domainsV2.Zone, domainsV2.RRSet], zoneName string) (*domainsV2.Zone, error) {
optsForSearchZone := map[string]string{
"filter": zoneName,
"limit": "1000",
"offset": "0",
}
r, err := regexp.Compile(fmt.Sprintf("^%s.?", zoneName))
if err != nil {
return nil, err
}

for {
zones, err := client.ListZones(ctx, &optsForSearchZone)
if err != nil {
return nil, err
}

for _, zone := range zones.GetItems() {
if r.MatchString(zone.Name) {
return zone, nil
}
}
optsForSearchZone["offset"] = strconv.Itoa(zones.GetNextOffset())
if zones.GetNextOffset() == 0 {
break
}
}

return nil, errGettingObject(objectZone, zoneName, ErrZoneNotFound)
}

func getRRSetByNameAndType(ctx context.Context, client domainsV2.DNSClient[domainsV2.Zone, domainsV2.RRSet], zoneID, rrsetName, rrsetType string) (*domainsV2.RRSet, error) {
optsForSearchRRSet := map[string]string{
"name": rrsetName,
"rrset_types": rrsetType,
"limit": "1000",
"offset": "0",
}

r, err := regexp.Compile(fmt.Sprintf("^%s.?", rrsetName))
if err != nil {
return nil, errGettingObject(objectRRSet, rrsetName, err)
}

for {
rrsets, err := client.ListRRSets(ctx, zoneID, &optsForSearchRRSet)
if err != nil {
return nil, errGettingObject(objectRRSet, rrsetName, err)
}
for _, rrset := range rrsets.GetItems() {
if r.MatchString(rrset.Name) && string(rrset.Type) == rrsetType {
return rrset, nil
}
}
optsForSearchRRSet["offset"] = strconv.Itoa(rrsets.GetNextOffset())
if rrsets.GetNextOffset() == 0 {
break
}
}

return nil, errGettingObject(objectRRSet, fmt.Sprintf("Name: %s. Type: %s.", rrsetName, rrsetType), ErrRRSetNotFound)
}

func setZoneToResourceData(d *schema.ResourceData, zone *domainsV2.Zone) error {
d.SetId(zone.ID)
d.Set("name", zone.Name)
d.Set("comment", zone.Comment)
d.Set("created_at", zone.CreatedAt.Format(time.RFC3339))
d.Set("updated_at", zone.UpdatedAt.Format(time.RFC3339))
d.Set("delegation_checked_at", zone.DelegationCheckedAt.Format(time.RFC3339))
d.Set("last_check_status", zone.LastCheckStatus)
d.Set("last_delegated_at", zone.LastDelegatedAt.Format(time.RFC3339))
d.Set("project_id", strings.ReplaceAll(zone.ProjectID, "-", ""))
d.Set("disabled", zone.Disabled)

return nil
}

func setRRSetToResourceData(d *schema.ResourceData, rrset *domainsV2.RRSet) error {
d.SetId(rrset.ID)
d.Set("name", rrset.Name)
d.Set("comment", rrset.Comment)
d.Set("managed_by", rrset.ManagedBy)
d.Set("ttl", rrset.TTL)
d.Set("type", rrset.Type)
d.Set("zone_id", rrset.ZoneID)
d.Set("records", generateSetFromRecords(rrset.Records))

return nil
}

// generateSetFromRecords - generate terraform TypeList from records in RRSet.
func generateSetFromRecords(records []domainsV2.RecordItem) []interface{} {
recordsAsList := []interface{}{}
for _, record := range records {
recordsAsList = append(recordsAsList, map[string]interface{}{
"content": record.Content,
"disabled": record.Disabled,
})
}

return recordsAsList
}

// generateRecordsFromSet - generate records for RRSet from terraform TypeList.
func generateRecordsFromSet(recordsSet *schema.Set) []domainsV2.RecordItem {
records := []domainsV2.RecordItem{}
for _, recordItem := range recordsSet.List() {
record, isOk := recordItem.(map[string]interface{})
if !isOk {
continue
}
records = append(records, domainsV2.RecordItem{
Content: record["content"].(string),
Disabled: record["disabled"].(bool),
})
}

return records
}
166 changes: 166 additions & 0 deletions selectel/domains_v2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package selectel

import (
"context"
"fmt"
"net/http"
"strconv"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
domainsV2 "github.com/selectel/domains-go/pkg/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func getDomainsV2ClientTest(rs *terraform.ResourceState, testAccProvider *schema.Provider) (domainsV2.DNSClient[domainsV2.Zone, domainsV2.RRSet], error) {
config := testAccProvider.Meta().(*Config)
projectID, ok := rs.Primary.Attributes["project_id"]
if !ok {
return nil, ErrProjectIDNotSetupForDNSV2
}
selvpcClient, err := config.GetSelVPCClientWithProjectScope(projectID)
if err != nil {
return nil, fmt.Errorf("can't get selvpc client for domains v2: %w", err)
}

httpClient := &http.Client{}
userAgent := "terraform-provider-selectel"
defaultAPIURL := "https://api.selectel.ru/domains/v2"
hdrs := http.Header{}
hdrs.Add("X-Auth-Token", selvpcClient.GetXAuthToken())
hdrs.Add("User-Agent", userAgent)
domainsClient := domainsV2.NewClient(defaultAPIURL, httpClient, hdrs)

return domainsClient, nil
}

type mockedDNSv2Client struct {
mock.Mock
domainsV2.Client
}

func (client *mockedDNSv2Client) ListZones(ctx context.Context, opts *map[string]string) (domainsV2.Listable[domainsV2.Zone], error) {
args := client.Called(ctx, opts)
zones := args.Get(0).(domainsV2.Listable[domainsV2.Zone])
err := args.Error(1)

return zones, err
}

func (client *mockedDNSv2Client) ListRRSets(ctx context.Context, zoneID string, opts *map[string]string) (domainsV2.Listable[domainsV2.RRSet], error) {
args := client.Called(ctx, zoneID, opts)
rrsets := args.Get(0).(domainsV2.Listable[domainsV2.RRSet])
err := args.Error(1)

return rrsets, err
}

func TestGetZoneByName_whenNeededZoneInResponseWithOffset(t *testing.T) {
nameForSearch := "test.xyz."
correctIDForSearch := "mocked-uuid-2"

mDNSClient := new(mockedDNSv2Client)
ctx := context.Background()
nextOffset := 3
opts1 := &map[string]string{
"filter": nameForSearch,
"limit": "1000",
"offset": "0",
}
opts2 := &map[string]string{
"filter": nameForSearch,
"limit": "1000",
"offset": strconv.Itoa(nextOffset),
}
incorrectNameForSearch := "a." + nameForSearch
incorrectIDForSearch := "mocked-uuid-1"
zonesWithNextOffset := domainsV2.Listable[domainsV2.Zone](domainsV2.List[domainsV2.Zone]{
Count: 1,
NextOffset: nextOffset,
Items: []*domainsV2.Zone{
{
ID: incorrectIDForSearch,
Name: incorrectNameForSearch,
},
},
})
mDNSClient.On("ListZones", ctx, opts1).Return(zonesWithNextOffset, nil)
zonesWithoutNextOffset := domainsV2.Listable[domainsV2.Zone](domainsV2.List[domainsV2.Zone]{
Count: 1,
NextOffset: 0,
Items: []*domainsV2.Zone{
{
ID: correctIDForSearch,
Name: nameForSearch,
},
},
})
mDNSClient.On("ListZones", ctx, opts2).Return(zonesWithoutNextOffset, nil)

zone, err := getZoneByName(ctx, mDNSClient, nameForSearch)

assert.NoError(t, err)

assert.NotNil(t, zone)
assert.Equal(t, correctIDForSearch, zone.ID)
assert.Equal(t, nameForSearch, zone.Name)
}

func TestGetRRSetByNameAndType_whenNeededRRSetInResponseWithOffset(t *testing.T) {
rrsetNameForSearch := "test.xyz."
rrsetTypeForSearch := "A"
correctIDForSearch := "mocked-uuid-2"
mockedZoneID := "mopcked-zone-id"
mDNSClient := new(mockedDNSv2Client)
ctx := context.Background()
nextOffset := 3
opts1 := &map[string]string{
"name": rrsetNameForSearch,
"rrset_types": rrsetTypeForSearch,
"limit": "1000",
"offset": "0",
}
opts2 := &map[string]string{
"name": rrsetNameForSearch,
"rrset_types": rrsetTypeForSearch,
"limit": "1000",
"offset": strconv.Itoa(nextOffset),
}
incorrectNameForSearch := "a." + rrsetNameForSearch
incorrectIDForSearch := "mocked-uuid-1"
rrsetWithNextOffset := domainsV2.Listable[domainsV2.RRSet](domainsV2.List[domainsV2.RRSet]{
Count: 1,
NextOffset: nextOffset,
Items: []*domainsV2.RRSet{
{
ID: incorrectIDForSearch,
Name: incorrectNameForSearch,
Type: domainsV2.RecordType(rrsetTypeForSearch),
},
},
})
mDNSClient.On("ListRRSets", ctx, mockedZoneID, opts1).Return(rrsetWithNextOffset, nil)
rrsetsWithoutNextOffset := domainsV2.Listable[domainsV2.RRSet](domainsV2.List[domainsV2.RRSet]{
Count: 1,
NextOffset: 0,
Items: []*domainsV2.RRSet{
{
ID: correctIDForSearch,
Name: rrsetNameForSearch,
Type: domainsV2.RecordType(rrsetTypeForSearch),
},
},
})
mDNSClient.On("ListRRSets", ctx, mockedZoneID, opts2).Return(rrsetsWithoutNextOffset, nil)

rrset, err := getRRSetByNameAndType(ctx, mDNSClient, mockedZoneID, rrsetNameForSearch, rrsetTypeForSearch)

assert.NoError(t, err)

assert.NotNil(t, rrset)
assert.Equal(t, correctIDForSearch, rrset.ID)
assert.Equal(t, rrsetNameForSearch, rrset.Name)
assert.Equal(t, rrsetTypeForSearch, string(rrset.Type))
}
Loading

0 comments on commit 3ee9b6a

Please sign in to comment.