Ns1 provider (#10782)
* vendor: update gopkg.in/ns1/ns1-go.v2 * provider/ns1: Port the ns1 provider to Terraform core * docs/ns1: Document the ns1 provider * ns1: rename remaining nsone -> ns1 (#10805) * Ns1 provider (#11300) * provider/ns1: Flesh out support for meta structs. Following the structure outlined by @pashap. Using reflection to reduce copy/paste. Putting metas inside single-item lists. This is clunky, but I couldn't figure out how else to have a nested struct. Maybe the Terraform people know a better way? Inside the meta struct, all fields are always written to the state; I can't figure out how to omit fields that aren't used. This is not just verbose, it actually causes issues because you can't have both "up" and "up_feed" set). Also some minor other changes: - Add "terraform" import support to records and zones. - Create helper class StringEnum. * provider/ns1: Make fmt * provider/ns1: Remove stubbed out RecordRead (used for testing metadata change). * provider/ns1: Need to get interface that m contains from Ptr Value with Elem() * provider/ns1: Use empty string to indicate no feed given. * provider/ns1: Remove old record.regions fields. * provider/ns1: Removes redundant testAccCheckRecordState * provider/ns1: Moves account permissions logic to permissions.go * provider/ns1: Adds tests for team resource. * provider/ns1: Move remaining permissions logic to permissions.go * ns1/provider: Adds datasource.config * provider/ns1: Small clean up of datafeed resource tests * provider/ns1: removes testAccCheckZoneState in favor of explicit name check * provider/ns1: More renaming of nsone -> ns1 * provider/ns1: Comment out metadata for the moment. * Ns1 provider (#11347) * Fix the removal of empty containers from a flatmap Removal of empty nested containers from a flatmap would sometimes fail a sanity check when removed in the wrong order. This would only fail sometimes due to map iteration. There was also an off-by-one error in the prefix check which could match the incorrect keys. * provider/ns1: Adds ns1 go client through govendor. * provider/ns1: Removes unused debug line * docs/ns1: Adds docs around apikey/datasource/datafeed/team/user/record. * provider/ns1: Gets go vet green
This commit is contained in:
parent
08dcd58923
commit
987b910828
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/builtin/providers/ns1"
|
||||
"github.com/hashicorp/terraform/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeOpts{
|
||||
ProviderFunc: ns1.Provider,
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -0,0 +1,218 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
type TfSchemaBuilder func(*schema.Schema)
|
||||
|
||||
func mtSimple(t schema.ValueType) TfSchemaBuilder {
|
||||
return func(s *schema.Schema) {
|
||||
s.Type = t
|
||||
}
|
||||
}
|
||||
|
||||
func mtStringEnum(se *StringEnum) TfSchemaBuilder {
|
||||
return func(s *schema.Schema) {
|
||||
s.Type = schema.TypeString
|
||||
s.ValidateFunc = func(v interface{}, k string) ([]string, []error) {
|
||||
_, err := se.Check(v.(string))
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mtInt TfSchemaBuilder = mtSimple(schema.TypeInt)
|
||||
var mtBool TfSchemaBuilder = mtSimple(schema.TypeBool)
|
||||
var mtString TfSchemaBuilder = mtSimple(schema.TypeString)
|
||||
var mtFloat64 TfSchemaBuilder = mtSimple(schema.TypeFloat)
|
||||
|
||||
func mtList(elementSchemaBuilder TfSchemaBuilder) TfSchemaBuilder {
|
||||
return func(s *schema.Schema) {
|
||||
s.Type = schema.TypeList
|
||||
elementSchema := &schema.Schema{}
|
||||
elementSchemaBuilder(elementSchema)
|
||||
s.Elem = elementSchema
|
||||
}
|
||||
}
|
||||
|
||||
var mtStringList TfSchemaBuilder = mtList(mtString)
|
||||
|
||||
type MetaFieldSpec struct {
|
||||
NameInDynamic string
|
||||
NameInStruct string
|
||||
SchemaBuilder TfSchemaBuilder
|
||||
}
|
||||
|
||||
type MetaField struct {
|
||||
MetaFieldSpec
|
||||
NameInDynamicForFeed string
|
||||
StructIndex int
|
||||
StructGoType reflect.Type
|
||||
}
|
||||
|
||||
var georegionEnum *StringEnum = NewStringEnum([]string{
|
||||
"US-WEST",
|
||||
"US-EAST",
|
||||
"US-CENTRAL",
|
||||
"EUROPE",
|
||||
"AFRICA",
|
||||
"ASIAPAC",
|
||||
"SOUTH-AMERICA",
|
||||
})
|
||||
|
||||
func makeMetaFields() []MetaField {
|
||||
var specs []MetaFieldSpec = []MetaFieldSpec{
|
||||
{"up", "Up", mtBool},
|
||||
{"connections", "Connections", mtInt},
|
||||
{"requests", "Requests", mtInt},
|
||||
{"loadavg", "LoadAvg", mtFloat64},
|
||||
{"pulsar", "Pulsar", mtInt},
|
||||
{"latitude", "Latitude", mtFloat64},
|
||||
{"longitude", "Longitude", mtFloat64},
|
||||
{"georegion", "Georegion", mtList(mtStringEnum(georegionEnum))},
|
||||
{"country", "Country", mtStringList},
|
||||
{"us_state", "USState", mtStringList},
|
||||
{"ca_province", "CAProvince", mtStringList},
|
||||
{"note", "Note", mtString},
|
||||
{"ip_prefixes", "IPPrefixes", mtStringList},
|
||||
{"asn", "ASN", mtList(mtInt)},
|
||||
{"priority", "Priority", mtInt},
|
||||
{"weight", "Weight", mtFloat64},
|
||||
{"low_watermark", "LowWatermark", mtInt},
|
||||
{"high_watermark", "HighWatermark", mtInt},
|
||||
}
|
||||
|
||||
// Figure out the field indexes (in data.Meta) for all the fields.
|
||||
// This way we can later lookup by index, which should be faster than by name.
|
||||
|
||||
rt := reflect.TypeOf(data.Meta{})
|
||||
fields := make([]MetaField, len(specs))
|
||||
for i, spec := range specs {
|
||||
rf, present := rt.FieldByName(spec.NameInStruct)
|
||||
if !present {
|
||||
panic(fmt.Sprintf("Field %q not present", spec.NameInStruct))
|
||||
}
|
||||
if len(rf.Index) != 1 {
|
||||
panic(fmt.Sprintf("Expecting a single index, got %#v", rf.Index))
|
||||
}
|
||||
index := rf.Index[0]
|
||||
fields[i] = MetaField{
|
||||
MetaFieldSpec: spec,
|
||||
StructIndex: index,
|
||||
NameInDynamicForFeed: spec.NameInDynamic + "_feed",
|
||||
StructGoType: rf.Type,
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
var metaFields []MetaField = makeMetaFields()
|
||||
|
||||
func makeMetaSchema() *schema.Schema {
|
||||
fields := make(map[string]*schema.Schema)
|
||||
|
||||
for _, f := range metaFields {
|
||||
fieldSchema := &schema.Schema{
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
// TODO: Fields that arent in configuration shouldnt show up in resource data
|
||||
// ConflictsWith: []string{f.NameInDynamicForFeed},
|
||||
}
|
||||
f.SchemaBuilder(fieldSchema)
|
||||
|
||||
fields[f.NameInDynamic] = fieldSchema
|
||||
|
||||
// Add an "_feed"-suffixed field for the {"feed":...} value.
|
||||
fields[f.NameInDynamicForFeed] = &schema.Schema{
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
// TODO: Fields that arent in configuration shouldnt show up in resource data
|
||||
// ConflictsWith: []string{f.NameInDynamic},
|
||||
Type: schema.TypeString,
|
||||
}
|
||||
}
|
||||
|
||||
metaSchemaInner := &schema.Resource{
|
||||
Schema: fields,
|
||||
}
|
||||
|
||||
// Wrap it in a list because that seems to be the only way to have nested structs.
|
||||
return &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: metaSchemaInner,
|
||||
}
|
||||
}
|
||||
|
||||
var metaSchema *schema.Schema = makeMetaSchema()
|
||||
|
||||
func metaStructToDynamic(m *data.Meta) interface{} {
|
||||
d := make(map[string]interface{})
|
||||
mr := reflect.ValueOf(m).Elem()
|
||||
for _, f := range metaFields {
|
||||
fr := mr.Field(f.StructIndex)
|
||||
fv := fr.Interface()
|
||||
|
||||
if fv == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if mapVal, isMap := fv.(map[string]interface{}); isMap {
|
||||
if len(mapVal) == 1 {
|
||||
if feedVal, ok := mapVal["feed"]; ok {
|
||||
if feedStr, ok := feedVal.(string); ok {
|
||||
d[f.NameInDynamicForFeed] = feedStr
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("expecting feed dict, got %+v", mapVal))
|
||||
}
|
||||
|
||||
d[f.NameInDynamic] = fv
|
||||
}
|
||||
return []interface{}{d}
|
||||
}
|
||||
|
||||
func metaDynamicToStruct(m *data.Meta, raw interface{}) {
|
||||
l := raw.([]interface{})
|
||||
if len(l) > 1 {
|
||||
panic(fmt.Sprintf("list too long %#v", l))
|
||||
}
|
||||
if len(l) == 0 {
|
||||
return
|
||||
}
|
||||
if l[0] == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d := l[0].(map[string]interface{})
|
||||
|
||||
mr := reflect.ValueOf(m).Elem()
|
||||
for _, f := range metaFields {
|
||||
val, present := d[f.NameInDynamic]
|
||||
if present {
|
||||
fr := mr.Field(f.StructIndex)
|
||||
fr.Set(reflect.ValueOf(val))
|
||||
}
|
||||
|
||||
feed, present := d[f.NameInDynamicForFeed]
|
||||
if present && feed != "" {
|
||||
if feed == nil {
|
||||
panic("unexpected nil")
|
||||
}
|
||||
fr := mr.Field(f.StructIndex)
|
||||
fr.Set(reflect.ValueOf(map[string]interface{}{"feed": feed.(string)}))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
func addPermsSchema(s map[string]*schema.Schema) map[string]*schema.Schema {
|
||||
s["dns_view_zones"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["dns_manage_zones"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["dns_zones_allow_by_default"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["dns_zones_deny"] = &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
}
|
||||
s["dns_zones_allow"] = &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
}
|
||||
s["data_push_to_datafeeds"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["data_manage_datasources"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["data_manage_datafeeds"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_manage_users"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_manage_payment_methods"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_manage_plan"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_manage_teams"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_manage_apikeys"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_manage_account_settings"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_view_activity_log"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["account_view_invoices"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["monitoring_manage_lists"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["monitoring_manage_jobs"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
s["monitoring_view_jobs"] = &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func permissionsToResourceData(d *schema.ResourceData, permissions account.PermissionsMap) {
|
||||
d.Set("dns_view_zones", permissions.DNS.ViewZones)
|
||||
d.Set("dns_manage_zones", permissions.DNS.ManageZones)
|
||||
d.Set("dns_zones_allow_by_default", permissions.DNS.ZonesAllowByDefault)
|
||||
d.Set("dns_zones_deny", permissions.DNS.ZonesDeny)
|
||||
d.Set("dns_zones_allow", permissions.DNS.ZonesAllow)
|
||||
d.Set("data_push_to_datafeeds", permissions.Data.PushToDatafeeds)
|
||||
d.Set("data_manage_datasources", permissions.Data.ManageDatasources)
|
||||
d.Set("data_manage_datafeeds", permissions.Data.ManageDatafeeds)
|
||||
d.Set("account_manage_users", permissions.Account.ManageUsers)
|
||||
d.Set("account_manage_payment_methods", permissions.Account.ManagePaymentMethods)
|
||||
d.Set("account_manage_plan", permissions.Account.ManagePlan)
|
||||
d.Set("account_manage_teams", permissions.Account.ManageTeams)
|
||||
d.Set("account_manage_apikeys", permissions.Account.ManageApikeys)
|
||||
d.Set("account_manage_account_settings", permissions.Account.ManageAccountSettings)
|
||||
d.Set("account_view_activity_log", permissions.Account.ViewActivityLog)
|
||||
d.Set("account_view_invoices", permissions.Account.ViewInvoices)
|
||||
d.Set("monitoring_manage_lists", permissions.Monitoring.ManageLists)
|
||||
d.Set("monitoring_manage_jobs", permissions.Monitoring.ManageJobs)
|
||||
d.Set("monitoring_view_jobs", permissions.Monitoring.ViewJobs)
|
||||
}
|
||||
|
||||
func resourceDataToPermissions(d *schema.ResourceData) account.PermissionsMap {
|
||||
var p account.PermissionsMap
|
||||
if v, ok := d.GetOk("dns_view_zones"); ok {
|
||||
p.DNS.ViewZones = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("dns_manage_zones"); ok {
|
||||
p.DNS.ManageZones = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("dns_zones_allow_by_default"); ok {
|
||||
p.DNS.ZonesAllowByDefault = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("dns_zones_deny"); ok {
|
||||
denyRaw := v.([]interface{})
|
||||
p.DNS.ZonesDeny = make([]string, len(denyRaw))
|
||||
for i, deny := range denyRaw {
|
||||
p.DNS.ZonesDeny[i] = deny.(string)
|
||||
}
|
||||
} else {
|
||||
p.DNS.ZonesDeny = make([]string, 0)
|
||||
}
|
||||
if v, ok := d.GetOk("dns_zones_allow"); ok {
|
||||
allowRaw := v.([]interface{})
|
||||
p.DNS.ZonesAllow = make([]string, len(allowRaw))
|
||||
for i, allow := range allowRaw {
|
||||
p.DNS.ZonesAllow[i] = allow.(string)
|
||||
}
|
||||
} else {
|
||||
p.DNS.ZonesAllow = make([]string, 0)
|
||||
}
|
||||
if v, ok := d.GetOk("data_push_to_datafeeds"); ok {
|
||||
p.Data.PushToDatafeeds = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("data_manage_datasources"); ok {
|
||||
p.Data.ManageDatasources = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("data_manage_datafeeds"); ok {
|
||||
p.Data.ManageDatafeeds = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_manage_users"); ok {
|
||||
p.Account.ManageUsers = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_manage_payment_methods"); ok {
|
||||
p.Account.ManagePaymentMethods = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_manage_plan"); ok {
|
||||
p.Account.ManagePlan = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_manage_teams"); ok {
|
||||
p.Account.ManageTeams = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_manage_apikeys"); ok {
|
||||
p.Account.ManageApikeys = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_manage_account_settings"); ok {
|
||||
p.Account.ManageAccountSettings = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_view_activity_log"); ok {
|
||||
p.Account.ViewActivityLog = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("account_view_invoices"); ok {
|
||||
p.Account.ViewInvoices = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("monitoring_manage_lists"); ok {
|
||||
p.Monitoring.ManageLists = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("monitoring_manage_jobs"); ok {
|
||||
p.Monitoring.ManageJobs = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("monitoring_view_jobs"); ok {
|
||||
p.Monitoring.ViewJobs = v.(bool)
|
||||
}
|
||||
return p
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
)
|
||||
|
||||
// Provider returns a terraform.ResourceProvider.
|
||||
func Provider() terraform.ResourceProvider {
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"apikey": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NS1_APIKEY", nil),
|
||||
Description: descriptions["api_key"],
|
||||
},
|
||||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"ns1_zone": zoneResource(),
|
||||
"ns1_record": recordResource(),
|
||||
"ns1_datasource": dataSourceResource(),
|
||||
"ns1_datafeed": dataFeedResource(),
|
||||
"ns1_monitoringjob": monitoringJobResource(),
|
||||
"ns1_user": userResource(),
|
||||
"ns1_apikey": apikeyResource(),
|
||||
"ns1_team": teamResource(),
|
||||
},
|
||||
ConfigureFunc: ns1Configure,
|
||||
}
|
||||
}
|
||||
|
||||
func ns1Configure(d *schema.ResourceData) (interface{}, error) {
|
||||
httpClient := &http.Client{}
|
||||
n := ns1.NewClient(httpClient, ns1.SetAPIKey(d.Get("apikey").(string)))
|
||||
n.RateLimitStrategySleep()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var descriptions map[string]string
|
||||
|
||||
func init() {
|
||||
descriptions = map[string]string{
|
||||
"api_key": "The ns1 API key, this is required",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var testAccProviders map[string]terraform.ResourceProvider
|
||||
var testAccProvider *schema.Provider
|
||||
|
||||
func init() {
|
||||
testAccProvider = Provider().(*schema.Provider)
|
||||
testAccProviders = map[string]terraform.ResourceProvider{
|
||||
"ns1": testAccProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = Provider()
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("NS1_APIKEY"); v == "" {
|
||||
t.Fatal("NS1_APIKEY must be set for acceptance tests")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
func apikeyResource() *schema.Resource {
|
||||
s := map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"teams": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
}
|
||||
s = addPermsSchema(s)
|
||||
return &schema.Resource{
|
||||
Schema: s,
|
||||
Create: ApikeyCreate,
|
||||
Read: ApikeyRead,
|
||||
Update: ApikeyUpdate,
|
||||
Delete: ApikeyDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func apikeyToResourceData(d *schema.ResourceData, k *account.APIKey) error {
|
||||
d.SetId(k.ID)
|
||||
d.Set("name", k.Name)
|
||||
d.Set("key", k.Key)
|
||||
d.Set("teams", k.TeamIDs)
|
||||
permissionsToResourceData(d, k.Permissions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDataToApikey(k *account.APIKey, d *schema.ResourceData) error {
|
||||
k.ID = d.Id()
|
||||
k.Name = d.Get("name").(string)
|
||||
if v, ok := d.GetOk("teams"); ok {
|
||||
teamsRaw := v.([]interface{})
|
||||
k.TeamIDs = make([]string, len(teamsRaw))
|
||||
for i, team := range teamsRaw {
|
||||
k.TeamIDs[i] = team.(string)
|
||||
}
|
||||
} else {
|
||||
k.TeamIDs = make([]string, 0)
|
||||
}
|
||||
k.Permissions = resourceDataToPermissions(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApikeyCreate creates ns1 API key
|
||||
func ApikeyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
k := account.APIKey{}
|
||||
if err := resourceDataToApikey(&k, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.APIKeys.Create(&k); err != nil {
|
||||
return err
|
||||
}
|
||||
return apikeyToResourceData(d, &k)
|
||||
}
|
||||
|
||||
// ApikeyRead reads API key from ns1
|
||||
func ApikeyRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
k, _, err := client.APIKeys.Get(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apikeyToResourceData(d, k)
|
||||
}
|
||||
|
||||
//ApikeyDelete deletes the given ns1 api key
|
||||
func ApikeyDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.APIKeys.Delete(d.Id())
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
//ApikeyUpdate updates the given api key in ns1
|
||||
func ApikeyUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
k := account.APIKey{
|
||||
ID: d.Id(),
|
||||
}
|
||||
if err := resourceDataToApikey(&k, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.APIKeys.Update(&k); err != nil {
|
||||
return err
|
||||
}
|
||||
return apikeyToResourceData(d, &k)
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
func dataFeedResource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"source_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"config": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Create: DataFeedCreate,
|
||||
Read: DataFeedRead,
|
||||
Update: DataFeedUpdate,
|
||||
Delete: DataFeedDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func dataFeedToResourceData(d *schema.ResourceData, f *data.Feed) {
|
||||
d.SetId(f.ID)
|
||||
d.Set("name", f.Name)
|
||||
d.Set("config", f.Config)
|
||||
}
|
||||
|
||||
func resourceDataToDataFeed(d *schema.ResourceData) *data.Feed {
|
||||
return &data.Feed{
|
||||
Name: d.Get("name").(string),
|
||||
SourceID: d.Get("source_id").(string),
|
||||
Config: d.Get("config").(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// DataFeedCreate creates an ns1 datafeed
|
||||
func DataFeedCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
f := resourceDataToDataFeed(d)
|
||||
if _, err := client.DataFeeds.Create(d.Get("source_id").(string), f); err != nil {
|
||||
return err
|
||||
}
|
||||
dataFeedToResourceData(d, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataFeedRead reads the datafeed for the given ID from ns1
|
||||
func DataFeedRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
f, _, err := client.DataFeeds.Get(d.Get("source_id").(string), d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataFeedToResourceData(d, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataFeedDelete delets the given datafeed from ns1
|
||||
func DataFeedDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.DataFeeds.Delete(d.Get("source_id").(string), d.Id())
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// DataFeedUpdate updates the given datafeed with modified parameters
|
||||
func DataFeedUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
f := resourceDataToDataFeed(d)
|
||||
f.ID = d.Id()
|
||||
if _, err := client.DataFeeds.Update(d.Get("source_id").(string), f); err != nil {
|
||||
return err
|
||||
}
|
||||
dataFeedToResourceData(d, f)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
func TestAccDataFeed_basic(t *testing.T) {
|
||||
var dataFeed data.Feed
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDataFeedDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccDataFeedBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataFeedExists("ns1_datafeed.foobar", "ns1_datasource.api", &dataFeed),
|
||||
testAccCheckDataFeedName(&dataFeed, "terraform test"),
|
||||
testAccCheckDataFeedConfig(&dataFeed, "label", "exampledc2"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDataFeed_updated(t *testing.T) {
|
||||
var dataFeed data.Feed
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDataFeedDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccDataFeedBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataFeedExists("ns1_datafeed.foobar", "ns1_datasource.api", &dataFeed),
|
||||
testAccCheckDataFeedName(&dataFeed, "terraform test"),
|
||||
testAccCheckDataFeedConfig(&dataFeed, "label", "exampledc2"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccDataFeedUpdated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataFeedExists("ns1_datafeed.foobar", "ns1_datasource.api", &dataFeed),
|
||||
testAccCheckDataFeedName(&dataFeed, "terraform test"),
|
||||
testAccCheckDataFeedConfig(&dataFeed, "label", "exampledc3"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDataFeedExists(n string, dsrc string, dataFeed *data.Feed) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
ds, ok := s.RootModule().Resources[dsrc]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("NoID is set")
|
||||
}
|
||||
|
||||
if ds.Primary.ID == "" {
|
||||
return fmt.Errorf("NoID is set for the datasource")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
foundFeed, _, err := client.DataFeeds.Get(ds.Primary.Attributes["id"], rs.Primary.Attributes["id"])
|
||||
|
||||
p := rs.Primary
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundFeed.Name != p.Attributes["name"] {
|
||||
return fmt.Errorf("DataFeed not found")
|
||||
}
|
||||
|
||||
*dataFeed = *foundFeed
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckDataFeedDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
var dataFeedID string
|
||||
var dataSourceID string
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
|
||||
if rs.Type == "ns1_datasource" {
|
||||
dataSourceID = rs.Primary.Attributes["id"]
|
||||
}
|
||||
|
||||
if rs.Type == "ns1_datafeed" {
|
||||
dataFeedID = rs.Primary.Attributes["id"]
|
||||
}
|
||||
}
|
||||
|
||||
df, _, _ := client.DataFeeds.Get(dataSourceID, dataFeedID)
|
||||
|
||||
if df != nil {
|
||||
return fmt.Errorf("DataFeed still exists: %#v", df)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckDataFeedName(dataFeed *data.Feed, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if dataFeed.Name != expected {
|
||||
return fmt.Errorf("Name: got: %#v want: %#v", dataFeed.Name, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckDataFeedConfig(dataFeed *data.Feed, key, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
if dataFeed.Config[key] != expected {
|
||||
return fmt.Errorf("Config[%s]: got: %#v, want: %s", key, dataFeed.Config[key], expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccDataFeedBasic = `
|
||||
resource "ns1_datasource" "api" {
|
||||
name = "terraform test"
|
||||
sourcetype = "nsone_v1"
|
||||
}
|
||||
|
||||
resource "ns1_datafeed" "foobar" {
|
||||
name = "terraform test"
|
||||
source_id = "${ns1_datasource.api.id}"
|
||||
config {
|
||||
label = "exampledc2"
|
||||
}
|
||||
}`
|
||||
|
||||
const testAccDataFeedUpdated = `
|
||||
resource "ns1_datasource" "api" {
|
||||
name = "terraform test"
|
||||
sourcetype = "nsone_v1"
|
||||
}
|
||||
|
||||
resource "ns1_datafeed" "foobar" {
|
||||
name = "terraform test"
|
||||
source_id = "${ns1_datasource.api.id}"
|
||||
config {
|
||||
label = "exampledc3"
|
||||
}
|
||||
}`
|
|
@ -0,0 +1,86 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
func dataSourceResource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"sourcetype": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"config": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Create: DataSourceCreate,
|
||||
Read: DataSourceRead,
|
||||
Update: DataSourceUpdate,
|
||||
Delete: DataSourceDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func dataSourceToResourceData(d *schema.ResourceData, s *data.Source) {
|
||||
d.SetId(s.ID)
|
||||
d.Set("name", s.Name)
|
||||
d.Set("sourcetype", s.Type)
|
||||
d.Set("config", s.Config)
|
||||
}
|
||||
|
||||
// DataSourceCreate creates an ns1 datasource
|
||||
func DataSourceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
s := data.NewSource(d.Get("name").(string), d.Get("sourcetype").(string))
|
||||
s.Config = d.Get("config").(map[string]interface{})
|
||||
if _, err := client.DataSources.Create(s); err != nil {
|
||||
return err
|
||||
}
|
||||
dataSourceToResourceData(d, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataSourceRead fetches info for the given datasource from ns1
|
||||
func DataSourceRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
s, _, err := client.DataSources.Get(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataSourceToResourceData(d, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataSourceDelete deteltes the given datasource from ns1
|
||||
func DataSourceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.DataSources.Delete(d.Id())
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// DataSourceUpdate updates the datasource with given parameters
|
||||
func DataSourceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
s := data.NewSource(d.Get("name").(string), d.Get("sourcetype").(string))
|
||||
s.ID = d.Id()
|
||||
if _, err := client.DataSources.Update(s); err != nil {
|
||||
return err
|
||||
}
|
||||
dataSourceToResourceData(d, s)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
func TestAccDataSource_basic(t *testing.T) {
|
||||
var dataSource data.Source
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDataSourceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccDataSourceBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataSourceExists("ns1_datasource.foobar", &dataSource),
|
||||
testAccCheckDataSourceName(&dataSource, "terraform test"),
|
||||
testAccCheckDataSourceType(&dataSource, "nsone_v1"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDataSource_updated(t *testing.T) {
|
||||
var dataSource data.Source
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDataSourceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccDataSourceBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataSourceExists("ns1_datasource.foobar", &dataSource),
|
||||
testAccCheckDataSourceName(&dataSource, "terraform test"),
|
||||
testAccCheckDataSourceType(&dataSource, "nsone_v1"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccDataSourceUpdated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDataSourceExists("ns1_datasource.foobar", &dataSource),
|
||||
testAccCheckDataSourceName(&dataSource, "terraform test"),
|
||||
testAccCheckDataSourceType(&dataSource, "nsone_monitoring"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDataSourceExists(n string, dataSource *data.Source) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("NoID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
foundSource, _, err := client.DataSources.Get(rs.Primary.Attributes["id"])
|
||||
|
||||
p := rs.Primary
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundSource.Name != p.Attributes["name"] {
|
||||
return fmt.Errorf("Datasource not found")
|
||||
}
|
||||
|
||||
*dataSource = *foundSource
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckDataSourceDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "ns1_datasource" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _, err := client.DataSources.Get(rs.Primary.Attributes["id"])
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Datasource still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckDataSourceName(dataSource *data.Source, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if dataSource.Name != expected {
|
||||
return fmt.Errorf("Name: got: %#v want: %#v", dataSource.Name, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckDataSourceType(dataSource *data.Source, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if dataSource.Type != expected {
|
||||
return fmt.Errorf("Type: got: %#v want: %#v", dataSource.Type, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccDataSourceBasic = `
|
||||
resource "ns1_datasource" "foobar" {
|
||||
name = "terraform test"
|
||||
sourcetype = "nsone_v1"
|
||||
}`
|
||||
|
||||
const testAccDataSourceUpdated = `
|
||||
resource "ns1_datasource" "foobar" {
|
||||
name = "terraform test"
|
||||
sourcetype = "nsone_monitoring"
|
||||
}`
|
|
@ -0,0 +1,297 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||
)
|
||||
|
||||
func monitoringJobResource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
// Required
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"job_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"regions": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
"frequency": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
"config": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Required: true,
|
||||
},
|
||||
// Optional
|
||||
"active": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
"rapid_recheck": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
"policy": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "quorum",
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(string)
|
||||
if !regexp.MustCompile(`^(all|one|quorum)$`).MatchString(value) {
|
||||
es = append(es, fmt.Errorf(
|
||||
"only all, one, quorum allowed in %q", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"notes": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"notify_delay": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
"notify_repeat": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
"notify_failback": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
"notify_regional": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
"notify_list": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"rules": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"value": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"comparison": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Computed
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
Create: MonitoringJobCreate,
|
||||
Read: MonitoringJobRead,
|
||||
Update: MonitoringJobUpdate,
|
||||
Delete: MonitoringJobDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func monitoringJobToResourceData(d *schema.ResourceData, r *monitor.Job) error {
|
||||
d.SetId(r.ID)
|
||||
d.Set("name", r.Name)
|
||||
d.Set("job_type", r.Type)
|
||||
d.Set("active", r.Active)
|
||||
d.Set("regions", r.Regions)
|
||||
d.Set("frequency", r.Frequency)
|
||||
d.Set("rapid_recheck", r.RapidRecheck)
|
||||
config := make(map[string]string)
|
||||
for k, v := range r.Config {
|
||||
if k == "ssl" {
|
||||
if v.(bool) {
|
||||
config[k] = "1"
|
||||
} else {
|
||||
config[k] = "0"
|
||||
}
|
||||
} else {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
config[k] = t
|
||||
case float64:
|
||||
config[k] = strconv.FormatFloat(t, 'f', -1, 64)
|
||||
}
|
||||
}
|
||||
}
|
||||
err := d.Set("config", config)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("[DEBUG] Error setting Config error: %#v %#v", r.Config, err))
|
||||
}
|
||||
d.Set("policy", r.Policy)
|
||||
d.Set("notes", r.Notes)
|
||||
d.Set("frequency", r.Frequency)
|
||||
d.Set("notify_delay", r.NotifyDelay)
|
||||
d.Set("notify_repeat", r.NotifyRepeat)
|
||||
d.Set("notify_regional", r.NotifyRegional)
|
||||
d.Set("notify_failback", r.NotifyFailback)
|
||||
d.Set("notify_list", r.NotifyListID)
|
||||
if len(r.Rules) > 0 {
|
||||
rules := make([]map[string]interface{}, len(r.Rules))
|
||||
for i, r := range r.Rules {
|
||||
m := make(map[string]interface{})
|
||||
m["value"] = r.Value
|
||||
m["comparison"] = r.Comparison
|
||||
m["key"] = r.Key
|
||||
rules[i] = m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDataToMonitoringJob(r *monitor.Job, d *schema.ResourceData) error {
|
||||
r.ID = d.Id()
|
||||
r.Name = d.Get("name").(string)
|
||||
r.Type = d.Get("job_type").(string)
|
||||
r.Active = d.Get("active").(bool)
|
||||
rawRegions := d.Get("regions").([]interface{})
|
||||
r.Regions = make([]string, len(rawRegions))
|
||||
for i, v := range rawRegions {
|
||||
r.Regions[i] = v.(string)
|
||||
}
|
||||
r.Frequency = d.Get("frequency").(int)
|
||||
r.RapidRecheck = d.Get("rapid_recheck").(bool)
|
||||
var rawRules []interface{}
|
||||
if rawRules := d.Get("rules"); rawRules != nil {
|
||||
r.Rules = make([]*monitor.Rule, len(rawRules.([]interface{})))
|
||||
for i, v := range rawRules.([]interface{}) {
|
||||
rule := v.(map[string]interface{})
|
||||
r.Rules[i] = &monitor.Rule{
|
||||
Value: rule["value"].(string),
|
||||
Comparison: rule["comparison"].(string),
|
||||
Key: rule["key"].(string),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.Rules = make([]*monitor.Rule, 0)
|
||||
}
|
||||
for i, v := range rawRules {
|
||||
rule := v.(map[string]interface{})
|
||||
r.Rules[i] = &monitor.Rule{
|
||||
Comparison: rule["comparison"].(string),
|
||||
Key: rule["key"].(string),
|
||||
}
|
||||
value := rule["value"].(string)
|
||||
if i, err := strconv.Atoi(value); err == nil {
|
||||
r.Rules[i].Value = i
|
||||
} else {
|
||||
r.Rules[i].Value = value
|
||||
}
|
||||
}
|
||||
config := make(map[string]interface{})
|
||||
if rawConfig := d.Get("config"); rawConfig != nil {
|
||||
for k, v := range rawConfig.(map[string]interface{}) {
|
||||
if k == "ssl" {
|
||||
if v.(string) == "1" {
|
||||
config[k] = true
|
||||
}
|
||||
} else {
|
||||
if i, err := strconv.Atoi(v.(string)); err == nil {
|
||||
config[k] = i
|
||||
} else {
|
||||
config[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
r.Config = config
|
||||
r.RegionScope = "fixed"
|
||||
r.Policy = d.Get("policy").(string)
|
||||
if v, ok := d.GetOk("notes"); ok {
|
||||
r.Notes = v.(string)
|
||||
}
|
||||
r.Frequency = d.Get("frequency").(int)
|
||||
if v, ok := d.GetOk("notify_delay"); ok {
|
||||
r.NotifyDelay = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("notify_repeat"); ok {
|
||||
r.NotifyRepeat = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("notify_regional"); ok {
|
||||
r.NotifyRegional = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("notify_failback"); ok {
|
||||
r.NotifyFailback = v.(bool)
|
||||
}
|
||||
if v, ok := d.GetOk("notify_list"); ok {
|
||||
r.NotifyListID = v.(string)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MonitoringJobCreate Creates monitoring job in ns1
|
||||
func MonitoringJobCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
j := monitor.Job{}
|
||||
if err := resourceDataToMonitoringJob(&j, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Jobs.Create(&j); err != nil {
|
||||
return err
|
||||
}
|
||||
return monitoringJobToResourceData(d, &j)
|
||||
}
|
||||
|
||||
// MonitoringJobRead reads the given monitoring job from ns1
|
||||
func MonitoringJobRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
j, _, err := client.Jobs.Get(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return monitoringJobToResourceData(d, j)
|
||||
}
|
||||
|
||||
// MonitoringJobDelete deteltes the given monitoring job from ns1
|
||||
func MonitoringJobDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.Jobs.Delete(d.Id())
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// MonitoringJobUpdate updates the given monitoring job
|
||||
func MonitoringJobUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
j := monitor.Job{
|
||||
ID: d.Id(),
|
||||
}
|
||||
if err := resourceDataToMonitoringJob(&j, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Jobs.Update(&j); err != nil {
|
||||
return err
|
||||
}
|
||||
return monitoringJobToResourceData(d, &j)
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||
)
|
||||
|
||||
func TestAccMonitoringJob_basic(t *testing.T) {
|
||||
var mj monitor.Job
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckMonitoringJobDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccMonitoringJobBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckMonitoringJobExists("ns1_monitoringjob.it", &mj),
|
||||
testAccCheckMonitoringJobName(&mj, "terraform test"),
|
||||
testAccCheckMonitoringJobActive(&mj, true),
|
||||
testAccCheckMonitoringJobRegions(&mj, []string{"lga"}),
|
||||
testAccCheckMonitoringJobType(&mj, "tcp"),
|
||||
testAccCheckMonitoringJobFrequency(&mj, 60),
|
||||
testAccCheckMonitoringJobRapidRecheck(&mj, false),
|
||||
testAccCheckMonitoringJobPolicy(&mj, "quorum"),
|
||||
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
|
||||
testAccCheckMonitoringJobConfigPort(&mj, 80),
|
||||
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccMonitoringJob_updated(t *testing.T) {
|
||||
var mj monitor.Job
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckMonitoringJobDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccMonitoringJobBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckMonitoringJobExists("ns1_monitoringjob.it", &mj),
|
||||
testAccCheckMonitoringJobName(&mj, "terraform test"),
|
||||
testAccCheckMonitoringJobActive(&mj, true),
|
||||
testAccCheckMonitoringJobRegions(&mj, []string{"lga"}),
|
||||
testAccCheckMonitoringJobType(&mj, "tcp"),
|
||||
testAccCheckMonitoringJobFrequency(&mj, 60),
|
||||
testAccCheckMonitoringJobRapidRecheck(&mj, false),
|
||||
testAccCheckMonitoringJobPolicy(&mj, "quorum"),
|
||||
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
|
||||
testAccCheckMonitoringJobConfigPort(&mj, 80),
|
||||
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccMonitoringJobUpdated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckMonitoringJobExists("ns1_monitoringjob.it", &mj),
|
||||
testAccCheckMonitoringJobName(&mj, "terraform test"),
|
||||
testAccCheckMonitoringJobActive(&mj, true),
|
||||
testAccCheckMonitoringJobRegions(&mj, []string{"lga"}),
|
||||
testAccCheckMonitoringJobType(&mj, "tcp"),
|
||||
testAccCheckMonitoringJobFrequency(&mj, 120),
|
||||
testAccCheckMonitoringJobRapidRecheck(&mj, true),
|
||||
testAccCheckMonitoringJobPolicy(&mj, "all"),
|
||||
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
|
||||
testAccCheckMonitoringJobConfigPort(&mj, 443),
|
||||
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobState(key, value string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources["ns1_monitoringjob.it"]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", "ns1_monitoringjob.it")
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
p := rs.Primary
|
||||
if p.Attributes[key] != value {
|
||||
return fmt.Errorf(
|
||||
"%s != %s (actual: %s)", key, value, p.Attributes[key])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobExists(n string, monitoringJob *monitor.Job) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Resource not found: %v", n)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("ID is not set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
foundMj, _, err := client.Jobs.Get(id)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundMj.ID != id {
|
||||
return fmt.Errorf("Monitoring Job not found want: %#v, got %#v", id, foundMj)
|
||||
}
|
||||
|
||||
*monitoringJob = *foundMj
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "ns1_monitoringjob" {
|
||||
continue
|
||||
}
|
||||
|
||||
mj, _, err := client.Jobs.Get(rs.Primary.Attributes["id"])
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Monitoring Job still exists %#v: %#v", err, mj)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobName(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Name != expected {
|
||||
return fmt.Errorf("Name: got: %#v want: %#v", mj.Name, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobActive(mj *monitor.Job, expected bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Active != expected {
|
||||
return fmt.Errorf("Active: got: %#v want: %#v", mj.Active, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobRegions(mj *monitor.Job, expected []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if !reflect.DeepEqual(mj.Regions, expected) {
|
||||
return fmt.Errorf("Regions: got: %#v want: %#v", mj.Regions, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobType(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Type != expected {
|
||||
return fmt.Errorf("Type: got: %#v want: %#v", mj.Type, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobFrequency(mj *monitor.Job, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Frequency != expected {
|
||||
return fmt.Errorf("Frequency: got: %#v want: %#v", mj.Frequency, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobRapidRecheck(mj *monitor.Job, expected bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.RapidRecheck != expected {
|
||||
return fmt.Errorf("RapidRecheck: got: %#v want: %#v", mj.RapidRecheck, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobPolicy(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Policy != expected {
|
||||
return fmt.Errorf("Policy: got: %#v want: %#v", mj.Policy, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobConfigSend(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Config["send"].(string) != expected {
|
||||
return fmt.Errorf("Config.send: got: %#v want: %#v", mj.Config["send"].(string), expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobConfigPort(mj *monitor.Job, expected float64) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Config["port"].(float64) != expected {
|
||||
return fmt.Errorf("Config.port: got: %#v want: %#v", mj.Config["port"].(float64), expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckMonitoringJobConfigHost(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if mj.Config["host"].(string) != expected {
|
||||
return fmt.Errorf("Config.host: got: %#v want: %#v", mj.Config["host"].(string), expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccMonitoringJobBasic = `
|
||||
resource "ns1_monitoringjob" "it" {
|
||||
job_type = "tcp"
|
||||
name = "terraform test"
|
||||
|
||||
regions = ["lga"]
|
||||
frequency = 60
|
||||
|
||||
config {
|
||||
send = "HEAD / HTTP/1.0\r\n\r\n"
|
||||
port = 80
|
||||
host = "1.1.1.1"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccMonitoringJobUpdated = `
|
||||
resource "ns1_monitoringjob" "it" {
|
||||
job_type = "tcp"
|
||||
name = "terraform test"
|
||||
|
||||
active = true
|
||||
regions = ["lga"]
|
||||
frequency = 120
|
||||
rapid_recheck = true
|
||||
policy = "all"
|
||||
|
||||
config {
|
||||
send = "HEAD / HTTP/1.0\r\n\r\n"
|
||||
port = 443
|
||||
host = "1.1.1.1"
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,367 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
"github.com/mitchellh/hashstructure"
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
|
||||
)
|
||||
|
||||
var recordTypeStringEnum *StringEnum = NewStringEnum([]string{
|
||||
"A",
|
||||
"AAAA",
|
||||
"ALIAS",
|
||||
"AFSDB",
|
||||
"CNAME",
|
||||
"DNAME",
|
||||
"HINFO",
|
||||
"MX",
|
||||
"NAPTR",
|
||||
"NS",
|
||||
"PTR",
|
||||
"RP",
|
||||
"SPF",
|
||||
"SRV",
|
||||
"TXT",
|
||||
})
|
||||
|
||||
func recordResource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
// Required
|
||||
"zone": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"domain": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: recordTypeStringEnum.ValidateFunc,
|
||||
},
|
||||
// Optional
|
||||
"ttl": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
// "meta": metaSchema,
|
||||
"link": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"use_client_subnet": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
"answers": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"answer": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"region": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
// "meta": metaSchema,
|
||||
},
|
||||
},
|
||||
Set: genericHasher,
|
||||
},
|
||||
"regions": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
// "meta": metaSchema,
|
||||
},
|
||||
},
|
||||
Set: genericHasher,
|
||||
},
|
||||
"filters": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"filter": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"disabled": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
"config": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Computed
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
Create: RecordCreate,
|
||||
Read: RecordRead,
|
||||
Update: RecordUpdate,
|
||||
Delete: RecordDelete,
|
||||
Importer: &schema.ResourceImporter{State: RecordStateFunc},
|
||||
}
|
||||
}
|
||||
|
||||
func genericHasher(v interface{}) int {
|
||||
hash, err := hashstructure.Hash(v, nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error computing hash code for %#v: %s", v, err.Error()))
|
||||
}
|
||||
return int(hash)
|
||||
}
|
||||
|
||||
func recordToResourceData(d *schema.ResourceData, r *dns.Record) error {
|
||||
d.SetId(r.ID)
|
||||
d.Set("domain", r.Domain)
|
||||
d.Set("zone", r.Zone)
|
||||
d.Set("type", r.Type)
|
||||
d.Set("ttl", r.TTL)
|
||||
if r.Link != "" {
|
||||
d.Set("link", r.Link)
|
||||
}
|
||||
// if r.Meta != nil {
|
||||
// d.State()
|
||||
// t := metaStructToDynamic(r.Meta)
|
||||
// d.Set("meta", t)
|
||||
// }
|
||||
if len(r.Filters) > 0 {
|
||||
filters := make([]map[string]interface{}, len(r.Filters))
|
||||
for i, f := range r.Filters {
|
||||
m := make(map[string]interface{})
|
||||
m["filter"] = f.Type
|
||||
if f.Disabled {
|
||||
m["disabled"] = true
|
||||
}
|
||||
if f.Config != nil {
|
||||
m["config"] = f.Config
|
||||
}
|
||||
filters[i] = m
|
||||
}
|
||||
d.Set("filters", filters)
|
||||
}
|
||||
if len(r.Answers) > 0 {
|
||||
ans := &schema.Set{
|
||||
F: genericHasher,
|
||||
}
|
||||
log.Printf("Got back from ns1 answers: %+v", r.Answers)
|
||||
for _, answer := range r.Answers {
|
||||
ans.Add(answerToMap(*answer))
|
||||
}
|
||||
log.Printf("Setting answers %+v", ans)
|
||||
err := d.Set("answers", ans)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[DEBUG] Error setting answers for: %s, error: %#v", r.Domain, err)
|
||||
}
|
||||
}
|
||||
if len(r.Regions) > 0 {
|
||||
regions := make([]map[string]interface{}, 0, len(r.Regions))
|
||||
for regionName, _ := range r.Regions {
|
||||
newRegion := make(map[string]interface{})
|
||||
newRegion["name"] = regionName
|
||||
// newRegion["meta"] = metaStructToDynamic(®ion.Meta)
|
||||
regions = append(regions, newRegion)
|
||||
}
|
||||
log.Printf("Setting regions %+v", regions)
|
||||
err := d.Set("regions", regions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[DEBUG] Error setting regions for: %s, error: %#v", r.Domain, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func answerToMap(a dns.Answer) map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
m["answer"] = strings.Join(a.Rdata, " ")
|
||||
if a.RegionName != "" {
|
||||
m["region"] = a.RegionName
|
||||
}
|
||||
// if a.Meta != nil {
|
||||
// m["meta"] = metaStructToDynamic(a.Meta)
|
||||
// }
|
||||
return m
|
||||
}
|
||||
|
||||
func btoi(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error {
|
||||
r.ID = d.Id()
|
||||
if answers := d.Get("answers").(*schema.Set); answers.Len() > 0 {
|
||||
al := make([]*dns.Answer, answers.Len())
|
||||
for i, answerRaw := range answers.List() {
|
||||
answer := answerRaw.(map[string]interface{})
|
||||
var a *dns.Answer
|
||||
v := answer["answer"].(string)
|
||||
switch d.Get("type") {
|
||||
case "TXT":
|
||||
a = dns.NewTXTAnswer(v)
|
||||
default:
|
||||
a = dns.NewAnswer(strings.Split(v, " "))
|
||||
}
|
||||
if v, ok := answer["region"]; ok {
|
||||
a.RegionName = v.(string)
|
||||
}
|
||||
|
||||
// if v, ok := answer["meta"]; ok {
|
||||
// metaDynamicToStruct(a.Meta, v)
|
||||
// }
|
||||
al[i] = a
|
||||
}
|
||||
r.Answers = al
|
||||
if _, ok := d.GetOk("link"); ok {
|
||||
return errors.New("Cannot have both link and answers in a record")
|
||||
}
|
||||
}
|
||||
if v, ok := d.GetOk("ttl"); ok {
|
||||
r.TTL = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("link"); ok {
|
||||
r.LinkTo(v.(string))
|
||||
}
|
||||
// if v, ok := d.GetOk("meta"); ok {
|
||||
// metaDynamicToStruct(r.Meta, v)
|
||||
// }
|
||||
useClientSubnetVal := d.Get("use_client_subnet").(bool)
|
||||
if v := strconv.FormatBool(useClientSubnetVal); v != "" {
|
||||
r.UseClientSubnet = &useClientSubnetVal
|
||||
}
|
||||
|
||||
if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
|
||||
f := make([]*filter.Filter, len(rawFilters))
|
||||
for i, filterRaw := range rawFilters {
|
||||
fi := filterRaw.(map[string]interface{})
|
||||
config := make(map[string]interface{})
|
||||
filter := filter.Filter{
|
||||
Type: fi["filter"].(string),
|
||||
Config: config,
|
||||
}
|
||||
if disabled, ok := fi["disabled"]; ok {
|
||||
filter.Disabled = disabled.(bool)
|
||||
}
|
||||
if rawConfig, ok := fi["config"]; ok {
|
||||
for k, v := range rawConfig.(map[string]interface{}) {
|
||||
if i, err := strconv.Atoi(v.(string)); err == nil {
|
||||
filter.Config[k] = i
|
||||
} else {
|
||||
filter.Config[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
f[i] = &filter
|
||||
}
|
||||
r.Filters = f
|
||||
}
|
||||
if regions := d.Get("regions").(*schema.Set); regions.Len() > 0 {
|
||||
for _, regionRaw := range regions.List() {
|
||||
region := regionRaw.(map[string]interface{})
|
||||
ns1R := data.Region{
|
||||
Meta: data.Meta{},
|
||||
}
|
||||
// if v, ok := region["meta"]; ok {
|
||||
// metaDynamicToStruct(&ns1R.Meta, v)
|
||||
// }
|
||||
|
||||
r.Regions[region["name"].(string)] = ns1R
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordCreate creates DNS record in ns1
|
||||
func RecordCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||
if err := resourceDataToRecord(r, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Records.Create(r); err != nil {
|
||||
return err
|
||||
}
|
||||
return recordToResourceData(d, r)
|
||||
}
|
||||
|
||||
// RecordRead reads the DNS record from ns1
|
||||
func RecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
|
||||
r, _, err := client.Records.Get(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return recordToResourceData(d, r)
|
||||
}
|
||||
|
||||
// RecordDelete deltes the DNS record from ns1
|
||||
func RecordDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.Records.Delete(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// RecordUpdate updates the given dns record in ns1
|
||||
func RecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||
if err := resourceDataToRecord(r, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Records.Update(r); err != nil {
|
||||
return err
|
||||
}
|
||||
return recordToResourceData(d, r)
|
||||
}
|
||||
|
||||
func RecordStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
parts := strings.Split(d.Id(), "/")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("Invalid record specifier. Expecting 2 slashes (\"zone/domain/type\"), got %d.", len(parts)-1)
|
||||
}
|
||||
|
||||
d.Set("zone", parts[0])
|
||||
d.Set("domain", parts[1])
|
||||
d.Set("type", parts[2])
|
||||
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
func TestAccRecord_basic(t *testing.T) {
|
||||
var record dns.Record
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckRecordDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccRecordBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckRecordExists("ns1_record.it", &record),
|
||||
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
|
||||
testAccCheckRecordTTL(&record, 60),
|
||||
testAccCheckRecordRegionName(&record, []string{"cal"}),
|
||||
// testAccCheckRecordAnswerMetaWeight(&record, 10),
|
||||
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccRecord_updated(t *testing.T) {
|
||||
var record dns.Record
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckRecordDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccRecordBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckRecordExists("ns1_record.it", &record),
|
||||
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
|
||||
testAccCheckRecordTTL(&record, 60),
|
||||
testAccCheckRecordRegionName(&record, []string{"cal"}),
|
||||
// testAccCheckRecordAnswerMetaWeight(&record, 10),
|
||||
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccRecordUpdated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckRecordExists("ns1_record.it", &record),
|
||||
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
|
||||
testAccCheckRecordTTL(&record, 120),
|
||||
testAccCheckRecordRegionName(&record, []string{"ny", "wa"}),
|
||||
// testAccCheckRecordAnswerMetaWeight(&record, 5),
|
||||
testAccCheckRecordAnswerRdata(&record, "test2.terraform-record-test.io"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckRecordExists(n string, record *dns.Record) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %v", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("NoID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
p := rs.Primary
|
||||
|
||||
foundRecord, _, err := client.Records.Get(p.Attributes["zone"], p.Attributes["domain"], p.Attributes["type"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
if foundRecord.Domain != p.Attributes["domain"] {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
*record = *foundRecord
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckRecordDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
var recordDomain string
|
||||
var recordZone string
|
||||
var recordType string
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "ns1_record" {
|
||||
continue
|
||||
}
|
||||
|
||||
if rs.Type == "ns1_record" {
|
||||
recordType = rs.Primary.Attributes["type"]
|
||||
recordDomain = rs.Primary.Attributes["domain"]
|
||||
recordZone = rs.Primary.Attributes["zone"]
|
||||
}
|
||||
}
|
||||
|
||||
foundRecord, _, err := client.Records.Get(recordDomain, recordZone, recordType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Record still exists: %#v", foundRecord)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckRecordDomain(r *dns.Record, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if r.Domain != expected {
|
||||
return fmt.Errorf("Domain: got: %#v want: %#v", r.Domain, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckRecordTTL(r *dns.Record, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if r.TTL != expected {
|
||||
return fmt.Errorf("TTL: got: %#v want: %#v", r.TTL, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckRecordRegionName(r *dns.Record, expected []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
regions := make([]string, len(r.Regions))
|
||||
|
||||
i := 0
|
||||
for k := range r.Regions {
|
||||
regions[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(regions)
|
||||
sort.Strings(expected)
|
||||
if !reflect.DeepEqual(regions, expected) {
|
||||
return fmt.Errorf("Regions: got: %#v want: %#v", regions, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckRecordAnswerMetaWeight(r *dns.Record, expected float64) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
recordAnswer := r.Answers[0]
|
||||
recordMetas := recordAnswer.Meta
|
||||
weight := recordMetas.Weight.(float64)
|
||||
if weight != expected {
|
||||
return fmt.Errorf("Answers[0].Meta.Weight: got: %#v want: %#v", weight, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckRecordAnswerRdata(r *dns.Record, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
recordAnswer := r.Answers[0]
|
||||
recordAnswerString := recordAnswer.Rdata[0]
|
||||
if recordAnswerString != expected {
|
||||
return fmt.Errorf("Answers[0].Rdata[0]: got: %#v want: %#v", recordAnswerString, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccRecordBasic = `
|
||||
resource "ns1_record" "it" {
|
||||
zone = "${ns1_zone.test.zone}"
|
||||
domain = "test.${ns1_zone.test.zone}"
|
||||
type = "CNAME"
|
||||
ttl = 60
|
||||
|
||||
// meta {
|
||||
// weight = 5
|
||||
// connections = 3
|
||||
// // up = false // Ignored by d.GetOk("meta.0.up") due to known issue
|
||||
// }
|
||||
|
||||
answers {
|
||||
answer = "test1.terraform-record-test.io"
|
||||
region = "cal"
|
||||
|
||||
// meta {
|
||||
// weight = 10
|
||||
// up = true
|
||||
// }
|
||||
}
|
||||
|
||||
regions {
|
||||
name = "cal"
|
||||
// meta {
|
||||
// up = true
|
||||
// us_state = ["CA"]
|
||||
// }
|
||||
}
|
||||
|
||||
filters {
|
||||
filter = "up"
|
||||
}
|
||||
|
||||
filters {
|
||||
filter = "geotarget_country"
|
||||
}
|
||||
|
||||
filters {
|
||||
filter = "select_first_n"
|
||||
config = {N=1}
|
||||
}
|
||||
}
|
||||
|
||||
resource "ns1_zone" "test" {
|
||||
zone = "terraform-record-test.io"
|
||||
}
|
||||
`
|
||||
|
||||
const testAccRecordUpdated = `
|
||||
resource "ns1_record" "it" {
|
||||
zone = "${ns1_zone.test.zone}"
|
||||
domain = "test.${ns1_zone.test.zone}"
|
||||
type = "CNAME"
|
||||
ttl = 120
|
||||
use_client_subnet = true
|
||||
|
||||
// meta {
|
||||
// weight = 5
|
||||
// connections = 3
|
||||
// // up = false // Ignored by d.GetOk("meta.0.up") due to known issue
|
||||
// }
|
||||
|
||||
answers {
|
||||
answer = "test2.terraform-record-test.io"
|
||||
region = "ny"
|
||||
|
||||
// meta {
|
||||
// weight = 5
|
||||
// up = true
|
||||
// }
|
||||
}
|
||||
|
||||
regions {
|
||||
name = "wa"
|
||||
// meta {
|
||||
// us_state = ["WA"]
|
||||
// }
|
||||
}
|
||||
|
||||
regions {
|
||||
name = "ny"
|
||||
// meta {
|
||||
// us_state = ["NY"]
|
||||
// }
|
||||
}
|
||||
|
||||
filters {
|
||||
filter = "up"
|
||||
}
|
||||
|
||||
filters {
|
||||
filter = "geotarget_country"
|
||||
}
|
||||
}
|
||||
|
||||
resource "ns1_zone" "test" {
|
||||
zone = "terraform-record-test.io"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,89 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
func teamResource() *schema.Resource {
|
||||
s := map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
s = addPermsSchema(s)
|
||||
return &schema.Resource{
|
||||
Schema: s,
|
||||
Create: TeamCreate,
|
||||
Read: TeamRead,
|
||||
Update: TeamUpdate,
|
||||
Delete: TeamDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func teamToResourceData(d *schema.ResourceData, t *account.Team) error {
|
||||
d.SetId(t.ID)
|
||||
d.Set("name", t.Name)
|
||||
permissionsToResourceData(d, t.Permissions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDataToTeam(t *account.Team, d *schema.ResourceData) error {
|
||||
t.ID = d.Id()
|
||||
t.Name = d.Get("name").(string)
|
||||
t.Permissions = resourceDataToPermissions(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TeamCreate creates the given team in ns1
|
||||
func TeamCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
t := account.Team{}
|
||||
if err := resourceDataToTeam(&t, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Teams.Create(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
return teamToResourceData(d, &t)
|
||||
}
|
||||
|
||||
// TeamRead reads the team data from ns1
|
||||
func TeamRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
t, _, err := client.Teams.Get(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return teamToResourceData(d, t)
|
||||
}
|
||||
|
||||
// TeamDelete deletes the given team from ns1
|
||||
func TeamDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.Teams.Delete(d.Id())
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// TeamUpdate updates the given team in ns1
|
||||
func TeamUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
t := account.Team{
|
||||
ID: d.Id(),
|
||||
}
|
||||
if err := resourceDataToTeam(&t, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Teams.Update(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
return teamToResourceData(d, &t)
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
func TestAccTeam_basic(t *testing.T) {
|
||||
var team account.Team
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckTeamDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccTeamBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTeamExists("ns1_team.foobar", &team),
|
||||
testAccCheckTeamName(&team, "terraform test"),
|
||||
testAccCheckTeamDNSPermission(&team, "view_zones", true),
|
||||
testAccCheckTeamDNSPermission(&team, "zones_allow_by_default", true),
|
||||
testAccCheckTeamDNSPermissionZones(&team, "zones_allow", []string{"mytest.zone"}),
|
||||
testAccCheckTeamDNSPermissionZones(&team, "zones_deny", []string{"myother.zone"}),
|
||||
testAccCheckTeamDataPermission(&team, "manage_datasources", true),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccTeam_updated(t *testing.T) {
|
||||
var team account.Team
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckTeamDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccTeamBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTeamExists("ns1_team.foobar", &team),
|
||||
testAccCheckTeamName(&team, "terraform test"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccTeamUpdated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTeamExists("ns1_team.foobar", &team),
|
||||
testAccCheckTeamName(&team, "terraform test updated"),
|
||||
testAccCheckTeamDNSPermission(&team, "view_zones", true),
|
||||
testAccCheckTeamDNSPermission(&team, "zones_allow_by_default", true),
|
||||
testAccCheckTeamDNSPermissionZones(&team, "zones_allow", []string{}),
|
||||
testAccCheckTeamDNSPermissionZones(&team, "zones_deny", []string{}),
|
||||
testAccCheckTeamDataPermission(&team, "manage_datasources", false),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckTeamExists(n string, team *account.Team) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("NoID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
foundTeam, _, err := client.Teams.Get(rs.Primary.Attributes["id"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundTeam.Name != rs.Primary.Attributes["name"] {
|
||||
return fmt.Errorf("Team not found")
|
||||
}
|
||||
|
||||
*team = *foundTeam
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTeamDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "ns1_team" {
|
||||
continue
|
||||
}
|
||||
|
||||
team, _, err := client.Teams.Get(rs.Primary.Attributes["id"])
|
||||
if err == nil {
|
||||
return fmt.Errorf("Team still exists: %#v: %#v", err, team.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckTeamName(team *account.Team, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if team.Name != expected {
|
||||
return fmt.Errorf("Name: got: %s want: %s", team.Name, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTeamDNSPermission(team *account.Team, perm string, expected bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
dns := team.Permissions.DNS
|
||||
|
||||
switch perm {
|
||||
case "view_zones":
|
||||
if dns.ViewZones != expected {
|
||||
return fmt.Errorf("DNS.ViewZones: got: %t want: %t", dns.ViewZones, expected)
|
||||
}
|
||||
case "manage_zones":
|
||||
if dns.ManageZones != expected {
|
||||
return fmt.Errorf("DNS.ManageZones: got: %t want: %t", dns.ManageZones, expected)
|
||||
}
|
||||
case "zones_allow_by_default":
|
||||
if dns.ZonesAllowByDefault != expected {
|
||||
return fmt.Errorf("DNS.ZonesAllowByDefault: got: %t want: %t", dns.ZonesAllowByDefault, expected)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTeamDataPermission(team *account.Team, perm string, expected bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
data := team.Permissions.Data
|
||||
|
||||
switch perm {
|
||||
case "push_to_datafeeds":
|
||||
if data.PushToDatafeeds != expected {
|
||||
return fmt.Errorf("Data.PushToDatafeeds: got: %t want: %t", data.PushToDatafeeds, expected)
|
||||
}
|
||||
case "manage_datasources":
|
||||
if data.ManageDatasources != expected {
|
||||
return fmt.Errorf("Data.ManageDatasources: got: %t want: %t", data.ManageDatasources, expected)
|
||||
}
|
||||
case "manage_datafeeds":
|
||||
if data.ManageDatafeeds != expected {
|
||||
return fmt.Errorf("Data.ManageDatafeeds: got: %t want: %t", data.ManageDatafeeds, expected)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTeamDNSPermissionZones(team *account.Team, perm string, expected []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
dns := team.Permissions.DNS
|
||||
|
||||
switch perm {
|
||||
case "zones_allow":
|
||||
if !reflect.DeepEqual(dns.ZonesAllow, expected) {
|
||||
return fmt.Errorf("DNS.ZonesAllow: got: %v want: %v", dns.ZonesAllow, expected)
|
||||
}
|
||||
case "zones_deny":
|
||||
if !reflect.DeepEqual(dns.ZonesDeny, expected) {
|
||||
return fmt.Errorf("DNS.ZonesDeny: got: %v want: %v", dns.ZonesDeny, expected)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccTeamBasic = `
|
||||
resource "ns1_team" "foobar" {
|
||||
name = "terraform test"
|
||||
|
||||
dns_view_zones = true
|
||||
dns_zones_allow_by_default = true
|
||||
dns_zones_allow = ["mytest.zone"]
|
||||
dns_zones_deny = ["myother.zone"]
|
||||
|
||||
data_manage_datasources = true
|
||||
}`
|
||||
|
||||
const testAccTeamUpdated = `
|
||||
resource "ns1_team" "foobar" {
|
||||
name = "terraform test updated"
|
||||
|
||||
dns_view_zones = true
|
||||
dns_zones_allow_by_default = true
|
||||
|
||||
data_manage_datasources = false
|
||||
}`
|
|
@ -0,0 +1,133 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
func userResource() *schema.Resource {
|
||||
s := map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"username": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"email": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"notify": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"billing": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"teams": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
}
|
||||
s = addPermsSchema(s)
|
||||
return &schema.Resource{
|
||||
Schema: s,
|
||||
Create: UserCreate,
|
||||
Read: UserRead,
|
||||
Update: UserUpdate,
|
||||
Delete: UserDelete,
|
||||
}
|
||||
}
|
||||
|
||||
func userToResourceData(d *schema.ResourceData, u *account.User) error {
|
||||
d.SetId(u.Username)
|
||||
d.Set("name", u.Name)
|
||||
d.Set("email", u.Email)
|
||||
d.Set("teams", u.TeamIDs)
|
||||
notify := make(map[string]bool)
|
||||
notify["billing"] = u.Notify.Billing
|
||||
d.Set("notify", notify)
|
||||
permissionsToResourceData(d, u.Permissions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDataToUser(u *account.User, d *schema.ResourceData) error {
|
||||
u.Name = d.Get("name").(string)
|
||||
u.Username = d.Get("username").(string)
|
||||
u.Email = d.Get("email").(string)
|
||||
if v, ok := d.GetOk("teams"); ok {
|
||||
teamsRaw := v.([]interface{})
|
||||
u.TeamIDs = make([]string, len(teamsRaw))
|
||||
for i, team := range teamsRaw {
|
||||
u.TeamIDs[i] = team.(string)
|
||||
}
|
||||
} else {
|
||||
u.TeamIDs = make([]string, 0)
|
||||
}
|
||||
if v, ok := d.GetOk("notify"); ok {
|
||||
notifyRaw := v.(map[string]interface{})
|
||||
u.Notify.Billing = notifyRaw["billing"].(bool)
|
||||
}
|
||||
u.Permissions = resourceDataToPermissions(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserCreate creates the given user in ns1
|
||||
func UserCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
u := account.User{}
|
||||
if err := resourceDataToUser(&u, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Users.Create(&u); err != nil {
|
||||
return err
|
||||
}
|
||||
return userToResourceData(d, &u)
|
||||
}
|
||||
|
||||
// UserRead reads the given users data from ns1
|
||||
func UserRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
u, _, err := client.Users.Get(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return userToResourceData(d, u)
|
||||
}
|
||||
|
||||
// UserDelete deletes the given user from ns1
|
||||
func UserDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.Users.Delete(d.Id())
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// UserUpdate updates the user with given parameters in ns1
|
||||
func UserUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
u := account.User{
|
||||
Username: d.Id(),
|
||||
}
|
||||
if err := resourceDataToUser(&u, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.Users.Update(&u); err != nil {
|
||||
return err
|
||||
}
|
||||
return userToResourceData(d, &u)
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
func zoneResource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
// Required
|
||||
"zone": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
// Optional
|
||||
"ttl": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
// SOA attributes per https://tools.ietf.org/html/rfc1035).
|
||||
"refresh": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"retry": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"expiry": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
// SOA MINUMUM overloaded as NX TTL per https://tools.ietf.org/html/rfc2308
|
||||
"nx_ttl": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
// TODO: test
|
||||
"link": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
// TODO: test
|
||||
"primary": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
// Computed
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"dns_servers": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"hostmaster": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
Create: ZoneCreate,
|
||||
Read: ZoneRead,
|
||||
Update: ZoneUpdate,
|
||||
Delete: ZoneDelete,
|
||||
Importer: &schema.ResourceImporter{State: ZoneStateFunc},
|
||||
}
|
||||
}
|
||||
|
||||
func zoneToResourceData(d *schema.ResourceData, z *dns.Zone) {
|
||||
d.SetId(z.ID)
|
||||
d.Set("hostmaster", z.Hostmaster)
|
||||
d.Set("ttl", z.TTL)
|
||||
d.Set("nx_ttl", z.NxTTL)
|
||||
d.Set("refresh", z.Refresh)
|
||||
d.Set("retry", z.Retry)
|
||||
d.Set("expiry", z.Expiry)
|
||||
d.Set("dns_servers", strings.Join(z.DNSServers[:], ","))
|
||||
if z.Secondary != nil && z.Secondary.Enabled {
|
||||
d.Set("primary", z.Secondary.PrimaryIP)
|
||||
}
|
||||
if z.Link != nil && *z.Link != "" {
|
||||
d.Set("link", *z.Link)
|
||||
}
|
||||
}
|
||||
|
||||
func resourceToZoneData(z *dns.Zone, d *schema.ResourceData) {
|
||||
z.ID = d.Id()
|
||||
if v, ok := d.GetOk("hostmaster"); ok {
|
||||
z.Hostmaster = v.(string)
|
||||
}
|
||||
if v, ok := d.GetOk("ttl"); ok {
|
||||
z.TTL = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("nx_ttl"); ok {
|
||||
z.NxTTL = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("refresh"); ok {
|
||||
z.Refresh = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("retry"); ok {
|
||||
z.Retry = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("expiry"); ok {
|
||||
z.Expiry = v.(int)
|
||||
}
|
||||
if v, ok := d.GetOk("primary"); ok {
|
||||
z.MakeSecondary(v.(string))
|
||||
}
|
||||
if v, ok := d.GetOk("link"); ok {
|
||||
z.LinkTo(v.(string))
|
||||
}
|
||||
}
|
||||
|
||||
// ZoneCreate creates the given zone in ns1
|
||||
func ZoneCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
z := dns.NewZone(d.Get("zone").(string))
|
||||
resourceToZoneData(z, d)
|
||||
if _, err := client.Zones.Create(z); err != nil {
|
||||
return err
|
||||
}
|
||||
zoneToResourceData(d, z)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ZoneRead reads the given zone data from ns1
|
||||
func ZoneRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
z, _, err := client.Zones.Get(d.Get("zone").(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zoneToResourceData(d, z)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ZoneDelete deteles the given zone from ns1
|
||||
func ZoneDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
_, err := client.Zones.Delete(d.Get("zone").(string))
|
||||
d.SetId("")
|
||||
return err
|
||||
}
|
||||
|
||||
// ZoneUpdate updates the zone with given params in ns1
|
||||
func ZoneUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ns1.Client)
|
||||
z := dns.NewZone(d.Get("zone").(string))
|
||||
resourceToZoneData(z, d)
|
||||
if _, err := client.Zones.Update(z); err != nil {
|
||||
return err
|
||||
}
|
||||
zoneToResourceData(d, z)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ZoneStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
d.Set("zone", d.Id())
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
func TestAccZone_basic(t *testing.T) {
|
||||
var zone dns.Zone
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckZoneDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccZoneBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckZoneExists("ns1_zone.it", &zone),
|
||||
testAccCheckZoneName(&zone, "terraform-test-zone.io"),
|
||||
testAccCheckZoneTTL(&zone, 3600),
|
||||
testAccCheckZoneRefresh(&zone, 43200),
|
||||
testAccCheckZoneRetry(&zone, 7200),
|
||||
testAccCheckZoneExpiry(&zone, 1209600),
|
||||
testAccCheckZoneNxTTL(&zone, 3600),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccZone_updated(t *testing.T) {
|
||||
var zone dns.Zone
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckZoneDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccZoneBasic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckZoneExists("ns1_zone.it", &zone),
|
||||
testAccCheckZoneName(&zone, "terraform-test-zone.io"),
|
||||
testAccCheckZoneTTL(&zone, 3600),
|
||||
testAccCheckZoneRefresh(&zone, 43200),
|
||||
testAccCheckZoneRetry(&zone, 7200),
|
||||
testAccCheckZoneExpiry(&zone, 1209600),
|
||||
testAccCheckZoneNxTTL(&zone, 3600),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccZoneUpdated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckZoneExists("ns1_zone.it", &zone),
|
||||
testAccCheckZoneName(&zone, "terraform-test-zone.io"),
|
||||
testAccCheckZoneTTL(&zone, 10800),
|
||||
testAccCheckZoneRefresh(&zone, 3600),
|
||||
testAccCheckZoneRetry(&zone, 300),
|
||||
testAccCheckZoneExpiry(&zone, 2592000),
|
||||
testAccCheckZoneNxTTL(&zone, 3601),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckZoneExists(n string, zone *dns.Zone) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("NoID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
foundZone, _, err := client.Zones.Get(rs.Primary.Attributes["zone"])
|
||||
|
||||
p := rs.Primary
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundZone.ID != p.Attributes["id"] {
|
||||
return fmt.Errorf("Zone not found")
|
||||
}
|
||||
|
||||
*zone = *foundZone
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckZoneDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ns1.Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "ns1_zone" {
|
||||
continue
|
||||
}
|
||||
|
||||
zone, _, err := client.Zones.Get(rs.Primary.Attributes["zone"])
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Zone still exists: %#v: %#v", err, zone)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckZoneName(zone *dns.Zone, expected string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if zone.Zone != expected {
|
||||
return fmt.Errorf("Zone: got: %s want: %s", zone.Zone, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckZoneTTL(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if zone.TTL != expected {
|
||||
return fmt.Errorf("TTL: got: %d want: %d", zone.TTL, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func testAccCheckZoneRefresh(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if zone.Refresh != expected {
|
||||
return fmt.Errorf("Refresh: got: %d want: %d", zone.Refresh, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func testAccCheckZoneRetry(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if zone.Retry != expected {
|
||||
return fmt.Errorf("Retry: got: %d want: %d", zone.Retry, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func testAccCheckZoneExpiry(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if zone.Expiry != expected {
|
||||
return fmt.Errorf("Expiry: got: %d want: %d", zone.Expiry, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func testAccCheckZoneNxTTL(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if zone.NxTTL != expected {
|
||||
return fmt.Errorf("NxTTL: got: %d want: %d", zone.NxTTL, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccZoneBasic = `
|
||||
resource "ns1_zone" "it" {
|
||||
zone = "terraform-test-zone.io"
|
||||
}
|
||||
`
|
||||
|
||||
const testAccZoneUpdated = `
|
||||
resource "ns1_zone" "it" {
|
||||
zone = "terraform-test-zone.io"
|
||||
ttl = 10800
|
||||
refresh = 3600
|
||||
retry = 300
|
||||
expiry = 2592000
|
||||
nx_ttl = 3601
|
||||
# link = "1.2.3.4.in-addr.arpa" # TODO
|
||||
# primary = "1.2.3.4.in-addr.arpa" # TODO
|
||||
}
|
||||
`
|
|
@ -0,0 +1,47 @@
|
|||
package ns1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StringEnum struct {
|
||||
ValueMap map[string]int
|
||||
Expecting string
|
||||
}
|
||||
|
||||
func NewStringEnum(values []string) *StringEnum {
|
||||
valueMap := make(map[string]int)
|
||||
quoted := make([]string, len(values), len(values))
|
||||
for i, value := range values {
|
||||
_, present := valueMap[value]
|
||||
if present {
|
||||
panic(fmt.Sprintf("duplicate value %q", value))
|
||||
}
|
||||
valueMap[value] = i
|
||||
|
||||
quoted[i] = fmt.Sprintf("%q", value)
|
||||
}
|
||||
|
||||
return &StringEnum{
|
||||
ValueMap: valueMap,
|
||||
Expecting: strings.Join(quoted, ", "),
|
||||
}
|
||||
}
|
||||
|
||||
func (se *StringEnum) Check(v string) (int, error) {
|
||||
i, present := se.ValueMap[v]
|
||||
if present {
|
||||
return i, nil
|
||||
} else {
|
||||
return -1, fmt.Errorf("expecting one of %s; got %q", se.Expecting, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (se *StringEnum) ValidateFunc(v interface{}, k string) (ws []string, es []error) {
|
||||
_, err := se.Check(v.(string))
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
|
@ -40,6 +40,7 @@ import (
|
|||
mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql"
|
||||
newrelicprovider "github.com/hashicorp/terraform/builtin/providers/newrelic"
|
||||
nomadprovider "github.com/hashicorp/terraform/builtin/providers/nomad"
|
||||
ns1provider "github.com/hashicorp/terraform/builtin/providers/ns1"
|
||||
nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
|
||||
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
|
||||
opsgenieprovider "github.com/hashicorp/terraform/builtin/providers/opsgenie"
|
||||
|
@ -108,6 +109,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
|||
"mysql": mysqlprovider.Provider,
|
||||
"newrelic": newrelicprovider.Provider,
|
||||
"nomad": nomadprovider.Provider,
|
||||
"ns1": ns1provider.Provider,
|
||||
"null": nullprovider.Provider,
|
||||
"openstack": openstackprovider.Provider,
|
||||
"opsgenie": opsgenieprovider.Provider,
|
||||
|
|
|
@ -0,0 +1,678 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
|
||||
APACHE HTTP SERVER SUBCOMPONENTS:
|
||||
|
||||
The Apache HTTP Server includes a number of subcomponents with
|
||||
separate copyright notices and license terms. Your use of the source
|
||||
code for the these subcomponents is subject to the terms and
|
||||
conditions of the following licenses.
|
||||
|
||||
For the mod_mime_magic component:
|
||||
|
||||
/*
|
||||
* mod_mime_magic: MIME type lookup via file magic numbers
|
||||
* Copyright (c) 1996-1997 Cisco Systems, Inc.
|
||||
*
|
||||
* This software was submitted by Cisco Systems to the Apache Group in July
|
||||
* 1997. Future revisions and derivatives of this source code must
|
||||
* acknowledge Cisco Systems as the original contributor of this module.
|
||||
* All other licensing and usage conditions are those of the Apache Group.
|
||||
*
|
||||
* Some of this code is derived from the free version of the file command
|
||||
* originally posted to comp.sources.unix. Copyright info for that program
|
||||
* is included below as required.
|
||||
* ---------------------------------------------------------------------------
|
||||
* - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin.
|
||||
*
|
||||
* This software is not subject to any license of the American Telephone and
|
||||
* Telegraph Company or of the Regents of the University of California.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose on any
|
||||
* computer system, and to alter it and redistribute it freely, subject to
|
||||
* the following restrictions:
|
||||
*
|
||||
* 1. The author is not responsible for the consequences of use of this
|
||||
* software, no matter how awful, even if they arise from flaws in it.
|
||||
*
|
||||
* 2. The origin of this software must not be misrepresented, either by
|
||||
* explicit claim or by omission. Since few users ever read sources, credits
|
||||
* must appear in the documentation.
|
||||
*
|
||||
* 3. Altered versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software. Since few users ever read
|
||||
* sources, credits must appear in the documentation.
|
||||
*
|
||||
* 4. This notice may not be removed or altered.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
For the modules\mappers\mod_imap.c component:
|
||||
|
||||
"macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
|
||||
|
||||
For the server\util_md5.c component:
|
||||
|
||||
/************************************************************************
|
||||
* NCSA HTTPd Server
|
||||
* Software Development Group
|
||||
* National Center for Supercomputing Applications
|
||||
* University of Illinois at Urbana-Champaign
|
||||
* 605 E. Springfield, Champaign, IL 61820
|
||||
* httpd@ncsa.uiuc.edu
|
||||
*
|
||||
* Copyright (C) 1995, Board of Trustees of the University of Illinois
|
||||
*
|
||||
************************************************************************
|
||||
*
|
||||
* md5.c: NCSA HTTPd code which uses the md5c.c RSA Code
|
||||
*
|
||||
* Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc.
|
||||
* Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon
|
||||
* University (see Copyright below).
|
||||
* Portions of Content-MD5 code Copyright (C) 1991 Bell Communications
|
||||
* Research, Inc. (Bellcore) (see Copyright below).
|
||||
* Portions extracted from mpack, John G. Myers - jgm+@cmu.edu
|
||||
* Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk)
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */
|
||||
/* (C) Copyright 1993,1994 by Carnegie Mellon University
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software
|
||||
* and its documentation for any purpose is hereby granted without
|
||||
* fee, provided that the above copyright notice appear in all copies
|
||||
* and that both that copyright notice and this permission notice
|
||||
* appear in supporting documentation, and that the name of Carnegie
|
||||
* Mellon University not be used in advertising or publicity
|
||||
* pertaining to distribution of the software without specific,
|
||||
* written prior permission. Carnegie Mellon University makes no
|
||||
* representations about the suitability of this software for any
|
||||
* purpose. It is provided "as is" without express or implied
|
||||
* warranty.
|
||||
*
|
||||
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
|
||||
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore)
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this material
|
||||
* for any purpose and without fee is hereby granted, provided
|
||||
* that the above copyright notice and this permission notice
|
||||
* appear in all copies, and that the name of Bellcore not be
|
||||
* used in advertising or publicity pertaining to this
|
||||
* material without the specific, prior written permission
|
||||
* of an authorized representative of Bellcore. BELLCORE
|
||||
* MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
|
||||
* OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS",
|
||||
* WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
|
||||
*/
|
||||
|
||||
For the srclib\apr\include\apr_md5.h component:
|
||||
/*
|
||||
* This is work is derived from material Copyright RSA Data Security, Inc.
|
||||
*
|
||||
* The RSA copyright statement and Licence for that original material is
|
||||
* included below. This is followed by the Apache copyright statement and
|
||||
* licence for the modifications made to that material.
|
||||
*/
|
||||
|
||||
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||
rights reserved.
|
||||
|
||||
License to copy and use this software is granted provided that it
|
||||
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
|
||||
Algorithm" in all material mentioning or referencing this software
|
||||
or this function.
|
||||
|
||||
License is also granted to make and use derivative works provided
|
||||
that such works are identified as "derived from the RSA Data
|
||||
Security, Inc. MD5 Message-Digest Algorithm" in all material
|
||||
mentioning or referencing the derived work.
|
||||
|
||||
RSA Data Security, Inc. makes no representations concerning either
|
||||
the merchantability of this software or the suitability of this
|
||||
software for any particular purpose. It is provided "as is"
|
||||
without express or implied warranty of any kind.
|
||||
|
||||
These notices must be retained in any copies of any part of this
|
||||
documentation and/or software.
|
||||
*/
|
||||
|
||||
For the srclib\apr\passwd\apr_md5.c component:
|
||||
|
||||
/*
|
||||
* This is work is derived from material Copyright RSA Data Security, Inc.
|
||||
*
|
||||
* The RSA copyright statement and Licence for that original material is
|
||||
* included below. This is followed by the Apache copyright statement and
|
||||
* licence for the modifications made to that material.
|
||||
*/
|
||||
|
||||
/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
|
||||
*/
|
||||
|
||||
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||
rights reserved.
|
||||
|
||||
License to copy and use this software is granted provided that it
|
||||
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
|
||||
Algorithm" in all material mentioning or referencing this software
|
||||
or this function.
|
||||
|
||||
License is also granted to make and use derivative works provided
|
||||
that such works are identified as "derived from the RSA Data
|
||||
Security, Inc. MD5 Message-Digest Algorithm" in all material
|
||||
mentioning or referencing the derived work.
|
||||
|
||||
RSA Data Security, Inc. makes no representations concerning either
|
||||
the merchantability of this software or the suitability of this
|
||||
software for any particular purpose. It is provided "as is"
|
||||
without express or implied warranty of any kind.
|
||||
|
||||
These notices must be retained in any copies of any part of this
|
||||
documentation and/or software.
|
||||
*/
|
||||
/*
|
||||
* The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
|
||||
* MD5 crypt() function, which is licenced as follows:
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
|
||||
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
For the srclib\apr-util\crypto\apr_md4.c component:
|
||||
|
||||
* This is derived from material copyright RSA Data Security, Inc.
|
||||
* Their notice is reproduced below in its entirety.
|
||||
*
|
||||
* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||
* rights reserved.
|
||||
*
|
||||
* License to copy and use this software is granted provided that it
|
||||
* is identified as the "RSA Data Security, Inc. MD4 Message-Digest
|
||||
* Algorithm" in all material mentioning or referencing this software
|
||||
* or this function.
|
||||
*
|
||||
* License is also granted to make and use derivative works provided
|
||||
* that such works are identified as "derived from the RSA Data
|
||||
* Security, Inc. MD4 Message-Digest Algorithm" in all material
|
||||
* mentioning or referencing the derived work.
|
||||
*
|
||||
* RSA Data Security, Inc. makes no representations concerning either
|
||||
* the merchantability of this software or the suitability of this
|
||||
* software for any particular purpose. It is provided "as is"
|
||||
* without express or implied warranty of any kind.
|
||||
*
|
||||
* These notices must be retained in any copies of any part of this
|
||||
* documentation and/or software.
|
||||
*/
|
||||
|
||||
For the srclib\apr-util\include\apr_md4.h component:
|
||||
|
||||
*
|
||||
* This is derived from material copyright RSA Data Security, Inc.
|
||||
* Their notice is reproduced below in its entirety.
|
||||
*
|
||||
* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||
* rights reserved.
|
||||
*
|
||||
* License to copy and use this software is granted provided that it
|
||||
* is identified as the "RSA Data Security, Inc. MD4 Message-Digest
|
||||
* Algorithm" in all material mentioning or referencing this software
|
||||
* or this function.
|
||||
*
|
||||
* License is also granted to make and use derivative works provided
|
||||
* that such works are identified as "derived from the RSA Data
|
||||
* Security, Inc. MD4 Message-Digest Algorithm" in all material
|
||||
* mentioning or referencing the derived work.
|
||||
*
|
||||
* RSA Data Security, Inc. makes no representations concerning either
|
||||
* the merchantability of this software or the suitability of this
|
||||
* software for any particular purpose. It is provided "as is"
|
||||
* without express or implied warranty of any kind.
|
||||
*
|
||||
* These notices must be retained in any copies of any part of this
|
||||
* documentation and/or software.
|
||||
*/
|
||||
|
||||
|
||||
For the srclib\apr-util\test\testdbm.c component:
|
||||
|
||||
/* ====================================================================
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution,
|
||||
* if any, must include the following acknowledgment:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowledgment may appear in the software itself,
|
||||
* if and wherever such third-party acknowledgments normally appear.
|
||||
*
|
||||
* 4. The names "Apache" and "Apache Software Foundation" must
|
||||
* not be used to endorse or promote products derived from this
|
||||
* software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache",
|
||||
* nor may "Apache" appear in their name, without prior written
|
||||
* permission of the Apache Software Foundation.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
* This file came from the SDBM package (written by oz@nexus.yorku.ca).
|
||||
* That package was under public domain. This file has been ported to
|
||||
* APR, updated to ANSI C and other, newer idioms, and added to the Apache
|
||||
* codebase under the above copyright and license.
|
||||
*/
|
||||
|
||||
|
||||
For the srclib\apr-util\test\testmd4.c component:
|
||||
|
||||
*
|
||||
* This is derived from material copyright RSA Data Security, Inc.
|
||||
* Their notice is reproduced below in its entirety.
|
||||
*
|
||||
* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
|
||||
* rights reserved.
|
||||
*
|
||||
* RSA Data Security, Inc. makes no representations concerning either
|
||||
* the merchantability of this software or the suitability of this
|
||||
* software for any particular purpose. It is provided "as is"
|
||||
* without express or implied warranty of any kind.
|
||||
*
|
||||
* These notices must be retained in any copies of any part of this
|
||||
* documentation and/or software.
|
||||
*/
|
||||
|
||||
For the srclib\apr-util\xml\expat\conftools\install-sh component:
|
||||
|
||||
#
|
||||
# install - install a program, script, or datafile
|
||||
# This comes from X11R5 (mit/util/scripts/install.sh).
|
||||
#
|
||||
# Copyright 1991 by the Massachusetts Institute of Technology
|
||||
#
|
||||
# Permission to use, copy, modify, distribute, and sell this software and its
|
||||
# documentation for any purpose is hereby granted without fee, provided that
|
||||
# the above copyright notice appear in all copies and that both that
|
||||
# copyright notice and this permission notice appear in supporting
|
||||
# documentation, and that the name of M.I.T. not be used in advertising or
|
||||
# publicity pertaining to distribution of the software without specific,
|
||||
# written prior permission. M.I.T. makes no representations about the
|
||||
# suitability of this software for any purpose. It is provided "as is"
|
||||
# without express or implied warranty.
|
||||
#
|
||||
|
||||
For the srclib\pcre\install-sh component:
|
||||
|
||||
#
|
||||
# Copyright 1991 by the Massachusetts Institute of Technology
|
||||
#
|
||||
# Permission to use, copy, modify, distribute, and sell this software and its
|
||||
# documentation for any purpose is hereby granted without fee, provided that
|
||||
# the above copyright notice appear in all copies and that both that
|
||||
# copyright notice and this permission notice appear in supporting
|
||||
# documentation, and that the name of M.I.T. not be used in advertising or
|
||||
# publicity pertaining to distribution of the software without specific,
|
||||
# written prior permission. M.I.T. makes no representations about the
|
||||
# suitability of this software for any purpose. It is provided "as is"
|
||||
# without express or implied warranty.
|
||||
|
||||
For the pcre component:
|
||||
|
||||
PCRE LICENCE
|
||||
------------
|
||||
|
||||
PCRE is a library of functions to support regular expressions whose syntax
|
||||
and semantics are as close as possible to those of the Perl 5 language.
|
||||
|
||||
Written by: Philip Hazel <ph10@cam.ac.uk>
|
||||
|
||||
University of Cambridge Computing Service,
|
||||
Cambridge, England. Phone: +44 1223 334714.
|
||||
|
||||
Copyright (c) 1997-2001 University of Cambridge
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose on any
|
||||
computer system, and to redistribute it freely, subject to the following
|
||||
restrictions:
|
||||
|
||||
1. This software 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.
|
||||
|
||||
2. The origin of this software must not be misrepresented, either by
|
||||
explicit claim or by omission. In practice, this means that if you use
|
||||
PCRE in software which you distribute to others, commercially or
|
||||
otherwise, you must put a sentence like this
|
||||
|
||||
Regular expression support is provided by the PCRE library package,
|
||||
which is open source software, written by Philip Hazel, and copyright
|
||||
by the University of Cambridge, England.
|
||||
|
||||
somewhere reasonably visible in your documentation and in any relevant
|
||||
files or online help data or similar. A reference to the ftp site for
|
||||
the source, that is, to
|
||||
|
||||
ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
|
||||
|
||||
should also be given in the documentation.
|
||||
|
||||
3. Altered versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
4. If PCRE is embedded in any software that is released under the GNU
|
||||
General Purpose Licence (GPL), or Lesser General Purpose Licence (LGPL),
|
||||
then the terms of that licence shall supersede any condition above with
|
||||
which it is incompatible.
|
||||
|
||||
The documentation for PCRE, supplied in the "doc" directory, is distributed
|
||||
under the same terms as the software itself.
|
||||
|
||||
End PCRE LICENCE
|
||||
|
||||
|
||||
For the test\zb.c component:
|
||||
|
||||
/* ZeusBench V1.01
|
||||
===============
|
||||
|
||||
This program is Copyright (C) Zeus Technology Limited 1996.
|
||||
|
||||
This program may be used and copied freely providing this copyright notice
|
||||
is not removed.
|
||||
|
||||
This software is provided "as is" and any express or implied waranties,
|
||||
including but not limited to, the implied warranties of merchantability and
|
||||
fitness for a particular purpose are disclaimed. In no event shall
|
||||
Zeus Technology Ltd. be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damaged (including, but not limited to,
|
||||
procurement of substitute good or services; loss of use, data, or profits;
|
||||
or business interruption) however caused and on theory of liability. Whether
|
||||
in contract, strict liability or tort (including negligence or otherwise)
|
||||
arising in any way out of the use of this software, even if advised of the
|
||||
possibility of such damage.
|
||||
|
||||
Written by Adam Twiss (adam@zeus.co.uk). March 1996
|
||||
|
||||
Thanks to the following people for their input:
|
||||
Mike Belshe (mbelshe@netscape.com)
|
||||
Michael Campanella (campanella@stevms.enet.dec.com)
|
||||
|
||||
*/
|
||||
|
||||
For the expat xml parser component:
|
||||
|
||||
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
|
||||
and Clark Cooper
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
====================================================================
|
|
@ -0,0 +1,11 @@
|
|||
.PHONY: all clean
|
||||
|
||||
all: .git/hooks/pre-commit
|
||||
go build .
|
||||
|
||||
clean:
|
||||
rm -f terraform-provider-nsone
|
||||
|
||||
.git/hooks/pre-commit:
|
||||
if [ ! -f .git/hooks/pre-commit ]; then ln -s ../../git-hooks/pre-commit .git/hooks/pre-commit; fi
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
[![Build Status](https://travis-ci.org/ns1/ns1-go.svg?branch=v2)](https://travis-ci.org/ns1/ns1-go) [![GoDoc](https://godoc.org/gopkg.in/ns1/ns1-go.v2?status.svg)](https://godoc.org/gopkg.in/ns1/ns1-go.v2)
|
||||
|
||||
# NS1 Golang SDK
|
||||
|
||||
The golang client for the NS1 API: https://api.nsone.net/
|
||||
|
||||
# Installing
|
||||
|
||||
```
|
||||
$ go get gopkg.in/ns1/ns1-go.v2
|
||||
```
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
[See more](https://github.com/ns1/ns1-go/tree/v2/rest/_examples)
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
api "gopkg.in/ns1/ns1-go.v2/rest"
|
||||
)
|
||||
|
||||
func main() {
|
||||
k := os.Getenv("NS1_APIKEY")
|
||||
if k == "" {
|
||||
fmt.Println("NS1_APIKEY environment variable is not set, giving up")
|
||||
}
|
||||
|
||||
httpClient := &http.Client{Timeout: time.Second * 10}
|
||||
client := api.NewClient(httpClient, api.SetAPIKey(k))
|
||||
|
||||
zones, _, err := client.Zones.List()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, z := range zones {
|
||||
fmt.Println(z.Zone)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Contributions, ideas and criticisms are all welcome.
|
||||
|
||||
# LICENSE
|
||||
|
||||
Apache2 - see the included LICENSE file for more information
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// Package ns1 is the NS1 golang SDK.
|
||||
//
|
||||
// To understand the REST models and terminology,
|
||||
// please visit the ns1 web page:
|
||||
//
|
||||
// https://ns1.com/
|
||||
//
|
||||
package ns1
|
|
@ -0,0 +1,143 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
// APIKeysService handles 'account/apikeys' endpoint.
|
||||
type APIKeysService service
|
||||
|
||||
// List returns all api keys in the account.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#apikeys-get
|
||||
func (s *APIKeysService) List() ([]*account.APIKey, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "account/apikeys", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
kl := []*account.APIKey{}
|
||||
resp, err := s.client.Do(req, &kl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return kl, resp, nil
|
||||
}
|
||||
|
||||
// Get returns details of an api key, including permissions, for a single API Key.
|
||||
// Note: do not use the API Key itself as the keyid in the URL — use the id of the key.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#apikeys-id-get
|
||||
func (s *APIKeysService) Get(keyID string) (*account.APIKey, *http.Response, error) {
|
||||
path := fmt.Sprintf("account/apikeys/%s", keyID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var a account.APIKey
|
||||
resp, err := s.client.Do(req, &a)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "unknown api key" {
|
||||
return nil, resp, ErrKeyMissing
|
||||
}
|
||||
default:
|
||||
return nil, resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return &a, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *APIKey and creates a new account apikey.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#apikeys-put
|
||||
func (s *APIKeysService) Create(a *account.APIKey) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("PUT", "account/apikeys", &a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update account fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &a)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == fmt.Sprintf("api key with name \"%s\" exists", a.Name) {
|
||||
return resp, ErrKeyExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update changes the name or access rights for an API Key.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#apikeys-id-post
|
||||
func (s *APIKeysService) Update(a *account.APIKey) (*http.Response, error) {
|
||||
path := fmt.Sprintf("account/apikeys/%s", a.ID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update apikey fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &a)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "unknown api key" {
|
||||
return resp, ErrKeyMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes an apikey.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#apikeys-id-delete
|
||||
func (s *APIKeysService) Delete(keyID string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("account/apikeys/%s", keyID)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "unknown api key" {
|
||||
return resp, ErrKeyMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrKeyExists bundles PUT create error.
|
||||
ErrKeyExists = errors.New("Key already exists.")
|
||||
// ErrKeyMissing bundles GET/POST/DELETE error.
|
||||
ErrKeyMissing = errors.New("Key does not exist.")
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
// SettingsService handles 'account/settings' endpoint.
|
||||
type SettingsService service
|
||||
|
||||
// Get returns the basic contact details associated with the account.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#settings-get
|
||||
func (s *SettingsService) Get() (*account.Setting, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "account/settings", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var us account.Setting
|
||||
resp, err := s.client.Do(req, &us)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &us, resp, nil
|
||||
}
|
||||
|
||||
// Update changes most of the basic contact details, except customerid.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#settings-post
|
||||
func (s *SettingsService) Update(us *account.Setting) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("POST", "account/settings", &us)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update usagewarnings fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &us)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
// TeamsService handles 'account/teams' endpoint.
|
||||
type TeamsService service
|
||||
|
||||
// List returns all teams in the account.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#teams-get
|
||||
func (s *TeamsService) List() ([]*account.Team, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "account/teams", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tl := []*account.Team{}
|
||||
resp, err := s.client.Do(req, &tl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return tl, resp, nil
|
||||
}
|
||||
|
||||
// Get returns details of a single team.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#teams-id-get
|
||||
func (s *TeamsService) Get(id string) (*account.Team, *http.Response, error) {
|
||||
path := fmt.Sprintf("account/teams/%s", id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var t account.Team
|
||||
resp, err := s.client.Do(req, &t)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "Unknown team id" {
|
||||
return nil, resp, ErrTeamMissing
|
||||
}
|
||||
default:
|
||||
return nil, resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return &t, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *Team and creates a new account team.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#teams-put
|
||||
func (s *TeamsService) Create(t *account.Team) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("PUT", "account/teams", &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update team fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &t)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == fmt.Sprintf("team with name \"%s\" exists", t.Name) {
|
||||
return resp, ErrTeamExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update changes the name or access rights for a team.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#teams-id-post
|
||||
func (s *TeamsService) Update(t *account.Team) (*http.Response, error) {
|
||||
path := fmt.Sprintf("account/teams/%s", t.ID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update team fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &t)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "unknown team id" {
|
||||
return resp, ErrTeamMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes a team.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#teams-id-delete
|
||||
func (s *TeamsService) Delete(id string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("account/teams/%s", id)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "unknown team id" {
|
||||
return resp, ErrTeamMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrTeamExists bundles PUT create error.
|
||||
ErrTeamExists = errors.New("Team already exists.")
|
||||
// ErrTeamMissing bundles GET/POST/DELETE error.
|
||||
ErrTeamMissing = errors.New("Team does not exist.")
|
||||
)
|
|
@ -0,0 +1,142 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
// UsersService handles 'account/users' endpoint.
|
||||
type UsersService service
|
||||
|
||||
// List returns all users in the account.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#users-get
|
||||
func (s *UsersService) List() ([]*account.User, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "account/users", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ul := []*account.User{}
|
||||
resp, err := s.client.Do(req, &ul)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return ul, resp, nil
|
||||
}
|
||||
|
||||
// Get returns details of a single user.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#users-user-get
|
||||
func (s *UsersService) Get(username string) (*account.User, *http.Response, error) {
|
||||
path := fmt.Sprintf("account/users/%s", username)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var u account.User
|
||||
resp, err := s.client.Do(req, &u)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "Unknown user" {
|
||||
return nil, resp, ErrUserMissing
|
||||
}
|
||||
default:
|
||||
return nil, resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return &u, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *User and creates a new account user.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#users-put
|
||||
func (s *UsersService) Create(u *account.User) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("PUT", "account/users", &u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update user fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &u)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "request failed:Login Name is already in use." {
|
||||
return resp, ErrUserExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update change contact details, notification settings, or access rights for a user.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#users-user-post
|
||||
func (s *UsersService) Update(u *account.User) (*http.Response, error) {
|
||||
path := fmt.Sprintf("account/users/%s", u.Username)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update user fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &u)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "Unknown user" {
|
||||
return resp, ErrUserMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes a user.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#users-user-delete
|
||||
func (s *UsersService) Delete(username string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("account/users/%s", username)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "Unknown user" {
|
||||
return resp, ErrUserMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrUserExists bundles PUT create error.
|
||||
ErrUserExists = errors.New("User already exists.")
|
||||
// ErrUserMissing bundles GET/POST/DELETE error.
|
||||
ErrUserMissing = errors.New("User does not exist.")
|
||||
)
|
|
@ -0,0 +1,47 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||
)
|
||||
|
||||
// WarningsService handles 'account/usagewarnings' endpoint.
|
||||
type WarningsService service
|
||||
|
||||
// Get returns toggles and thresholds used when sending overage warning
|
||||
// alert messages to users with billing notifications enabled.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#usagewarnings-get
|
||||
func (s *WarningsService) Get() (*account.UsageWarning, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "account/usagewarnings", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var uw account.UsageWarning
|
||||
resp, err := s.client.Do(req, &uw)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &uw, resp, nil
|
||||
}
|
||||
|
||||
// Update changes alerting toggles and thresholds for overage warning alert messages.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#usagewarnings-post
|
||||
func (s *WarningsService) Update(uw *account.UsageWarning) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("POST", "account/usagewarnings", &uw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update usagewarnings fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &uw)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
clientVersion = "2.0.0"
|
||||
defaultEndpoint = "https://api.nsone.net/v1/"
|
||||
defaultUserAgent = "go-ns1/" + clientVersion
|
||||
|
||||
headerAuth = "X-NSONE-Key"
|
||||
headerRateLimit = "X-Ratelimit-Limit"
|
||||
headerRateRemaining = "X-Ratelimit-Remaining"
|
||||
headerRatePeriod = "X-Ratelimit-Period"
|
||||
)
|
||||
|
||||
// Doer is a single method interface that allows a user to extend/augment an http.Client instance.
|
||||
// Note: http.Client satisfies the Doer interface.
|
||||
type Doer interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Client manages communication with the NS1 Rest API.
|
||||
type Client struct {
|
||||
// httpClient handles all rest api communication,
|
||||
// and expects an *http.Client.
|
||||
httpClient Doer
|
||||
|
||||
// NS1 rest endpoint, overrides default if given.
|
||||
Endpoint *url.URL
|
||||
|
||||
// NS1 api key (value for http request header 'X-NSONE-Key').
|
||||
APIKey string
|
||||
|
||||
// NS1 go rest user agent (value for http request header 'User-Agent').
|
||||
UserAgent string
|
||||
|
||||
// Func to call after response is returned in Do
|
||||
RateLimitFunc func(RateLimit)
|
||||
|
||||
// From the excellent github-go client.
|
||||
common service // Reuse a single struct instead of allocating one for each service on the heap.
|
||||
|
||||
// Services used for communicating with different components of the NS1 API.
|
||||
APIKeys *APIKeysService
|
||||
DataFeeds *DataFeedsService
|
||||
DataSources *DataSourcesService
|
||||
Jobs *JobsService
|
||||
Notifications *NotificationsService
|
||||
Records *RecordsService
|
||||
Settings *SettingsService
|
||||
Teams *TeamsService
|
||||
Users *UsersService
|
||||
Warnings *WarningsService
|
||||
Zones *ZonesService
|
||||
}
|
||||
|
||||
// NewClient constructs and returns a reference to an instantiated Client.
|
||||
func NewClient(httpClient Doer, options ...func(*Client)) *Client {
|
||||
endpoint, _ := url.Parse(defaultEndpoint)
|
||||
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
httpClient: httpClient,
|
||||
Endpoint: endpoint,
|
||||
RateLimitFunc: defaultRateLimitFunc,
|
||||
UserAgent: defaultUserAgent,
|
||||
}
|
||||
|
||||
c.common.client = c
|
||||
c.APIKeys = (*APIKeysService)(&c.common)
|
||||
c.DataFeeds = (*DataFeedsService)(&c.common)
|
||||
c.DataSources = (*DataSourcesService)(&c.common)
|
||||
c.Jobs = (*JobsService)(&c.common)
|
||||
c.Notifications = (*NotificationsService)(&c.common)
|
||||
c.Records = (*RecordsService)(&c.common)
|
||||
c.Settings = (*SettingsService)(&c.common)
|
||||
c.Teams = (*TeamsService)(&c.common)
|
||||
c.Users = (*UsersService)(&c.common)
|
||||
c.Warnings = (*WarningsService)(&c.common)
|
||||
c.Zones = (*ZonesService)(&c.common)
|
||||
|
||||
for _, option := range options {
|
||||
option(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type service struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// SetHTTPClient sets a Client instances' httpClient.
|
||||
func SetHTTPClient(httpClient Doer) func(*Client) {
|
||||
return func(c *Client) { c.httpClient = httpClient }
|
||||
}
|
||||
|
||||
// SetAPIKey sets a Client instances' APIKey.
|
||||
func SetAPIKey(key string) func(*Client) {
|
||||
return func(c *Client) { c.APIKey = key }
|
||||
}
|
||||
|
||||
// SetEndpoint sets a Client instances' Endpoint.
|
||||
func SetEndpoint(endpoint string) func(*Client) {
|
||||
return func(c *Client) { c.Endpoint, _ = url.Parse(endpoint) }
|
||||
}
|
||||
|
||||
// SetUserAgent sets a Client instances' user agent.
|
||||
func SetUserAgent(ua string) func(*Client) {
|
||||
return func(c *Client) { c.UserAgent = ua }
|
||||
}
|
||||
|
||||
// SetRateLimitFunc sets a Client instances' RateLimitFunc.
|
||||
func SetRateLimitFunc(ratefunc func(rl RateLimit)) func(*Client) {
|
||||
return func(c *Client) { c.RateLimitFunc = ratefunc }
|
||||
}
|
||||
|
||||
// Do satisfies the Doer interface.
|
||||
func (c Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = CheckResponse(resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
rl := parseRate(resp)
|
||||
c.RateLimitFunc(rl)
|
||||
|
||||
if v != nil {
|
||||
// Try to unmarshal body into given type using streaming decoder.
|
||||
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// NewRequest constructs and returns a http.Request.
|
||||
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
|
||||
rel, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uri := c.Endpoint.ResolveReference(rel)
|
||||
|
||||
// Encode body as json
|
||||
buf := new(bytes.Buffer)
|
||||
if body != nil {
|
||||
err := json.NewEncoder(buf).Encode(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, uri.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add(headerAuth, c.APIKey)
|
||||
req.Header.Add("User-Agent", c.UserAgent)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Response wraps stdlib http response.
|
||||
type Response struct {
|
||||
*http.Response
|
||||
}
|
||||
|
||||
// Error contains all http responses outside the 2xx range.
|
||||
type Error struct {
|
||||
Resp *http.Response
|
||||
Message string
|
||||
}
|
||||
|
||||
// Satisfy std lib error interface.
|
||||
func (re *Error) Error() string {
|
||||
return fmt.Sprintf("%v %v: %d %v", re.Resp.Request.Method, re.Resp.Request.URL, re.Resp.StatusCode, re.Message)
|
||||
}
|
||||
|
||||
// CheckResponse handles parsing of rest api errors. Returns nil if no error.
|
||||
func CheckResponse(resp *http.Response) error {
|
||||
if c := resp.StatusCode; c >= 200 && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
restErr := &Error{Resp: resp}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return restErr
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, restErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return restErr
|
||||
}
|
||||
|
||||
// RateLimitFunc is rate limiting strategy for the Client instance.
|
||||
type RateLimitFunc func(RateLimit)
|
||||
|
||||
// RateLimit stores X-Ratelimit-* headers
|
||||
type RateLimit struct {
|
||||
Limit int
|
||||
Remaining int
|
||||
Period int
|
||||
}
|
||||
|
||||
var defaultRateLimitFunc = func(rl RateLimit) {}
|
||||
|
||||
// PercentageLeft returns the ratio of Remaining to Limit as a percentage
|
||||
func (rl RateLimit) PercentageLeft() int {
|
||||
return rl.Remaining * 100 / rl.Limit
|
||||
}
|
||||
|
||||
// WaitTime returns the time.Duration ratio of Period to Limit
|
||||
func (rl RateLimit) WaitTime() time.Duration {
|
||||
return (time.Second * time.Duration(rl.Period)) / time.Duration(rl.Limit)
|
||||
}
|
||||
|
||||
// WaitTimeRemaining returns the time.Duration ratio of Period to Remaining
|
||||
func (rl RateLimit) WaitTimeRemaining() time.Duration {
|
||||
return (time.Second * time.Duration(rl.Period)) / time.Duration(rl.Remaining)
|
||||
}
|
||||
|
||||
// RateLimitStrategySleep sets RateLimitFunc to sleep by WaitTimeRemaining
|
||||
func (c *Client) RateLimitStrategySleep() {
|
||||
c.RateLimitFunc = func(rl RateLimit) {
|
||||
remaining := rl.WaitTimeRemaining()
|
||||
time.Sleep(remaining)
|
||||
}
|
||||
}
|
||||
|
||||
// parseRate parses rate related headers from http response.
|
||||
func parseRate(resp *http.Response) RateLimit {
|
||||
var rl RateLimit
|
||||
|
||||
if limit := resp.Header.Get(headerRateLimit); limit != "" {
|
||||
rl.Limit, _ = strconv.Atoi(limit)
|
||||
}
|
||||
if remaining := resp.Header.Get(headerRateRemaining); remaining != "" {
|
||||
rl.Remaining, _ = strconv.Atoi(remaining)
|
||||
}
|
||||
if period := resp.Header.Get(headerRatePeriod); period != "" {
|
||||
rl.Period, _ = strconv.Atoi(period)
|
||||
}
|
||||
|
||||
return rl
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRateLimit(t *testing.T) {
|
||||
r := RateLimit{
|
||||
Limit: 10,
|
||||
Remaining: 10,
|
||||
Period: 10,
|
||||
}
|
||||
if r.WaitTime() != time.Second {
|
||||
t.Error("WaitTime is wrong duration ", r.WaitTime())
|
||||
}
|
||||
if r.PercentageLeft() != 100 {
|
||||
t.Error("PercentLeft != 100")
|
||||
}
|
||||
r.Remaining = 5
|
||||
if r.PercentageLeft() != 50 {
|
||||
t.Error("PercentLeft != 50")
|
||||
}
|
||||
if r.WaitTime() != time.Second {
|
||||
t.Error("WaitTime is wrong duration ", r.WaitTime())
|
||||
}
|
||||
if r.WaitTimeRemaining() != (time.Duration(2) * time.Second) {
|
||||
t.Error("WaitTimeRemaining is wrong duration ", r.WaitTimeRemaining())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
// DataFeedsService handles 'data/feeds' endpoint.
|
||||
type DataFeedsService service
|
||||
|
||||
// List returns all data feeds connected to a given data source.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#feeds-get
|
||||
func (s *DataFeedsService) List(sourceID string) ([]*data.Feed, *http.Response, error) {
|
||||
path := fmt.Sprintf("data/feeds/%s", sourceID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dfl := []*data.Feed{}
|
||||
resp, err := s.client.Do(req, &dfl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return dfl, resp, nil
|
||||
}
|
||||
|
||||
// Get takes a data source ID and a data feed ID and returns the details of a single data feed
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#feeds-feed-get
|
||||
func (s *DataFeedsService) Get(sourceID string, feedID string) (*data.Feed, *http.Response, error) {
|
||||
path := fmt.Sprintf("data/feeds/%s/%s", sourceID, feedID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var df data.Feed
|
||||
resp, err := s.client.Do(req, &df)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &df, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *DataFeed and connects a new data feed to an existing data source.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#feeds-put
|
||||
func (s *DataFeedsService) Create(sourceID string, df *data.Feed) (*http.Response, error) {
|
||||
path := fmt.Sprintf("data/feeds/%s", sourceID)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", path, &df)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update datafeeds' fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &df)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update takes a *Feed and modifies and existing data feed.
|
||||
// Note:
|
||||
// - The 'data' portion of a feed does not actually
|
||||
// get updated during a POST. In order to update a feeds'
|
||||
// 'data' attribute, one must use the Publish method.
|
||||
// - Both the 'destinations' and 'networks' attributes are
|
||||
// not updated during a POST.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#feeds-post
|
||||
func (s *DataFeedsService) Update(sourceID string, df *data.Feed) (*http.Response, error) {
|
||||
path := fmt.Sprintf("data/feeds/%s/%s", sourceID, df.ID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &df)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update df instance fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &df)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete takes a data source ID and a data feed ID and disconnects the feed from the data source and all attached destination metadata tables.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#feeds-delete
|
||||
func (s *DataFeedsService) Delete(sourceID string, feedID string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("data/feeds/%s/%s", sourceID, feedID)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
// DataSourcesService handles 'data/sources' endpoint.
|
||||
type DataSourcesService service
|
||||
|
||||
// List returns all connected data sources.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#sources-get
|
||||
func (s *DataSourcesService) List() ([]*data.Source, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "data/sources", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dsl := []*data.Source{}
|
||||
resp, err := s.client.Do(req, &dsl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return dsl, resp, nil
|
||||
}
|
||||
|
||||
// Get takes an ID returns the details for a single data source.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#sources-source-get
|
||||
func (s *DataSourcesService) Get(id string) (*data.Source, *http.Response, error) {
|
||||
path := fmt.Sprintf("data/sources/%s", id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var ds data.Source
|
||||
resp, err := s.client.Do(req, &ds)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &ds, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *DataSource and creates a new data source.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#sources-put
|
||||
func (s *DataSourcesService) Create(ds *data.Source) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("PUT", "data/sources", &ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update data sources' fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &ds)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update takes a *DataSource modifies basic details of a data source.
|
||||
// NOTE: This does not 'publish' data. See the Publish method.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#sources-post
|
||||
func (s *DataSourcesService) Update(ds *data.Source) (*http.Response, error) {
|
||||
path := fmt.Sprintf("data/sources/%s", ds.ID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update data sources' instance fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &ds)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete takes an ID and removes an existing data source and all connected feeds from the source.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#sources-delete
|
||||
func (s *DataSourcesService) Delete(id string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("data/sources/%s", id)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Publish takes a datasources' id and data to publish.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#feed-post
|
||||
func (s *DataSourcesService) Publish(dsID string, data interface{}) (*http.Response, error) {
|
||||
path := fmt.Sprintf("feed/%s", dsID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package rest defines the api services used to communicate with NS1.
|
||||
package rest
|
|
@ -0,0 +1,13 @@
|
|||
package account
|
||||
|
||||
// APIKey wraps an NS1 /account/apikeys resource
|
||||
type APIKey struct {
|
||||
// Read-only fields
|
||||
ID string `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
LastAccess int `json:"last_access,omitempty"`
|
||||
|
||||
Name string `json:"name"`
|
||||
TeamIDs []string `json:"teams"`
|
||||
Permissions PermissionsMap `json:"permissions"`
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package account contains definitions for NS1 apikeys/teams/users/etc.
|
||||
package account
|
|
@ -0,0 +1,44 @@
|
|||
package account
|
||||
|
||||
// PermissionsMap wraps a User's "permissions" attribute
|
||||
type PermissionsMap struct {
|
||||
DNS PermissionsDNS `json:"dns"`
|
||||
Data PermissionsData `json:"data"`
|
||||
Account PermissionsAccount `json:"account"`
|
||||
Monitoring PermissionsMonitoring `json:"monitoring"`
|
||||
}
|
||||
|
||||
// PermissionsDNS wraps a User's "permissions.dns" attribute
|
||||
type PermissionsDNS struct {
|
||||
ViewZones bool `json:"view_zones"`
|
||||
ManageZones bool `json:"manage_zones"`
|
||||
ZonesAllowByDefault bool `json:"zones_allow_by_default"`
|
||||
ZonesDeny []string `json:"zones_deny"`
|
||||
ZonesAllow []string `json:"zones_allow"`
|
||||
}
|
||||
|
||||
// PermissionsData wraps a User's "permissions.data" attribute
|
||||
type PermissionsData struct {
|
||||
PushToDatafeeds bool `json:"push_to_datafeeds"`
|
||||
ManageDatasources bool `json:"manage_datasources"`
|
||||
ManageDatafeeds bool `json:"manage_datafeeds"`
|
||||
}
|
||||
|
||||
// PermissionsAccount wraps a User's "permissions.account" attribute
|
||||
type PermissionsAccount struct {
|
||||
ManageUsers bool `json:"manage_users"`
|
||||
ManagePaymentMethods bool `json:"manage_payment_methods"`
|
||||
ManagePlan bool `json:"manage_plan"`
|
||||
ManageTeams bool `json:"manage_teams"`
|
||||
ManageApikeys bool `json:"manage_apikeys"`
|
||||
ManageAccountSettings bool `json:"manage_account_settings"`
|
||||
ViewActivityLog bool `json:"view_activity_log"`
|
||||
ViewInvoices bool `json:"view_invoices"`
|
||||
}
|
||||
|
||||
// PermissionsMonitoring wraps a User's "permissions.monitoring" attribute
|
||||
type PermissionsMonitoring struct {
|
||||
ManageLists bool `json:"manage_lists"`
|
||||
ManageJobs bool `json:"manage_jobs"`
|
||||
ViewJobs bool `json:"view_jobs"`
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package account
|
||||
|
||||
// Setting represents an accounts' contact info.
|
||||
type Setting struct {
|
||||
CustomerID int `json:"customerid,omitempty"`
|
||||
FirstName string `json:"firstname,omitempty"`
|
||||
LastName string `json:"lastname,omitempty"`
|
||||
Company string `json:"company,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Address Address `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
// Address for Setting struct.
|
||||
type Address struct {
|
||||
Country string `json:"country,omitempty"`
|
||||
Street string `json:"street,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
Postal string `json:"postalcode,omitempty"`
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package account
|
||||
|
||||
// Team wraps an NS1 /accounts/teams resource
|
||||
type Team struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Permissions PermissionsMap `json:"permissions"`
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package account
|
||||
|
||||
// User wraps an NS1 /account/users resource
|
||||
type User struct {
|
||||
// Read-only fields
|
||||
LastAccess float64 `json:"last_access"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
TeamIDs []string `json:"teams"`
|
||||
Notify NotificationSettings `json:"notify"`
|
||||
Permissions PermissionsMap `json:"permissions"`
|
||||
}
|
||||
|
||||
// NotificationSettings wraps a User's "notify" attribute
|
||||
type NotificationSettings struct {
|
||||
Billing bool `json:"billing"`
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package account
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnmarshalUsers(t *testing.T) {
|
||||
d := []byte(`[
|
||||
{
|
||||
"permissions": {},
|
||||
"teams": [],
|
||||
"email": "support@nsone.net",
|
||||
"last_access": 1376325771.0,
|
||||
"notify": {
|
||||
"billing": true
|
||||
},
|
||||
"name": "API Example",
|
||||
"username": "apiexample"
|
||||
},
|
||||
{
|
||||
"permissions": {
|
||||
"dns": {
|
||||
"view_zones": true,
|
||||
"manage_zones": true,
|
||||
"zones_allow_by_default": false,
|
||||
"zones_deny": [],
|
||||
"zones_allow": ["example.com"]
|
||||
},
|
||||
"data": {
|
||||
"push_to_datafeeds": false,
|
||||
"manage_datasources": false,
|
||||
"manage_datafeeds": false
|
||||
},
|
||||
"account": {
|
||||
"manage_payment_methods": false,
|
||||
"manage_plan": false,
|
||||
"manage_teams": false,
|
||||
"manage_apikeys": false,
|
||||
"manage_account_settings": false,
|
||||
"view_activity_log": false,
|
||||
"view_invoices": false,
|
||||
"manage_users": false
|
||||
},
|
||||
"monitoring": {
|
||||
"manage_lists": false,
|
||||
"manage_jobs": false,
|
||||
"view_jobs": false
|
||||
}
|
||||
},
|
||||
"teams": ["520422919f782d37dffb588a"],
|
||||
"email": "newuser@example.com",
|
||||
"last_access": null,
|
||||
"notify": {
|
||||
"billing": true
|
||||
},
|
||||
"name": "New User",
|
||||
"username": "newuser"
|
||||
}
|
||||
]
|
||||
`)
|
||||
ul := []*User{}
|
||||
if err := json.Unmarshal(d, &ul); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(ul), 2, "Userlist should have 2 users")
|
||||
|
||||
u := ul[0]
|
||||
assert.Equal(t, u.TeamIDs, []string{}, "User should have empty teams")
|
||||
assert.Equal(t, u.Email, "support@nsone.net", "User wrong email")
|
||||
assert.Equal(t, u.LastAccess, 1376325771.0, "User wrong last access")
|
||||
assert.Equal(t, u.Name, "API Example", "User wrong name")
|
||||
assert.Equal(t, u.Username, "apiexample", "User wrong username")
|
||||
assert.Equal(t, u.Notify, NotificationSettings{true}, "User wrong notify")
|
||||
assert.Equal(t, u.Permissions, PermissionsMap{}, "User should have empty permissions")
|
||||
|
||||
u2 := ul[1]
|
||||
assert.Equal(t, u2.TeamIDs, []string{"520422919f782d37dffb588a"}, "User should have empty teams")
|
||||
assert.Equal(t, u2.Email, "newuser@example.com", "User wrong email")
|
||||
assert.Equal(t, u2.LastAccess, 0.0, "User wrong last access")
|
||||
assert.Equal(t, u2.Name, "New User", "User wrong name")
|
||||
assert.Equal(t, u2.Username, "newuser", "User wrong username")
|
||||
assert.Equal(t, u.Notify, NotificationSettings{true}, "User wrong notify")
|
||||
|
||||
permMap := PermissionsMap{
|
||||
DNS: PermissionsDNS{
|
||||
ViewZones: true,
|
||||
ManageZones: true,
|
||||
ZonesAllowByDefault: false,
|
||||
ZonesDeny: []string{},
|
||||
ZonesAllow: []string{"example.com"},
|
||||
},
|
||||
Data: PermissionsData{
|
||||
PushToDatafeeds: false,
|
||||
ManageDatasources: false,
|
||||
ManageDatafeeds: false,
|
||||
},
|
||||
Account: PermissionsAccount{
|
||||
ManagePaymentMethods: false,
|
||||
ManagePlan: false,
|
||||
ManageTeams: false,
|
||||
ManageApikeys: false,
|
||||
ManageAccountSettings: false,
|
||||
ViewActivityLog: false,
|
||||
ViewInvoices: false,
|
||||
ManageUsers: false,
|
||||
},
|
||||
Monitoring: PermissionsMonitoring{
|
||||
ManageLists: false,
|
||||
ManageJobs: false,
|
||||
ViewJobs: false,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, u2.Permissions, permMap, "User wrong permissions")
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package account
|
||||
|
||||
// UsageWarning wraps an NS1 /account/usagewarnings resource
|
||||
type UsageWarning struct {
|
||||
Records Warning `json:"records"`
|
||||
Queries Warning `json:"queries"`
|
||||
}
|
||||
|
||||
// Warning contains alerting toggles and thresholds for overage warning alert messages.
|
||||
// First thresholds must be smaller than Second ones and all thresholds
|
||||
// must be percentages between 0 and 100.
|
||||
type Warning struct {
|
||||
Send bool `json:"send_warnings"`
|
||||
|
||||
First int `json:"warning_1"`
|
||||
Second int `json:"warning_2"`
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package data contains definitions for NS1 metadata/sources/feeds/etc.
|
||||
package data
|
|
@ -0,0 +1,70 @@
|
|||
package data_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
func ExampleSource() {
|
||||
// Construct an NSONE API data source.
|
||||
source := data.NewSource("my api source", "nsone_v1")
|
||||
fmt.Println(source.ID) // will be empty string
|
||||
fmt.Println(source.Name)
|
||||
fmt.Println(source.Type)
|
||||
// Output:
|
||||
// my api source
|
||||
// nsone_v1
|
||||
}
|
||||
|
||||
func ExampleFeed() {
|
||||
|
||||
// Construct the london data feed.
|
||||
feed := data.NewFeed(
|
||||
"London Feed",
|
||||
data.Config{"label": "London-UK"})
|
||||
fmt.Println(feed.ID) // will be empty string
|
||||
fmt.Println(feed.Name)
|
||||
fmt.Println(feed.Config)
|
||||
// Output:
|
||||
// London Feed
|
||||
// map[label:London-UK]
|
||||
}
|
||||
|
||||
func ExampleMeta() {
|
||||
feedID := "feed_id"
|
||||
|
||||
meta := data.Meta{}
|
||||
meta.Priority = 1
|
||||
meta.Up = data.FeedPtr{FeedID: feedID}
|
||||
fmt.Println(meta.Connections) // will be nil
|
||||
fmt.Println(meta.Priority)
|
||||
fmt.Println(meta.Up)
|
||||
// Output:
|
||||
// <nil>
|
||||
// 1
|
||||
// {feed_id}
|
||||
}
|
||||
|
||||
func ExampleRegions() {
|
||||
feedPtr := data.FeedPtr{FeedID: "feed_id"}
|
||||
|
||||
regions := data.Regions{}
|
||||
// Set a regions' 'up' metavalue to false('down').
|
||||
regions["some_region"] = data.Region{
|
||||
Meta: data.Meta{Up: false},
|
||||
}
|
||||
// Set a regions' 'connections' metavalue to receive from a feed.
|
||||
regions["other_region"] = data.Region{
|
||||
Meta: data.Meta{Connections: feedPtr},
|
||||
}
|
||||
fmt.Println(regions["some_region"].Meta.Up)
|
||||
fmt.Println(regions["some_region"].Meta.Priority)
|
||||
fmt.Println(regions["other_region"].Meta.Connections)
|
||||
fmt.Println(regions["other_region"].Meta.Priority)
|
||||
// Output:
|
||||
// false
|
||||
// <nil>
|
||||
// {feed_id}
|
||||
// <nil>
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package data
|
||||
|
||||
// Destination is the target resource the receives data from a feed/source.
|
||||
type Destination struct {
|
||||
ID string `json:"destid"`
|
||||
|
||||
// All destinations must point to a record.
|
||||
RecordID string `json:"record"`
|
||||
|
||||
// Type is the 'level' at which to apply the filters(on the targeted record).
|
||||
// Options:
|
||||
// - answer (highest precedence)
|
||||
// - region
|
||||
// - record (lowest precendence)
|
||||
Type string `json:"desttype"`
|
||||
|
||||
SourceID string `json:"-"`
|
||||
}
|
||||
|
||||
// NewDestination returns an empty feed destination.
|
||||
func NewDestination() *Destination {
|
||||
return &Destination{}
|
||||
}
|
||||
|
||||
// Feed wraps an NS1 /data/feeds resource
|
||||
type Feed struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Config Config `json:"config,omitempty"`
|
||||
Data Meta `json:"data,omitempty"`
|
||||
|
||||
SourceID string
|
||||
}
|
||||
|
||||
// NewFeed returns a data feed with given name and config.
|
||||
func NewFeed(name string, cfg Config) *Feed {
|
||||
return &Feed{Name: name, Config: cfg}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package data
|
||||
|
||||
// FeedPtr represents the dynamic metadata value in which a feed is providing the value.
|
||||
type FeedPtr struct {
|
||||
FeedID string `json:"feed,omitempty"`
|
||||
}
|
||||
|
||||
// Meta contains information on an entities metadata table. Metadata key/value
|
||||
// pairs are used by a records' filter pipeline during a dns query.
|
||||
// All values can be a feed id as well, indicating real-time updates of these values.
|
||||
// Structure/Precendence of metadata tables:
|
||||
// - Record
|
||||
// - Meta <- lowest precendence in filter
|
||||
// - Region(s)
|
||||
// - Meta <- middle precedence in filter chain
|
||||
// - ...
|
||||
// - Answer(s)
|
||||
// - Meta <- highest precedence in filter chain
|
||||
// - ...
|
||||
// - ...
|
||||
type Meta struct {
|
||||
// STATUS
|
||||
|
||||
// Indicates whether or not entity is considered 'up'
|
||||
// bool or FeedPtr.
|
||||
Up interface{} `json:"up,omitempty"`
|
||||
|
||||
// Indicates the number of active connections.
|
||||
// Values must be positive.
|
||||
// int or FeedPtr.
|
||||
Connections interface{} `json:"connections,omitempty"`
|
||||
|
||||
// Indicates the number of active requests (HTTP or otherwise).
|
||||
// Values must be positive.
|
||||
// int or FeedPtr.
|
||||
Requests interface{} `json:"requests,omitempty"`
|
||||
|
||||
// Indicates the "load average".
|
||||
// Values must be positive, and will be rounded to the nearest tenth.
|
||||
// float64 or FeedPtr.
|
||||
LoadAvg interface{} `json:",loadavg,omitempty"`
|
||||
|
||||
// The Job ID of a Pulsar telemetry gathering job and routing granularities
|
||||
// to associate with.
|
||||
// string or FeedPtr.
|
||||
Pulsar interface{} `json:"pulsar,omitempty"`
|
||||
|
||||
// GEOGRAPHICAL
|
||||
|
||||
// Must be between -180.0 and +180.0 where negative
|
||||
// indicates South and positive indicates North.
|
||||
// e.g., the longitude of the datacenter where a server resides.
|
||||
// float64 or FeedPtr.
|
||||
Latitude interface{} `json:"latitude,omitempty"`
|
||||
|
||||
// Must be between -180.0 and +180.0 where negative
|
||||
// indicates West and positive indicates East.
|
||||
// e.g., the longitude of the datacenter where a server resides.
|
||||
// float64 or FeedPtr.
|
||||
Longitude interface{} `json:"longitude,omitempty"`
|
||||
|
||||
// Valid geographic regions are: 'US-EAST', 'US-CENTRAL', 'US-WEST',
|
||||
// 'EUROPE', 'ASIAPAC', 'SOUTH-AMERICA', 'AFRICA'.
|
||||
// e.g., the rough geographic location of the Datacenter where a server resides.
|
||||
// []string or FeedPtr.
|
||||
Georegion interface{} `json:"georegion,omitempty"`
|
||||
|
||||
// Countr(ies) must be specified as ISO3166 2-character country code(s).
|
||||
// []string or FeedPtr.
|
||||
Country interface{} `json:"country,omitempty"`
|
||||
|
||||
// State(s) must be specified as standard 2-character state code(s).
|
||||
// []string or FeedPtr.
|
||||
USState interface{} `json:"us_state,omitempty"`
|
||||
|
||||
// Canadian Province(s) must be specified as standard 2-character province
|
||||
// code(s).
|
||||
// []string or FeedPtr.
|
||||
CAProvince interface{} `json:"ca_province,omitempty"`
|
||||
|
||||
// INFORMATIONAL
|
||||
|
||||
// Notes to indicate any necessary details for operators.
|
||||
// Up to 256 characters in length.
|
||||
// string or FeedPtr.
|
||||
Note interface{} `json:"note,omitempty"`
|
||||
|
||||
// NETWORK
|
||||
|
||||
// IP (v4 and v6) prefixes in CIDR format ("a.b.c.d/mask").
|
||||
// May include up to 1000 prefixes.
|
||||
// e.g., "1.2.3.4/24"
|
||||
// []string or FeedPtr.
|
||||
IPPrefixes interface{} `json:"ip_prefixes,omitempty"`
|
||||
|
||||
// Autonomous System (AS) number(s).
|
||||
// May include up to 1000 AS numbers.
|
||||
// []string or FeedPtr.
|
||||
ASN interface{} `json:"asn,omitempty"`
|
||||
|
||||
// TRAFFIC
|
||||
|
||||
// Indicates the "priority tier".
|
||||
// Lower values indicate higher priority.
|
||||
// Values must be positive.
|
||||
// int or FeedPtr.
|
||||
Priority interface{} `json:"priority,omitempty"`
|
||||
|
||||
// Indicates a weight.
|
||||
// Filters that use weights normalize them.
|
||||
// Any positive values are allowed.
|
||||
// Values between 0 and 100 are recommended for simplicity's sake.
|
||||
// float64 or FeedPtr.
|
||||
Weight interface{} `json:"weight,omitempty"`
|
||||
|
||||
// Indicates a "low watermark" to use for load shedding.
|
||||
// The value should depend on the metric used to determine
|
||||
// load (e.g., loadavg, connections, etc).
|
||||
// int or FeedPtr.
|
||||
LowWatermark interface{} `json:"low_watermark,omitempty"`
|
||||
|
||||
// Indicates a "high watermark" to use for load shedding.
|
||||
// The value should depend on the metric used to determine
|
||||
// load (e.g., loadavg, connections, etc).
|
||||
// int or FeedPtr.
|
||||
HighWatermark interface{} `json:"high_watermark,omitempty"`
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package data
|
||||
|
||||
// Region is a metadata table with a name/key.
|
||||
// Can be thought of as metadata groupings.
|
||||
type Region struct {
|
||||
Meta Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// Regions is simply a mapping of Regions inside a record.
|
||||
type Regions map[string]Region
|
|
@ -0,0 +1,28 @@
|
|||
package data
|
||||
|
||||
// Config is a flat mapping where values are simple (no slices/maps).
|
||||
type Config map[string]interface{}
|
||||
|
||||
// Source wraps an NS1 /data/sources resource
|
||||
type Source struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// Human readable name of the source.
|
||||
Name string `json:"name"`
|
||||
|
||||
Type string `json:"sourcetype"`
|
||||
Config Config `json:"config,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
Feeds []*Feed `json:"feeds,omitempty"`
|
||||
}
|
||||
|
||||
// NewSource takes a name and type t.
|
||||
func NewSource(name string, t string) *Source {
|
||||
return &Source{
|
||||
Name: name,
|
||||
Type: t,
|
||||
Config: Config{},
|
||||
Feeds: []*Feed{},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
)
|
||||
|
||||
// Answer wraps the values of a Record's "filters" attribute
|
||||
type Answer struct {
|
||||
Meta *data.Meta `json:"meta,omitempty"`
|
||||
|
||||
// Answer response data. eg:
|
||||
// Av4: ["1.1.1.1"]
|
||||
// Av6: ["2001:db8:85a3::8a2e:370:7334"]
|
||||
// MX: [10, "2.2.2.2"]
|
||||
Rdata []string `json:"answer"`
|
||||
|
||||
// Region(grouping) that answer belongs to.
|
||||
RegionName string `json:"region,omitempty"`
|
||||
}
|
||||
|
||||
func (a Answer) String() string {
|
||||
return strings.Trim(fmt.Sprint(a.Rdata), "[]")
|
||||
}
|
||||
|
||||
// SetRegion associates a region with this answer.
|
||||
func (a *Answer) SetRegion(name string) {
|
||||
a.RegionName = name
|
||||
}
|
||||
|
||||
// NewAnswer creates a generic Answer with given rdata.
|
||||
func NewAnswer(rdata []string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: rdata,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAv4Answer creates an Answer for A record.
|
||||
func NewAv4Answer(host string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{host},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAv6Answer creates an Answer for AAAA record.
|
||||
func NewAv6Answer(host string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{host},
|
||||
}
|
||||
}
|
||||
|
||||
// NewALIASAnswer creates an Answer for ALIAS record.
|
||||
func NewALIASAnswer(host string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{host},
|
||||
}
|
||||
}
|
||||
|
||||
// NewCNAMEAnswer creates an Answer for CNAME record.
|
||||
func NewCNAMEAnswer(name string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{name},
|
||||
}
|
||||
}
|
||||
|
||||
// NewTXTAnswer creates an Answer for TXT record.
|
||||
func NewTXTAnswer(text string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{text},
|
||||
}
|
||||
}
|
||||
|
||||
// NewMXAnswer creates an Answer for MX record.
|
||||
func NewMXAnswer(pri int, host string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{strconv.Itoa(pri), host},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSRVAnswer creates an Answer for SRV record.
|
||||
func NewSRVAnswer(priority, weight, port int, target string) *Answer {
|
||||
return &Answer{
|
||||
Meta: &data.Meta{},
|
||||
Rdata: []string{
|
||||
strconv.Itoa(priority),
|
||||
strconv.Itoa(weight),
|
||||
strconv.Itoa(port),
|
||||
target,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package dns contains definitions for NS1 zones/records/answers/etc.
|
||||
package dns
|
|
@ -0,0 +1,141 @@
|
|||
package dns_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
|
||||
)
|
||||
|
||||
func ExampleZone() {
|
||||
z := dns.NewZone("example.com")
|
||||
|
||||
fmt.Println(z)
|
||||
// Output:
|
||||
// example.com
|
||||
}
|
||||
|
||||
// Example references https://ns1.com/articles/primary-dns-with-ns1
|
||||
func ExamplePrimaryZone() {
|
||||
// Secondary/slave dns server info.
|
||||
secondary := dns.ZoneSecondaryServer{
|
||||
IP: "1.2.3.4",
|
||||
Port: 53,
|
||||
Notify: true,
|
||||
}
|
||||
|
||||
// Construct the primary/master zone.
|
||||
domain := "masterzone.example"
|
||||
|
||||
masterZone := dns.NewZone(domain)
|
||||
masterZone.MakePrimary(secondary)
|
||||
|
||||
b, _ := json.MarshalIndent(masterZone, "", " ")
|
||||
|
||||
fmt.Println(string(b))
|
||||
// Output:
|
||||
// {
|
||||
// "zone": "masterzone.example",
|
||||
// "primary": {
|
||||
// "enabled": true,
|
||||
// "secondaries": [
|
||||
// {
|
||||
// "ip": "1.2.3.4",
|
||||
// "port": 53,
|
||||
// "notify": true
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
func ExampleRecord() {
|
||||
// Construct the A record
|
||||
record := dns.NewRecord("test.com", "a", "A")
|
||||
record.TTL = 300
|
||||
|
||||
// Construct primary answer(higher priority)
|
||||
pAns := dns.NewAv4Answer("1.1.1.1")
|
||||
pAns.Meta.Priority = 1
|
||||
pAns.Meta.Up = data.FeedPtr{FeedID: "feed1_id"}
|
||||
|
||||
// Construct secondary answer(lower priority)
|
||||
sAns := dns.NewAv4Answer("2.2.2.2")
|
||||
sAns.Meta.Priority = 2
|
||||
sAns.Meta.Up = data.FeedPtr{FeedID: "feed2_id"}
|
||||
|
||||
// Add both answers to record
|
||||
record.AddAnswer(pAns)
|
||||
record.AddAnswer(sAns)
|
||||
|
||||
// Construct and add both filters to the record(ORDER MATTERS)
|
||||
record.AddFilter(filter.NewUp())
|
||||
record.AddFilter(filter.NewSelFirstN(1))
|
||||
|
||||
// Add region 'test' to record(set as down)
|
||||
record.Regions["test"] = data.Region{Meta: data.Meta{Up: false}}
|
||||
|
||||
fmt.Println(record)
|
||||
fmt.Println(record.TTL)
|
||||
|
||||
fmt.Println("Primary answer:")
|
||||
fmt.Println(record.Answers[0])
|
||||
fmt.Println(record.Answers[0].Meta.Priority)
|
||||
fmt.Println(record.Answers[0].Meta.Up)
|
||||
|
||||
fmt.Println("Secondary answer:")
|
||||
fmt.Println(record.Answers[1])
|
||||
fmt.Println(record.Answers[1].Meta.Priority)
|
||||
fmt.Println(record.Answers[1].Meta.Up)
|
||||
|
||||
fmt.Println("First Filter in Chain:")
|
||||
fmt.Println(record.Filters[0].Type)
|
||||
fmt.Println(record.Filters[0].Config)
|
||||
|
||||
fmt.Println("Second Filter in Chain:")
|
||||
fmt.Println(record.Filters[1].Type)
|
||||
fmt.Println(record.Filters[1].Config)
|
||||
|
||||
fmt.Println("Regions:")
|
||||
fmt.Println(record.Regions["test"].Meta.Up)
|
||||
|
||||
// Output:
|
||||
// a.test.com A
|
||||
// 300
|
||||
// Primary answer:
|
||||
// 1.1.1.1
|
||||
// 1
|
||||
// {feed1_id}
|
||||
// Secondary answer:
|
||||
// 2.2.2.2
|
||||
// 2
|
||||
// {feed2_id}
|
||||
// First Filter in Chain:
|
||||
// up
|
||||
// map[]
|
||||
// Second Filter in Chain:
|
||||
// select_first_n
|
||||
// map[N:1]
|
||||
// Regions:
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleRecordLink() {
|
||||
// Construct the src record
|
||||
srcRecord := dns.NewRecord("test.com", "a", "A")
|
||||
srcRecord.TTL = 300
|
||||
srcRecord.Meta.Priority = 2
|
||||
|
||||
linkedRecord := dns.NewRecord("test.com", "l", "A")
|
||||
linkedRecord.LinkTo(srcRecord.Domain)
|
||||
fmt.Println(linkedRecord)
|
||||
fmt.Println(linkedRecord.Meta)
|
||||
fmt.Println(linkedRecord.Answers)
|
||||
// Output:
|
||||
// l.test.com A
|
||||
// <nil>
|
||||
// []
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
|
||||
)
|
||||
|
||||
// Record wraps an NS1 /zone/{zone}/{domain}/{type} resource
|
||||
type Record struct {
|
||||
Meta *data.Meta `json:"meta,omitempty"`
|
||||
|
||||
ID string `json:"id,omitempty"`
|
||||
Zone string `json:"zone"`
|
||||
Domain string `json:"domain"`
|
||||
Type string `json:"type"`
|
||||
Link string `json:"link,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
UseClientSubnet *bool `json:"use_client_subnet,omitempty"`
|
||||
|
||||
// Answers must all be of the same type as the record.
|
||||
Answers []*Answer `json:"answers"`
|
||||
// The records' filter chain.
|
||||
Filters []*filter.Filter `json:"filters,omitempty"`
|
||||
// The records' regions.
|
||||
Regions data.Regions `json:"regions,omitempty"`
|
||||
}
|
||||
|
||||
func (r Record) String() string {
|
||||
return fmt.Sprintf("%s %s", r.Domain, r.Type)
|
||||
}
|
||||
|
||||
// NewRecord takes a zone, domain and record type t and creates a *Record with
|
||||
// UseClientSubnet: true & empty Answers.
|
||||
func NewRecord(zone string, domain string, t string) *Record {
|
||||
if !strings.HasSuffix(domain, zone) {
|
||||
domain = fmt.Sprintf("%s.%s", domain, zone)
|
||||
}
|
||||
return &Record{
|
||||
Meta: &data.Meta{},
|
||||
Zone: zone,
|
||||
Domain: domain,
|
||||
Type: t,
|
||||
Answers: []*Answer{},
|
||||
Regions: data.Regions{},
|
||||
}
|
||||
}
|
||||
|
||||
// LinkTo sets a Record Link to an FQDN.
|
||||
// to is the FQDN of the target record whose config should be used. Does
|
||||
// not have to be in the same zone.
|
||||
func (r *Record) LinkTo(to string) {
|
||||
r.Meta = nil
|
||||
r.Answers = []*Answer{}
|
||||
r.Link = to
|
||||
}
|
||||
|
||||
// AddAnswer adds an answer to the record.
|
||||
func (r *Record) AddAnswer(ans *Answer) {
|
||||
if r.Answers == nil {
|
||||
r.Answers = []*Answer{}
|
||||
}
|
||||
|
||||
r.Answers = append(r.Answers, ans)
|
||||
}
|
||||
|
||||
// AddFilter adds a filter to the records' filter chain(ordering of filters matters).
|
||||
func (r *Record) AddFilter(fil *filter.Filter) {
|
||||
if r.Filters == nil {
|
||||
r.Filters = []*filter.Filter{}
|
||||
}
|
||||
|
||||
r.Filters = append(r.Filters, fil)
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package dns
|
||||
|
||||
import "gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||
|
||||
// Zone wraps an NS1 /zone resource
|
||||
type Zone struct {
|
||||
// Zones have metadata tables, but no filters act on 'zone-level' meta.
|
||||
Meta *data.Meta `json:"meta,omitempty"`
|
||||
|
||||
// Read-only fields
|
||||
DNSServers []string `json:"dns_servers,omitempty"`
|
||||
NetworkPools []string `json:"network_pools,omitempty"`
|
||||
Pool string `json:"pool,omitempty"` // Deprecated
|
||||
|
||||
ID string `json:"id,omitempty"`
|
||||
Zone string `json:"zone,omitempty"`
|
||||
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
NxTTL int `json:"nx_ttl,omitempty"`
|
||||
Retry int `json:"retry,omitempty"`
|
||||
Serial int `json:"serial,omitempty"`
|
||||
Refresh int `json:"refresh,omitempty"`
|
||||
Expiry int `json:"expiry,omitempty"`
|
||||
Hostmaster string `json:"hostmaster,omitempty"`
|
||||
|
||||
// If this is a linked zone, Link points to an existing standard zone,
|
||||
// reusing its configuration and records. Link is a zones' domain name.
|
||||
Link *string `json:"link,omitempty"`
|
||||
|
||||
// Networks contains the network ids the zone is available. Most zones
|
||||
// will be in the NSONE Global Network(which is id 0).
|
||||
NetworkIDs []int `json:"networks,omitempty"`
|
||||
Records []*ZoneRecord `json:"records,omitempty"`
|
||||
|
||||
// Primary contains info to enable slaving of the zone by third party dns servers.
|
||||
Primary *ZonePrimary `json:"primary,omitempty"`
|
||||
// Secondary contains info for slaving the zone to a primary dns server.
|
||||
Secondary *ZoneSecondary `json:"secondary,omitempty"`
|
||||
}
|
||||
|
||||
func (z Zone) String() string {
|
||||
return z.Zone
|
||||
}
|
||||
|
||||
// ZoneRecord wraps Zone's "records" attribute
|
||||
type ZoneRecord struct {
|
||||
Domain string `json:"Domain,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
ShortAns []string `json:"short_answers,omitempty"`
|
||||
Tier int `json:"tier,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// ZonePrimary wraps a Zone's "primary" attribute
|
||||
type ZonePrimary struct {
|
||||
// Enabled determines whether AXFR queries (and optionally NOTIFY messages)
|
||||
// will be enabled for the zone.
|
||||
Enabled bool `json:"enabled"`
|
||||
Secondaries []ZoneSecondaryServer `json:"secondaries"`
|
||||
}
|
||||
|
||||
// ZoneSecondaryServer wraps elements of a Zone's "primary.secondary" attribute
|
||||
type ZoneSecondaryServer struct {
|
||||
// Read-Only
|
||||
NetworkIDs []int `json:"networks,omitempty"`
|
||||
|
||||
IP string `json:"ip"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Notify bool `json:"notify"`
|
||||
}
|
||||
|
||||
// ZoneSecondary wraps a Zone's "secondary" attribute
|
||||
type ZoneSecondary struct {
|
||||
// Read-Only fields
|
||||
Expired bool `json:"expired,omitempty"`
|
||||
LastXfr int `json:"last_xfr,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Error *string `json:"error"`
|
||||
|
||||
PrimaryIP string `json:"primary_ip,omitempty"`
|
||||
PrimaryPort int `json:"primary_port,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
TSIG *TSIG `json:"tsig"`
|
||||
}
|
||||
|
||||
// TSIG is a zones transaction signature.
|
||||
type TSIG struct {
|
||||
// Key is the encrypted TSIG key(read-only)
|
||||
Key string `json:"key,omitempty"`
|
||||
|
||||
// Whether TSIG is enabled for a secondary zone.
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
// Which hashing algorithm
|
||||
Hash string `json:"hash,omitempty"`
|
||||
// Name of the TSIG key
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// NewZone takes a zone domain name and creates a new zone.
|
||||
func NewZone(zone string) *Zone {
|
||||
z := Zone{
|
||||
Zone: zone,
|
||||
}
|
||||
return &z
|
||||
}
|
||||
|
||||
// MakePrimary enables Primary, disables Secondary, and sets primary's
|
||||
// Secondaries to all provided ZoneSecondaryServers
|
||||
func (z *Zone) MakePrimary(secondaries ...ZoneSecondaryServer) {
|
||||
z.Secondary = nil
|
||||
z.Primary = &ZonePrimary{
|
||||
Enabled: true,
|
||||
Secondaries: secondaries,
|
||||
}
|
||||
if z.Primary.Secondaries == nil {
|
||||
z.Primary.Secondaries = make([]ZoneSecondaryServer, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// MakeSecondary enables Secondary, disables Primary, and sets secondary's
|
||||
// Primary_ip to provided ip.
|
||||
func (z *Zone) MakeSecondary(ip string) {
|
||||
z.Secondary = &ZoneSecondary{
|
||||
Enabled: true,
|
||||
PrimaryIP: ip,
|
||||
PrimaryPort: 53,
|
||||
}
|
||||
z.Primary = &ZonePrimary{
|
||||
Enabled: false,
|
||||
Secondaries: make([]ZoneSecondaryServer, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// LinkTo sets Link to a target zone domain name and unsets all other configuration properties.
|
||||
// No other zone configuration properties (such as refresh, retry, etc) may be specified,
|
||||
// since they are all pulled from the target zone. Linked zones, once created, cannot be
|
||||
// configured at all and cannot have records added to them. They may only be deleted, which
|
||||
// does not affect the target zone at all.
|
||||
func (z *Zone) LinkTo(to string) {
|
||||
z.Meta = nil
|
||||
z.TTL = 0
|
||||
z.NxTTL = 0
|
||||
z.Retry = 0
|
||||
z.Refresh = 0
|
||||
z.Expiry = 0
|
||||
z.Primary = nil
|
||||
z.DNSServers = nil
|
||||
z.NetworkIDs = nil
|
||||
z.NetworkPools = nil
|
||||
z.Hostmaster = ""
|
||||
z.Pool = ""
|
||||
z.Secondary = nil
|
||||
z.Link = &to
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package model defines structures for interacting with the NS1 API.
|
||||
package model
|
|
@ -0,0 +1,2 @@
|
|||
// Package filter contains definitions for NS1 filter chains.
|
||||
package filter
|
|
@ -0,0 +1,182 @@
|
|||
package filter
|
||||
|
||||
// Filter wraps the values of a Record's "filters" attribute
|
||||
type Filter struct {
|
||||
Type string `json:"filter"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Config Config `json:"config"`
|
||||
}
|
||||
|
||||
// Enable a filter.
|
||||
func (f *Filter) Enable() {
|
||||
f.Disabled = false
|
||||
}
|
||||
|
||||
// Disable a filter.
|
||||
func (f *Filter) Disable() {
|
||||
f.Disabled = true
|
||||
}
|
||||
|
||||
// Config is a flat mapping where values are simple (no slices/maps).
|
||||
type Config map[string]interface{}
|
||||
|
||||
// NewSelFirstN returns a filter that eliminates all but the
|
||||
// first N answers from the list.
|
||||
func NewSelFirstN(n int) *Filter {
|
||||
return &Filter{
|
||||
Type: "select_first_n",
|
||||
Config: Config{"N": n},
|
||||
}
|
||||
}
|
||||
|
||||
// NewShuffle returns a filter that randomly sorts the answers.
|
||||
func NewShuffle() *Filter {
|
||||
return &Filter{Type: "shuffle", Config: Config{}}
|
||||
}
|
||||
|
||||
// GEOGRAPHICAL FILTERS
|
||||
|
||||
// NewSelFirstRegion returns a filter that keeps only the answers
|
||||
// that are in the same region as the first answer.
|
||||
func NewSelFirstRegion() *Filter {
|
||||
return &Filter{Type: "select_first_n", Config: Config{}}
|
||||
}
|
||||
|
||||
// NewStickyRegion first sorts regions uniquely depending on the IP
|
||||
// address of the requester, and then groups all answers together by
|
||||
// region. The same requester always gets the same ordering of regions,
|
||||
// but answers within each region may be in any order. byNetwork indicates
|
||||
// whether to apply the 'stickyness' by subnet(not individual IP).
|
||||
func NewStickyRegion(byNetwork bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "sticky_region",
|
||||
Config: Config{"sticky_by_network": byNetwork},
|
||||
}
|
||||
}
|
||||
|
||||
// NewGeofenceCountry returns a filter that fences using "country",
|
||||
// "us_state", and "ca_province" metadata fields in answers. Only
|
||||
// answers in the same country/state/province as the user (or
|
||||
// answers with no specified location) are returned. rmNoLoc determines
|
||||
// whether to remove answers without location on any match.
|
||||
func NewGeofenceCountry(rmNoLoc bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "geofence_country",
|
||||
Config: Config{"remove_no_location": rmNoLoc},
|
||||
}
|
||||
}
|
||||
|
||||
// NewGeofenceRegional returns a filter that restricts to answers in
|
||||
// same geographical region as requester. rmNoGeo determines whether
|
||||
// to remove answers without georegion on any match.
|
||||
func NewGeofenceRegional(rmNoGeo bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "geofence_regional",
|
||||
Config: Config{"remove_no_georegion": rmNoGeo},
|
||||
}
|
||||
}
|
||||
|
||||
// NewGeotargetCountry returns a filter that sorts answers by distance
|
||||
// to requester by country, US state, and/or Canadian province.
|
||||
func NewGeotargetCountry() *Filter {
|
||||
return &Filter{Type: "geofence_country", Config: Config{}}
|
||||
}
|
||||
|
||||
// NewGeotargetLatLong returns a filter that sorts answers by distance
|
||||
// to user using lat/long.
|
||||
func NewGeotargetLatLong() *Filter {
|
||||
return &Filter{Type: "geotarget_latlong", Config: Config{}}
|
||||
}
|
||||
|
||||
// NewGeotargetRegional returns a filter that sorts answers by distance
|
||||
// to user by geographical region.
|
||||
func NewGeotargetRegional() *Filter {
|
||||
return &Filter{Type: "geotarget_regional", Config: Config{}}
|
||||
}
|
||||
|
||||
// NETWORK FILTERS
|
||||
|
||||
// NewSticky returns a filter that sorts answers uniquely depending
|
||||
// on the IP address of the requester. The same requester always
|
||||
// gets the same ordering of answers. byNetwork indicates whether
|
||||
// to apply the 'stickyness' by subnet(not individual IP).
|
||||
func NewSticky(byNetwork bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "sticky",
|
||||
Config: Config{"sticky_by_network": byNetwork},
|
||||
}
|
||||
}
|
||||
|
||||
// NewWeightedSticky returns a filter that shuffles answers randomly
|
||||
// per-requester based on weight. byNetwork indicates whether to
|
||||
// apply the 'stickyness' by subnet(not individual IP).
|
||||
func NewWeightedSticky(byNetwork bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "weighted_sticky",
|
||||
Config: Config{"sticky_by_network": byNetwork},
|
||||
}
|
||||
}
|
||||
|
||||
// NewIPv4PrefixShuffle returns a filter that randomly selects
|
||||
// IPv4 addresses from prefix list. This filter can only be used
|
||||
// A records. n is the number of IPs to randomly select per answer.
|
||||
func NewIPv4PrefixShuffle(n int) *Filter {
|
||||
return &Filter{
|
||||
Type: "ipv4_prefix_shuffle",
|
||||
Config: Config{"N": n},
|
||||
}
|
||||
}
|
||||
|
||||
// NewNetfenceASN returns a filter that restricts to answers where
|
||||
// the ASN of requester IP matches ASN list. rmNoASN determines
|
||||
// whether to remove answers without asn list on any match.
|
||||
func NewNetfenceASN(rmNoASN bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "netfence_asn",
|
||||
Config: Config{"remove_no_asn": rmNoASN},
|
||||
}
|
||||
}
|
||||
|
||||
// NewNetfencePrefix returns a filter that restricts to answers where
|
||||
// requester IP matches prefix list. rmNoIPPrefix determines
|
||||
// whether to remove answers without ip prefixes on any match.
|
||||
func NewNetfencePrefix(rmNoIPPrefix bool) *Filter {
|
||||
return &Filter{
|
||||
Type: "netfence_prefix",
|
||||
Config: Config{"remove_no_ip_prefixes": rmNoIPPrefix},
|
||||
}
|
||||
}
|
||||
|
||||
// STATUS FILTERS
|
||||
|
||||
// NewUp returns a filter that eliminates all answers where
|
||||
// the 'up' metadata field is not true.
|
||||
func NewUp() *Filter {
|
||||
return &Filter{Type: "up", Config: Config{}}
|
||||
}
|
||||
|
||||
// NewPriority returns a filter that fails over according to
|
||||
// prioritized answer tiers.
|
||||
func NewPriority() *Filter {
|
||||
return &Filter{Type: "priority", Config: Config{}}
|
||||
}
|
||||
|
||||
// NewShedLoad returns a filter that "sheds" traffic to answers
|
||||
// based on load, using one of several load metrics. You must set
|
||||
// values for low_watermark, high_watermark, and the configured
|
||||
// load metric, for each answer you intend to subject to load
|
||||
// shedding.
|
||||
func NewShedLoad(metric string) *Filter {
|
||||
return &Filter{
|
||||
Type: "shed_load",
|
||||
Config: Config{"metric": metric},
|
||||
}
|
||||
}
|
||||
|
||||
// TRAFFIC FILTERS
|
||||
|
||||
// NewWeightedShuffle returns a filter that shuffles answers
|
||||
// randomly based on their weight.
|
||||
func NewWeightedShuffle() *Filter {
|
||||
return &Filter{Type: "weighted_shuffle", Config: Config{}}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package monitor
|
||||
|
||||
// Config is a flat mapping where values are simple (no slices/maps).
|
||||
type Config map[string]interface{}
|
|
@ -0,0 +1,2 @@
|
|||
// Package monitor contains definitions for NS1 monitoring jobs.
|
||||
package monitor
|
|
@ -0,0 +1,172 @@
|
|||
package monitor
|
||||
|
||||
// Job wraps an NS1 /monitoring/jobs resource
|
||||
type Job struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// The id of the notification list to send notifications to.
|
||||
NotifyListID string `json:"notify_list"`
|
||||
|
||||
// Type of monitor to be run.
|
||||
// Available job types:
|
||||
// - http: Do an HTTP request against a webserver
|
||||
// - dns: Do a DNS lookup against a nameserver
|
||||
// - tcp: Connect to a TCP port on a host
|
||||
// - ping: Ping a host using ICMP packets
|
||||
Type string `json:"job_type"`
|
||||
|
||||
// Configuration dictionary(key/vals depend on the jobs' type).
|
||||
Config Config `json:"config"`
|
||||
|
||||
// The current status of the monitor.
|
||||
Status map[string]Status `json:"status,omitempty"`
|
||||
|
||||
// Rules for determining failure conditions.
|
||||
Rules []*Rule `json:"rules"`
|
||||
|
||||
// List of regions in which to run the monitor.
|
||||
// eg, ["dal", "sin", "sjc", "lga", "ams"]
|
||||
Regions []string `json:"regions"`
|
||||
|
||||
// Indicates if the job is active or temporarily disabled.
|
||||
Active bool `json:"active"`
|
||||
|
||||
// Frequency(in seconds), at which to run the monitor.
|
||||
Frequency int `json:"frequency"`
|
||||
|
||||
// The policy for determining the monitor's global status based
|
||||
// on the status of the job in all regions.
|
||||
// Available policies:
|
||||
// - quorum: Status change when majority status
|
||||
// - all: Status change only when all regions are in agreement
|
||||
// - one: Status change if any region changes
|
||||
Policy string `json:"policy"`
|
||||
|
||||
// Controls behavior of how the job is assigned to monitoring regions.
|
||||
// Currently this must be fixed — indicating monitoring regions are explicitly chosen.
|
||||
RegionScope string `json:"region_scope"`
|
||||
|
||||
// Freeform notes to be included in any notifications about this job,
|
||||
// e.g., instructions for operators who will receive the notifications.
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
||||
// A free-form display name for the monitoring job.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Time(in seconds) between repeat notifications of a failed job.
|
||||
// Set to 0 to disable repeating notifications.
|
||||
NotifyRepeat int `json:"notify_repeat"`
|
||||
|
||||
// If true, on any apparent state change, the job is quickly re-run after
|
||||
// one second to confirm the state change before notification.
|
||||
RapidRecheck bool `json:"rapid_recheck"`
|
||||
|
||||
// Time(in seconds) after a failure to wait before sending a notification.
|
||||
NotifyDelay int `json:"notify_delay"`
|
||||
|
||||
// If true, notifications are sent for any regional failure (and failback if desired),
|
||||
// in addition to global state notifications.
|
||||
NotifyRegional bool `json:"notidy_regional"`
|
||||
|
||||
// If true, a notification is sent when a job returns to an "up" state.
|
||||
NotifyFailback bool `json:"notify_failback"`
|
||||
}
|
||||
|
||||
// Activate a monitoring job.
|
||||
func (j *Job) Activate() {
|
||||
j.Active = true
|
||||
|
||||
}
|
||||
|
||||
// Deactivate a monitoring job.
|
||||
func (j *Job) Deactivate() {
|
||||
j.Active = false
|
||||
}
|
||||
|
||||
// Result wraps an element of a JobType's "results" attribute
|
||||
type Result struct {
|
||||
Comparators []string `json:"comparators"`
|
||||
Metric bool `json:"metric"`
|
||||
Validator string `json:"validator"`
|
||||
ShortDesc string `json:"shortdesc"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// Status wraps an value of a Job's "status" attribute
|
||||
type Status struct {
|
||||
Since int `json:"since"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Rule wraps an element of a Job's "rules" attribute
|
||||
type Rule struct {
|
||||
Key string `json:"key"`
|
||||
Value interface{} `json:"value"`
|
||||
Comparison string `json:"comparison"`
|
||||
}
|
||||
|
||||
// NewHTTPConfig constructs/returns a job configuration for HTTP type jobs.
|
||||
// url is the URL to query. (Required)
|
||||
// method is the HTTP method(valid methods are HEAD, GET, and POST).
|
||||
// ua is the user agent text in the request header.
|
||||
// auth is the authorization header to use in request.
|
||||
// connTimeout is the timeout(in sec) to wait for query output.
|
||||
func NewHTTPConfig(url, method, ua, auth string, connTimeout int) *Config {
|
||||
return &Config{
|
||||
"url": url, // Required
|
||||
"method": method,
|
||||
"user_agent": ua,
|
||||
"auth": auth,
|
||||
"connection_timeout": connTimeout,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewDNSConfig constructs/returns a job configuration for DNS type jobs.
|
||||
// host is the IP address or hostname of the nameserver to query. (Required)
|
||||
// domain name to query. (Required)
|
||||
// port is the dns port to query on host.
|
||||
// t is the type of the DNS record type to query.
|
||||
// respTimeout is the timeout(in ms) after sending query to wait for the output.
|
||||
func NewDNSConfig(host, domain string, port int, t string, respTimeout int) *Config {
|
||||
return &Config{
|
||||
"host": host, // Required
|
||||
"domain": domain, // Required
|
||||
"port": port,
|
||||
"type": t,
|
||||
"response_timeout": respTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTCPConfig constructs/returns a job configuration for TCP type jobs.
|
||||
// host is the IP address or hostname to connect to. (Required)
|
||||
// port is the tcp port to connect to on host. (Required)
|
||||
// connTimeout is the timeout(in ms) before giving up on trying to connect.
|
||||
// respTimeout is the timeout(in sec) after connecting to wait for output.
|
||||
// send is the string to send to the host upon connecting.
|
||||
// ssl determines whether to attempt negotiating an SSL connection.
|
||||
func NewTCPConfig(host string, port, connTimeout, respTimeout int, send string, ssl bool) *Config {
|
||||
return &Config{
|
||||
"host": host, // Required
|
||||
"port": port, // Required
|
||||
"connection_timeout": connTimeout,
|
||||
"response_timeout": respTimeout,
|
||||
"send": send,
|
||||
"ssl": ssl,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPINGConfig constructs/returns a job configuration for PING type jobs.
|
||||
// host is the IP address or hostname to ping. (Required)
|
||||
// timeout is the timeout(in ms) before marking the host as failed.
|
||||
// count is the number of packets to send.
|
||||
// interval is the minimum time(in ms) to wait between sending each packet.
|
||||
func NewPINGConfig(host string, timeout, count, interval int) *Config {
|
||||
return &Config{
|
||||
"host": host, // Required
|
||||
"timeout": timeout,
|
||||
"count": count,
|
||||
"interval": interval,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnmarshalJobs(t *testing.T) {
|
||||
data := []byte(`[
|
||||
{
|
||||
"id": "52a27d4397d5f07003fdbe7b",
|
||||
"config": {
|
||||
"host": "1.2.3.4"
|
||||
},
|
||||
"status": {
|
||||
"lga": {
|
||||
"since": 1389407609,
|
||||
"status": "up"
|
||||
},
|
||||
"global": {
|
||||
"since": 1389407609,
|
||||
"status": "up"
|
||||
},
|
||||
"sjc": {
|
||||
"since": 1389404014,
|
||||
"status": "up"
|
||||
}
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"key": "rtt",
|
||||
"value": 100,
|
||||
"comparison": "<"
|
||||
}
|
||||
],
|
||||
"job_type": "ping",
|
||||
"regions": [
|
||||
"lga",
|
||||
"sjc"
|
||||
],
|
||||
"active": true,
|
||||
"frequency": 60,
|
||||
"policy": "quorum",
|
||||
"region_scope": "fixed"
|
||||
}
|
||||
]`)
|
||||
mjl := []*Job{}
|
||||
if err := json.Unmarshal(data, &mjl); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(mjl) != 1 {
|
||||
fmt.Println(mjl)
|
||||
t.Error("Do not have any jobs")
|
||||
}
|
||||
j := mjl[0]
|
||||
if j.ID != "52a27d4397d5f07003fdbe7b" {
|
||||
t.Error("Wrong ID")
|
||||
}
|
||||
conf := j.Config
|
||||
if conf["host"] != "1.2.3.4" {
|
||||
t.Error("Wrong host")
|
||||
}
|
||||
status := j.Status["global"]
|
||||
if status.Since != 1389407609 {
|
||||
t.Error("since has unexpected value")
|
||||
}
|
||||
if status.Status != "up" {
|
||||
t.Error("Status is not up")
|
||||
}
|
||||
r := j.Rules[0]
|
||||
assert.Equal(t, r.Key, "rtt", "RTT rule key is wrong")
|
||||
assert.Equal(t, r.Value.(float64), float64(100), "RTT rule value is wrong")
|
||||
if r.Comparison != "<" {
|
||||
t.Error("RTT rule comparison is wrong")
|
||||
}
|
||||
if j.Type != "ping" {
|
||||
t.Error("Jobtype is wrong")
|
||||
}
|
||||
if j.Regions[0] != "lga" {
|
||||
t.Error("First region is not lga")
|
||||
}
|
||||
if !j.Active {
|
||||
t.Error("Job is not active")
|
||||
}
|
||||
if j.Frequency != 60 {
|
||||
t.Error("Job frequency != 60")
|
||||
}
|
||||
if j.Policy != "quorum" {
|
||||
t.Error("Job policy is not quorum")
|
||||
}
|
||||
if j.RegionScope != "fixed" {
|
||||
t.Error("Job region scope is not fixed")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package monitor
|
||||
|
||||
// NotifyList wraps notifications.
|
||||
type NotifyList struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Notifications []*Notification `json:"notify_list,omitempty"`
|
||||
}
|
||||
|
||||
// Notification represents endpoint to alert to.
|
||||
type Notification struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Config Config `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
// NewNotifyList returns a notify list that alerts via the given notifications.
|
||||
func NewNotifyList(name string, nl ...*Notification) *NotifyList {
|
||||
if nl == nil {
|
||||
nl = []*Notification{}
|
||||
}
|
||||
|
||||
return &NotifyList{Name: name, Notifications: nl}
|
||||
}
|
||||
|
||||
// NewUserNotification returns a notification that alerts via user.
|
||||
func NewUserNotification(username string) *Notification {
|
||||
return &Notification{
|
||||
Type: "user",
|
||||
Config: Config{"user": username}}
|
||||
}
|
||||
|
||||
// NewEmailNotification returns a notification that alerts via email.
|
||||
func NewEmailNotification(email string) *Notification {
|
||||
return &Notification{
|
||||
Type: "email",
|
||||
Config: Config{"email": email}}
|
||||
}
|
||||
|
||||
// NewFeedNotification returns a notification that alerts via datafeed.
|
||||
func NewFeedNotification(sourceID string) *Notification {
|
||||
return &Notification{
|
||||
Type: "datafeed",
|
||||
Config: Config{"sourceid": sourceID}}
|
||||
}
|
||||
|
||||
// NewWebNotification returns a notification that alerts via webhook.
|
||||
func NewWebNotification(url string) *Notification {
|
||||
return &Notification{
|
||||
Type: "webhook",
|
||||
Config: Config{"url": url}}
|
||||
}
|
||||
|
||||
// NewPagerDutyNotification returns a notification that alerts via pagerduty.
|
||||
func NewPagerDutyNotification(key string) *Notification {
|
||||
return &Notification{
|
||||
Type: "pagerduty",
|
||||
Config: Config{"service_key": key}}
|
||||
}
|
||||
|
||||
// NewHipChatNotification returns a notification that alerts via hipchat.
|
||||
func NewHipChatNotification(token, room string) *Notification {
|
||||
return &Notification{
|
||||
Type: "hipchat",
|
||||
Config: Config{"token": token, "room": room}}
|
||||
}
|
||||
|
||||
// NewSlackNotification returns a notification that alerts via slack.
|
||||
func NewSlackNotification(url, username, channel string) *Notification {
|
||||
return &Notification{
|
||||
Type: "slack",
|
||||
Config: Config{"url": url, "username": username, "channel": channel}}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package model
|
||||
|
||||
// // GetQPSStats returns current queries per second (QPS) for the account
|
||||
// func (c APIClient) GetQPSStats() (v float64, err error) {
|
||||
// var s map[string]float64
|
||||
// _, err = c.doHTTPUnmarshal("GET", "https://api.nsone.net/v1/stats/qps", nil, &s)
|
||||
// if err != nil {
|
||||
// return v, err
|
||||
// }
|
||||
// v, found := s["qps"]
|
||||
// if !found {
|
||||
// return v, errors.New("Could not find 'qps' key in returned data")
|
||||
// }
|
||||
// return v, nil
|
||||
// }
|
|
@ -0,0 +1,108 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||
)
|
||||
|
||||
// JobsService handles 'monitoring/jobs' endpoint.
|
||||
type JobsService service
|
||||
|
||||
// List returns all monitoring jobs for the account.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#jobs-get
|
||||
func (s *JobsService) List() ([]*monitor.Job, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "monitoring/jobs", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
mjl := []*monitor.Job{}
|
||||
resp, err := s.client.Do(req, &mjl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return mjl, resp, nil
|
||||
}
|
||||
|
||||
// Get takes an ID and returns details for a specific monitoring job.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#jobs-jobid-get
|
||||
func (s *JobsService) Get(id string) (*monitor.Job, *http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "monitoring/jobs", id)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var mj monitor.Job
|
||||
resp, err := s.client.Do(req, &mj)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &mj, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *MonitoringJob and creates a new monitoring job.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#jobs-put
|
||||
func (s *JobsService) Create(mj *monitor.Job) (*http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "monitoring/jobs", mj.ID)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", path, &mj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update mon jobs' fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &mj)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update takes a *MonitoringJob and change the configuration details of an existing monitoring job.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#jobs-jobid-post
|
||||
func (s *JobsService) Update(mj *monitor.Job) (*http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "monitoring/jobs", mj.ID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &mj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update mon jobs' fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &mj)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete takes an ID and immediately terminates and deletes and existing monitoring job.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#jobs-jobid-delete
|
||||
func (s *JobsService) Delete(id string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "monitoring/jobs", id)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||
)
|
||||
|
||||
// NotificationsService handles 'monitoring/lists' endpoint.
|
||||
type NotificationsService service
|
||||
|
||||
// List returns all configured notification lists.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#lists-get
|
||||
func (s *NotificationsService) List() ([]*monitor.NotifyList, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "lists", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
nl := []*monitor.NotifyList{}
|
||||
resp, err := s.client.Do(req, &nl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return nl, resp, nil
|
||||
}
|
||||
|
||||
// Get returns the details and notifiers associated with a specific notification list.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#lists-listid-get
|
||||
func (s *NotificationsService) Get(listID string) (*monitor.NotifyList, *http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "lists", listID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var nl monitor.NotifyList
|
||||
resp, err := s.client.Do(req, &nl)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "unknown notification list" {
|
||||
return nil, resp, ErrListMissing
|
||||
}
|
||||
default:
|
||||
return nil, resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return &nl, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *NotifyList and creates a new notify list.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#lists-put
|
||||
func (s *NotificationsService) Create(nl *monitor.NotifyList) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("PUT", "lists", &nl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update notify list fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &nl)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == fmt.Sprintf("notification list with name \"%s\" exists", nl.Name) {
|
||||
return resp, ErrListExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update adds or removes entries or otherwise update a notification list.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#list-listid-post
|
||||
func (s *NotificationsService) Update(nl *monitor.NotifyList) (*http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "lists", nl.ID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &nl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update mon lists' fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &nl)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete immediately deletes an existing notification list.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#lists-listid-delete
|
||||
func (s *NotificationsService) Delete(listID string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", "lists", listID)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrListExists bundles PUT create error.
|
||||
ErrListExists = errors.New("Notify List already exists.")
|
||||
// ErrListMissing bundles GET/POST/DELETE error.
|
||||
ErrListMissing = errors.New("Notify List does not exist.")
|
||||
)
|
|
@ -0,0 +1,134 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
// RecordsService handles 'zones/ZONE/DOMAIN/TYPE' endpoint.
|
||||
type RecordsService service
|
||||
|
||||
// Get takes a zone, domain and record type t and returns full configuration for a DNS record.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#record-get
|
||||
func (s *RecordsService) Get(zone, domain, t string) (*dns.Record, *http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s/%s/%s", zone, domain, t)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var r dns.Record
|
||||
resp, err := s.client.Do(req, &r)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "record not found" {
|
||||
return nil, resp, ErrRecordMissing
|
||||
}
|
||||
default:
|
||||
return nil, resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return &r, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *Record and creates a new DNS record in the specified zone, for the specified domain, of the given record type.
|
||||
//
|
||||
// The given record must have at least one answer.
|
||||
// NS1 API docs: https://ns1.com/api/#record-put
|
||||
func (s *RecordsService) Create(r *dns.Record) (*http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s/%s/%s", r.Zone, r.Domain, r.Type)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", path, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update record fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &r)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
switch err.(*Error).Message {
|
||||
case "zone not found":
|
||||
return resp, ErrZoneMissing
|
||||
case "record already exists":
|
||||
return resp, ErrRecordExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update takes a *Record and modifies configuration details for an existing DNS record.
|
||||
//
|
||||
// Only the fields to be updated are required in the given record.
|
||||
// NS1 API docs: https://ns1.com/api/#record-post
|
||||
func (s *RecordsService) Update(r *dns.Record) (*http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s/%s/%s", r.Zone, r.Domain, r.Type)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update records fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &r)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
switch err.(*Error).Message {
|
||||
case "zone not found":
|
||||
return resp, ErrZoneMissing
|
||||
case "record already exists":
|
||||
return resp, ErrRecordExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete takes a zone, domain and record type t and removes an existing record and all associated answers and configuration details.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#record-delete
|
||||
func (s *RecordsService) Delete(zone string, domain string, t string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s/%s/%s", zone, domain, t)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "record not found" {
|
||||
return resp, ErrRecordMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrRecordExists bundles PUT create error.
|
||||
ErrRecordExists = errors.New("Record already exists.")
|
||||
// ErrRecordMissing bundles GET/POST/DELETE error.
|
||||
ErrRecordMissing = errors.New("Record does not exist.")
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
package rest
|
||||
|
||||
// // GetQPSStats returns current queries per second (QPS) for the account
|
||||
// func (c APIClient) GetQPSStats() (v float64, err error) {
|
||||
// var s map[string]float64
|
||||
// _, err = c.doHTTPUnmarshal("GET", "https://api.nsone.net/v1/stats/qps", nil, &s)
|
||||
// if err != nil {
|
||||
// return v, err
|
||||
// }
|
||||
// v, found := s["qps"]
|
||||
// if !found {
|
||||
// return v, errors.New("Could not find 'qps' key in returned data")
|
||||
// }
|
||||
// return v, nil
|
||||
// }
|
|
@ -0,0 +1,42 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// DoerFunc satisfies Interface. DoerFuncs are useful for adding
|
||||
// logging/instrumentation to the http.Client that is used
|
||||
// within the rest.APIClient.
|
||||
type DoerFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
// Do is implementation of rest.Doer interface. Calls itself on the
|
||||
// given http.Request.
|
||||
func (f DoerFunc) Do(r *http.Request) (*http.Response, error) {
|
||||
return f(r)
|
||||
}
|
||||
|
||||
// A Decorator wraps a Doer with extra behavior, and doesnt
|
||||
// affect the behavior of other instances of the same type.
|
||||
type Decorator func(Doer) Doer
|
||||
|
||||
// Decorate decorates a Doer c with all the given Decorators, in order.
|
||||
// Core object(Doer instance) that we want to apply layers(Decorator slice) to.
|
||||
func Decorate(d Doer, ds ...Decorator) Doer {
|
||||
decorated := d
|
||||
for _, decorate := range ds {
|
||||
decorated = decorate(decorated)
|
||||
}
|
||||
return decorated
|
||||
}
|
||||
|
||||
// Logging returns a Decorator that logs a Doer's requests.
|
||||
// Dependency injection for the logger instance(inside the closures environment).
|
||||
func Logging(l *log.Logger) Decorator {
|
||||
return func(d Doer) Doer {
|
||||
return DoerFunc(func(r *http.Request) (*http.Response, error) {
|
||||
l.Printf("%s: %s %s", r.UserAgent(), r.Method, r.URL)
|
||||
return d.Do(r)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
// ZonesService handles 'zones' endpoint.
|
||||
type ZonesService service
|
||||
|
||||
// List returns all active zones and basic zone configuration details for each.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#zones-get
|
||||
func (s *ZonesService) List() ([]*dns.Zone, *http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", "zones", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
zl := []*dns.Zone{}
|
||||
resp, err := s.client.Do(req, &zl)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return zl, resp, nil
|
||||
}
|
||||
|
||||
// Get takes a zone name and returns a single active zone and its basic configuration details.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#zones-zone-get
|
||||
func (s *ZonesService) Get(zone string) (*dns.Zone, *http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s", zone)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var z dns.Zone
|
||||
resp, err := s.client.Do(req, &z)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "zone not found" {
|
||||
return nil, resp, ErrZoneMissing
|
||||
}
|
||||
default:
|
||||
return nil, resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return &z, resp, nil
|
||||
}
|
||||
|
||||
// Create takes a *Zone and creates a new DNS zone.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#zones-put
|
||||
func (s *ZonesService) Create(z *dns.Zone) (*http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s", z.Zone)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", path, &z)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update zones fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &z)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "zone already exists" {
|
||||
return resp, ErrZoneExists
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update takes a *Zone and modifies basic details of a DNS zone.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#zones-post
|
||||
func (s *ZonesService) Update(z *dns.Zone) (*http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s", z.Zone)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, &z)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update zones fields with data from api(ensure consistent)
|
||||
resp, err := s.client.Do(req, &z)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "zone not found" {
|
||||
return resp, ErrZoneMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete takes a zone and destroys an existing DNS zone and all records in the zone.
|
||||
//
|
||||
// NS1 API docs: https://ns1.com/api/#zones-delete
|
||||
func (s *ZonesService) Delete(zone string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("zones/%s", zone)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
if err.(*Error).Message == "zone not found" {
|
||||
return resp, ErrZoneMissing
|
||||
}
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrZoneExists bundles PUT create error.
|
||||
ErrZoneExists = errors.New("Zone already exists.")
|
||||
// ErrZoneMissing bundles GET/POST/DELETE error.
|
||||
ErrZoneMissing = errors.New("Zone does not exist.")
|
||||
)
|
|
@ -2959,6 +2959,12 @@
|
|||
"revision": "766e555c68dc8bda90d197ee8946c37519c19409",
|
||||
"revisionTime": "2017-01-17T13:00:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "U0s6Vq/AxUhEEsnORw2DnZ4cw1c=",
|
||||
"path": "gopkg.in/ns1/ns1-go.v2",
|
||||
"revision": "d8d10b7f448291ddbdce48d4594fb1b667014c8b",
|
||||
"revisionTime": "2016-11-05T01:14:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "mkLQOQwQwoUc9Kr9+PaVGrKUzI4=",
|
||||
"path": "gopkg.in/resty.v0",
|
||||
|
|
|
@ -41,6 +41,7 @@ body.layout-mailgun,
|
|||
body.layout-mysql,
|
||||
body.layout-newrelic,
|
||||
body.layout-nomad,
|
||||
body.layout-ns1,
|
||||
body.layout-openstack,
|
||||
body.layout-opsgenie,
|
||||
body.layout-packet,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "Provider: NS1"
|
||||
sidebar_current: "docs-ns1-index"
|
||||
description: |-
|
||||
The [NS1](https://ns1.com/) provider is used to interact with the resources supported by NS1.
|
||||
---
|
||||
|
||||
# NS1 Provider
|
||||
|
||||
The NS1 provider exposes resources to interact with the NS1 REST API. The provider needs to be configured
|
||||
with the proper credentials before it can be used.
|
||||
|
||||
Use the navigation to the left to read about the available resources.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Configure the NS1 provider
|
||||
provider "ns1" {
|
||||
apikey = "${var.ns1_apikey}"
|
||||
}
|
||||
|
||||
# Create a new zone
|
||||
resource "ns1_zone" "foobar" {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `apikey` - (Required) NS1 API token. It must be provided, but it can also
|
||||
be sourced from the `NS1_API_KEY` environment variable.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_apikey"
|
||||
sidebar_current: "docs-ns1-resource-apikey"
|
||||
description: |-
|
||||
Provides a NS1 Api Key resource.
|
||||
---
|
||||
|
||||
# ns1\_apikey
|
||||
|
||||
Provides a NS1 Api Key resource. This can be used to create, modify, and delete api keys.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "ns1_team" "example" {
|
||||
name = "Example team"
|
||||
}
|
||||
|
||||
resource "ns1_apikey" "example" {
|
||||
name = "Example key"
|
||||
teams = ["${ns1_team.example.id}"]
|
||||
permissions = {
|
||||
dns_view_zones = false
|
||||
account_manage_users = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The free form name of the apikey.
|
||||
* `key` - (Required) The apikeys authentication token.
|
||||
* `teams` - (Required) The teams that the apikey belongs to.
|
||||
* `permissions` - (Optional) The allowed permissions of the apikey. Permissions documented below.
|
||||
|
||||
Permissions (`permissions`) support the following:
|
||||
|
||||
* `dns_view_zones` - (Optional) Whether the apikey can view the accounts zones.
|
||||
* `dns_manage_zones` - (Optional) Whether the apikey can modify the accounts zones.
|
||||
* `dns_zones_allow_by_default` - (Optional) If true, enable the `dns_zones_allow` list, otherwise enable the `dns_zones_deny` list.
|
||||
* `dns_zones_allow` - (Optional) List of zones that the apikey may access.
|
||||
* `dns_zones_deny` - (Optional) List of zones that the apikey may not access.
|
||||
* `data_push_to_datafeeds` - (Optional) Whether the apikey can publish to data feeds.
|
||||
* `data_manage_datasources` - (Optional) Whether the apikey can modify data sources.
|
||||
* `data_manage_datafeeds` - (Optional) Whether the apikey can modify data feeds.
|
||||
* `account_manage_users` - (Optional) Whether the apikey can modify account users.
|
||||
* `account_manage_payment_methods` - (Optional) Whether the apikey can modify account payment methods.
|
||||
* `account_manage_plan` - (Optional) Whether the apikey can modify the account plan.
|
||||
* `account_manage_teams` - (Optional) Whether the apikey can modify other teams in the account.
|
||||
* `account_manage_apikeys` - (Optional) Whether the apikey can modify account apikeys.
|
||||
* `account_manage_account_settings` - (Optional) Whether the apikey can modify account settings.
|
||||
* `account_view_activity_log` - (Optional) Whether the apikey can view activity logs.
|
||||
* `account_view_invoices` - (Optional) Whether the apikey can view invoices.
|
||||
* `monitoring_manage_lists` - (Optional) Whether the apikey can modify notification lists.
|
||||
* `monitoring_manage_jobs` - (Optional) Whether the apikey can modify monitoring jobs.
|
||||
* `monitoring_view_jobs` - (Optional) Whether the apikey can view monitoring jobs.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_datafeed"
|
||||
sidebar_current: "docs-ns1-resource-datafeed"
|
||||
description: |-
|
||||
Provides a NS1 Data Feed resource.
|
||||
---
|
||||
|
||||
# ns1\_datafeed
|
||||
|
||||
Provides a NS1 Data Feed resource. This can be used to create, modify, and delete data feeds.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "ns1_datasource" "example" {
|
||||
name = "example"
|
||||
sourcetype = "nsone_v1"
|
||||
}
|
||||
|
||||
resource "ns1_datafeed" "uswest_feed" {
|
||||
name = "uswest_feed"
|
||||
source_id = "${ns1_datasource.example.id}"
|
||||
config = {
|
||||
label = "uswest"
|
||||
}
|
||||
}
|
||||
|
||||
resource "ns1_datafeed" "useast_feed" {
|
||||
name = "useast_feed"
|
||||
source_id = "${ns1_datasource.example.id}"
|
||||
config = {
|
||||
label = "useast"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `source_id` - (Required) The data source id that this feed is connected to.
|
||||
* `name` - (Required) The free form name of the data feed.
|
||||
* `config` - (Optional) The feeds configuration matching the specification in 'feed\_config' from /data/sourcetypes.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_datasource"
|
||||
sidebar_current: "docs-ns1-resource-datasource"
|
||||
description: |-
|
||||
Provides a NS1 Data Source resource.
|
||||
---
|
||||
|
||||
# ns1\_datasource
|
||||
|
||||
Provides a NS1 Data Source resource. This can be used to create, modify, and delete data sources.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "ns1_datasource" "example" {
|
||||
name = "example"
|
||||
sourcetype = "nsone_v1"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The free form name of the data source.
|
||||
* `sourcetype` - (Required) The data sources type, listed in API endpoint https://api.nsone.net/v1/data/sourcetypes.
|
||||
* `config` - (Optional) The data source configuration, determined by its type.
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_monitoringjob"
|
||||
sidebar_current: "docs-ns1-resource-monitoringjob"
|
||||
description: |-
|
||||
Provides a NS1 Monitoring Job resource.
|
||||
---
|
||||
|
||||
# ns1\_monitoringjob
|
||||
|
||||
Provides a NS1 Monitoring Job resource. This can be used to create, modify, and delete monitoring jobs.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "ns1_monitoringjob" "uswest_monitor" {
|
||||
name = "uswest"
|
||||
active = true
|
||||
regions = ["sjc", "sin", "lga"]
|
||||
job_type = "tcp"
|
||||
frequency = 60
|
||||
rapid_recheck = true
|
||||
policy = "quorum"
|
||||
config = {
|
||||
send = "HEAD / HTTP/1.0\r\n\r\n"
|
||||
port = 80
|
||||
host = "example-elb-uswest.aws.amazon.com"
|
||||
}
|
||||
rules = {
|
||||
value = "200 OK"
|
||||
comparison = "contains"
|
||||
key = "output"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The free-form display name for the monitoring job.
|
||||
* `job_type` - (Required) The type of monitoring job to be run.
|
||||
* `active` - (Required) Indicates if the job is active or temporaril.y disabled.
|
||||
* `regions` - (Required) The list of region codes in which to run the monitoring job.
|
||||
* `frequency` - (Required) The frequency, in seconds, at which to run the monitoring job in each region.
|
||||
* `rapid_recheck` - (Required) If true, on any apparent state change, the job is quickly re-run after one second to confirm the state change before notification.
|
||||
* `policy` - (Required) The policy for determining the monitor's global status based on the status of the job in all regions.
|
||||
* `config` - (Required) A configuration dictionary with keys and values depending on the jobs' type.
|
||||
* `notify_delay` - (Optional) The time in seconds after a failure to wait before sending a notification.
|
||||
* `notify_repeat` - (Optional) The time in seconds between repeat notifications of a failed job.
|
||||
* `notify_failback` - (Optional) If true, a notification is sent when a job returns to an "up" state.
|
||||
* `notify_regional` - (Optional) If true, notifications are sent for any regional failure (and failback if desired), in addition to global state notifications.
|
||||
* `notify_list` - (Optional) The id of the notification list to send notifications to.
|
||||
* `notes` - (Optional) Freeform notes to be included in any notifications about this job.
|
||||
* `rules` - (Optional) A list of rules for determining failure conditions. Job Rules are documented below.
|
||||
|
||||
Monitoring Job Rules (`rules`) support the following:
|
||||
|
||||
* `key` - (Required) The output key.
|
||||
* `comparison` - (Required) The comparison to perform on the the output.
|
||||
* `value` - (Required) The value to compare to.
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_record"
|
||||
sidebar_current: "docs-ns1-resource-record"
|
||||
description: |-
|
||||
Provides a NS1 Record resource.
|
||||
---
|
||||
|
||||
# ns1\_record
|
||||
|
||||
Provides a NS1 Record resource. This can be used to create, modify, and delete records.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "ns1_zone" "tld" {
|
||||
zone = "terraform.example"
|
||||
}
|
||||
|
||||
resource "ns1_record" "www" {
|
||||
zone = "${ns1_zone.tld.id}"
|
||||
domain = "www.${ns1_zone.tld.zone}"
|
||||
type = "CNAME"
|
||||
ttl = 60
|
||||
|
||||
answers = {
|
||||
answer = ["sub1.${ns1_zone.tld.zone}"]
|
||||
}
|
||||
answer = {
|
||||
answer = ["sub2.${ns1_zone.tld.zone}"]
|
||||
}
|
||||
|
||||
filters = {
|
||||
filter = "select_first_n"
|
||||
config = {N=1}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `zone` - (Required) The zone the record belongs to.
|
||||
* `domain` - (Required) The records' domain.
|
||||
* `type` - (Required) The records' RR type.
|
||||
* `ttl` - (Optional) The records' time to live.
|
||||
* `link` - (Optional) The target record to link to. This means this record is a 'linked' record, and it inherits all properties from its target.
|
||||
* `use_client_subnet` - (Optional) Whether to use EDNS client subnet data when available(in filter chain).
|
||||
* `answers` - (Optional) The list of the RDATA fields for the records' specified type. Answers are documented below.
|
||||
* `filters` - (Optional) The list of NS1 filters for the record(order matters). Filters are documented below.
|
||||
|
||||
Answers (`answers`) support the following:
|
||||
|
||||
* `answer` - (Required) List of RDATA fields.
|
||||
* `region` - (Required) The region this answer belongs to.
|
||||
|
||||
Filters (`filters`) support the following:
|
||||
|
||||
* `filter` - (Required) The type of filter.
|
||||
* `disabled` - (Required) Determines whether the filter is applied in the filter chain.
|
||||
* `config` - (Required) The filters' configuration. Simple key/value pairs determined by the filter type.
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_team"
|
||||
sidebar_current: "docs-ns1-resource-team"
|
||||
description: |-
|
||||
Provides a NS1 Team resource.
|
||||
---
|
||||
|
||||
# ns1\_team
|
||||
|
||||
Provides a NS1 Team resource. This can be used to create, modify, and delete teams.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Create a new NS1 Team
|
||||
resource "ns1_team" "example" {
|
||||
name = "Example team"
|
||||
permissions = {
|
||||
dns_view_zones = false
|
||||
account_manage_users = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The free form name of the team.
|
||||
* `permissions` - (Optional) The allowed permissions of the team. Permissions documented below.
|
||||
|
||||
Permissions (`permissions`) support the following:
|
||||
|
||||
* `dns_view_zones` - (Optional) Whether the team can view the accounts zones.
|
||||
* `dns_manage_zones` - (Optional) Whether the team can modify the accounts zones.
|
||||
* `dns_zones_allow_by_default` - (Optional) If true, enable the `dns_zones_allow` list, otherwise enable the `dns_zones_deny` list.
|
||||
* `dns_zones_allow` - (Optional) List of zones that the team may access.
|
||||
* `dns_zones_deny` - (Optional) List of zones that the team may not access.
|
||||
* `data_push_to_datafeeds` - (Optional) Whether the team can publish to data feeds.
|
||||
* `data_manage_datasources` - (Optional) Whether the team can modify data sources.
|
||||
* `data_manage_datafeeds` - (Optional) Whether the team can modify data feeds.
|
||||
* `account_manage_users` - (Optional) Whether the team can modify account users.
|
||||
* `account_manage_payment_methods` - (Optional) Whether the team can modify account payment methods.
|
||||
* `account_manage_plan` - (Optional) Whether the team can modify the account plan.
|
||||
* `account_manage_teams` - (Optional) Whether the team can modify other teams in the account.
|
||||
* `account_manage_apikeys` - (Optional) Whether the team can modify account apikeys.
|
||||
* `account_manage_account_settings` - (Optional) Whether the team can modify account settings.
|
||||
* `account_view_activity_log` - (Optional) Whether the team can view activity logs.
|
||||
* `account_view_invoices` - (Optional) Whether the team can view invoices.
|
||||
* `monitoring_manage_lists` - (Optional) Whether the team can modify notification lists.
|
||||
* `monitoring_manage_jobs` - (Optional) Whether the team can modify monitoring jobs.
|
||||
* `monitoring_view_jobs` - (Optional) Whether the team can view monitoring jobs.
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_user"
|
||||
sidebar_current: "docs-ns1-resource-user"
|
||||
description: |-
|
||||
Provides a NS1 User resource.
|
||||
---
|
||||
|
||||
# ns1\_user
|
||||
|
||||
Provides a NS1 User resource. Creating a user sends an invitation email to the user's email address. This can be used to create, modify, and delete users.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "ns1_team" "example" {
|
||||
name = "Example team"
|
||||
permissions = {
|
||||
dns_view_zones = false
|
||||
account_manage_users = false
|
||||
}
|
||||
}
|
||||
|
||||
resource "ns1_user" "example" {
|
||||
name = "Example User"
|
||||
username = "example_user"
|
||||
email = "user@example.com"
|
||||
teams = ["${ns1_team.example.id}"]
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The free form name of the user.
|
||||
* `username` - (Required) The users login name.
|
||||
* `email` - (Required) The email address of the user.
|
||||
* `notify` - (Required) The Whether or not to notify the user of specified events. Only `billing` is available currently.
|
||||
* `teams` - (Required) The teams that the user belongs to.
|
||||
* `permissions` - (Optional) The allowed permissions of the user. Permissions documented below.
|
||||
|
||||
Permissions (`permissions`) support the following:
|
||||
|
||||
* `dns_view_zones` - (Optional) Whether the user can view the accounts zones.
|
||||
* `dns_manage_zones` - (Optional) Whether the user can modify the accounts zones.
|
||||
* `dns_zones_allow_by_default` - (Optional) If true, enable the `dns_zones_allow` list, otherwise enable the `dns_zones_deny` list.
|
||||
* `dns_zones_allow` - (Optional) List of zones that the user may access.
|
||||
* `dns_zones_deny` - (Optional) List of zones that the user may not access.
|
||||
* `data_push_to_datafeeds` - (Optional) Whether the user can publish to data feeds.
|
||||
* `data_manage_datasources` - (Optional) Whether the user can modify data sources.
|
||||
* `data_manage_datafeeds` - (Optional) Whether the user can modify data feeds.
|
||||
* `account_manage_users` - (Optional) Whether the user can modify account users.
|
||||
* `account_manage_payment_methods` - (Optional) Whether the user can modify account payment methods.
|
||||
* `account_manage_plan` - (Optional) Whether the user can modify the account plan.
|
||||
* `account_manage_teams` - (Optional) Whether the user can modify other teams in the account.
|
||||
* `account_manage_apikeys` - (Optional) Whether the user can modify account apikeys.
|
||||
* `account_manage_account_settings` - (Optional) Whether the user can modify account settings.
|
||||
* `account_view_activity_log` - (Optional) Whether the user can view activity logs.
|
||||
* `account_view_invoices` - (Optional) Whether the user can view invoices.
|
||||
* `monitoring_manage_lists` - (Optional) Whether the user can modify notification lists.
|
||||
* `monitoring_manage_jobs` - (Optional) Whether the user can modify monitoring jobs.
|
||||
* `monitoring_view_jobs` - (Optional) Whether the user can view monitoring jobs.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
layout: "ns1"
|
||||
page_title: "NS1: ns1_zone"
|
||||
sidebar_current: "docs-ns1-resource-zone"
|
||||
description: |-
|
||||
Provides a NS1 Zone resource.
|
||||
---
|
||||
|
||||
# ns1\_zone
|
||||
|
||||
Provides a NS1 DNS Zone resource. This can be used to create, modify, and delete zones.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Create a new DNS zone
|
||||
resource "ns1_zone" "example" {
|
||||
zone = "terraform.example.io"
|
||||
ttl = 600
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `zone` - (Required) The domain name of the zone.
|
||||
* `link` - (Optional) The target zone(domain name) to link to.
|
||||
* `ttl` - (Optional) The SOA TTL.
|
||||
* `refresh` - (Optional) The SOA Refresh.
|
||||
* `retry` - (Optional) The SOA Retry.
|
||||
* `expiry` - (Optional) The SOA Expiry.
|
||||
* `nx_ttl` - (Optional) The SOA NX TTL.
|
||||
* `primary` - (Optional) The primary zones' ip. This makes the zone a secondary.
|
|
@ -295,6 +295,14 @@
|
|||
<a href="/docs/providers/newrelic/index.html">New Relic</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-nomad") %>>
|
||||
<a href="/docs/providers/nomad/index.html">Nomad</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-ns1") %>>
|
||||
<a href="/docs/providers/ns1/index.html">NS1</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-azurerm") %>>
|
||||
<a href="/docs/providers/azurerm/index.html">Microsoft Azure</a>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<% wrap_layout :inner do %>
|
||||
<% content_for :sidebar do %>
|
||||
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/providers/index.html">« Documentation Home</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-ns1-index") %>>
|
||||
<a href="/docs/providers/ns1/index.html">NS1 Provider</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current(/^docs-ns1-resource/) %>>
|
||||
<a href="#">Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-ns1-resource-zone") %>>
|
||||
<a href="/docs/providers/ns1/r/zone.html">ns1_zone</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-record") %>>
|
||||
<a href="/docs/providers/ns1/r/record.html">ns1_record</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-monitoringjob") %>>
|
||||
<a href="/docs/providers/ns1/r/monitoringjob.html">ns1_monitoringjob</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-datasource") %>>
|
||||
<a href="/docs/providers/ns1/r/datasource.html">ns1_datasource</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-datafeed") %>>
|
||||
<a href="/docs/providers/ns1/r/datafeed.html">ns1_datafeed</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-apikey") %>>
|
||||
<a href="/docs/providers/ns1/r/apikey.html">ns1_apikey</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-team") %>>
|
||||
<a href="/docs/providers/ns1/r/team.html">ns1_team</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-ns1-resource-user") %>>
|
||||
<a href="/docs/providers/ns1/r/user.html">ns1_user</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
Loading…
Reference in New Issue