diff --git a/OWNERS b/OWNERS
index 71cb6a4be..a56030929 100644
--- a/OWNERS
+++ b/OWNERS
@@ -5,6 +5,7 @@ providers/digitalocean @Deraen
providers/dnsimple @aeden
providers/gandi @TomOnTime
# providers/gcloud
+providers/hexonet @papakai
providers/linode @koesie10
providers/namecheap @captncraig
# providers/namedotcom
diff --git a/README.md b/README.md
index 88270317b..b1382c96b 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Currently supported DNS providers:
- DNSimple
- Gandi
- Google
+ - HEXONET
- Linode
- Namecheap
- Name.com
diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html
index ea5c265a1..6114c3706 100644
--- a/docs/_includes/matrix.html
+++ b/docs/_includes/matrix.html
@@ -14,6 +14,7 @@
GANDI |
GANDI-LIVEDNS |
GCLOUD |
+ HEXONET |
LINODE |
NAMECHEAP |
NAMEDOTCOM |
@@ -53,6 +54,9 @@
|
+
+
+ |
|
@@ -125,6 +129,9 @@
|
+
+
+ |
|
@@ -167,6 +174,9 @@
|
+
+
+ |
|
@@ -214,6 +224,9 @@
|
|
|
+
+
+ |
|
@@ -259,6 +272,9 @@
|
|
+
+
+ |
|
@@ -300,6 +316,9 @@
|
|
+
+
+ |
|
@@ -349,6 +368,9 @@
|
|
+
+
+ |
|
@@ -390,6 +412,9 @@
| |
|
|
+
+
+ |
|
@@ -419,6 +444,9 @@
| |
|
|
+
+
+ |
|
|
@@ -444,6 +472,9 @@
| |
|
|
+
+
+ |
|
|
|
@@ -477,6 +508,9 @@
|
+
+
+ |
|
@@ -528,6 +562,9 @@
|
+
+
+ |
|
@@ -588,6 +625,9 @@
|
+
+
+ |
|
diff --git a/docs/_providers/hexonet.md b/docs/_providers/hexonet.md
new file mode 100644
index 000000000..c7553bd33
--- /dev/null
+++ b/docs/_providers/hexonet.md
@@ -0,0 +1,92 @@
+---
+name: HEXONET
+title: HEXONET Provider
+layout: default
+jsId: HEXONET
+---
+# HEXONET Provider
+
+HEXONET is a leading developer and operator of domain names and DNS platforms.
+Individual, service provider and registrars around the globe choose HEXONET for
+domains and DNS because of our advanced technology, operational performance and
+up-time, and most importantly for DNS expertise. DnsControl with HEXONET's DNS
+marries DNS automation with an industry-leading DNS platform that supports DNSSEC,
+PremiumDNS via Anycast Network, and nearly all of DnsControl's listed provider features.
+
+## Configuration
+
+Please provide your HEXONET login data in your credentials file `creds.json` as follows:
+
+{% highlight json %}
+{
+ "hexonet": {
+ "apilogin": "your-hexonet-account-id",
+ "apipassword": "your-hexonet-account-password",
+ "apientity": "LIVE", // for the LIVE system; use "OTE" for the OT&E system
+ "ipaddress": "172.31.3.16", // provide here your outgoing ip address
+ "debugmode": "0", // set it to "1" to get debug output of the communication with our Backend System API
+ }
+}
+{% endhighlight %}
+
+Here a working example for our OT&E System:
+
+{% highlight json %}
+{
+ "hexonet": {
+ "apilogin": "test.user",
+ "apipassword": "test.passw0rd",
+ "apientity": "OTE",
+ "debugmode": "0",
+ }
+}
+{% endhighlight %}
+
+## Usage
+
+Here an example DNS Configuration `dnsconfig.js` using our provider module.
+Even though it shows how you use us as Domain Registrar AND DNS Provider, we don't force you to do that.
+You are free to decide if you want to use both of our provider technology or just one of them.
+
+{% highlight javascript %}
+// Providers:
+var REG_HX = NewRegistrar('hexonet', 'HEXONET');
+var DNS_HX = NewDnsProvider('hexonet', 'HEXONET');
+
+// Set Default TTL for all RR to reflect our Backend API Default
+// If you use additional DNS Providers, configure a default TTL
+// per domain using the domain modifyer DefaultTTL instead.
+// also check this issue for [NAMESERVER TTL](https://github.com/StackExchange/dnscontrol/issues/176).
+DEFAULTS(
+ {"ns_ttl":"3600"},
+ DefaultTTL(3600)
+);
+
+// Domains:
+D('abhoster.com', REG_HX, DnsProvider(DNS_HX),
+ NAMESERVER('ns1.ispapi.net'),
+ NAMESERVER('ns2.ispapi.net'),
+ NAMESERVER('ns3.ispapi.net'),
+ NAMESERVER('ns4.ispapi.net'),
+ A('elk1', '10.190.234.178'),
+ A('test', '56.123.54.12')
+);
+{% endhighlight %}
+
+## Metadata
+
+This provider does not recognize any special metadata fields unique to HEXONET.
+
+## New domains
+
+If a dnszone does not exist in your HEXONET account, DNSControl will *not* automatically add it with the `dnscontrol push` or `dnscontrol preview` command. You'll need to do that via the control panel manually or using the command `dnscontrol create-domains`.
+This is because it could lead to unwanted costs on customer-side that we want to avoid.
+
+## Debug Mode
+
+As shown in the configuration examples above, this can be activated on demand and it can be used to check the API commands send to our system.
+In general this is thought for our purpose to have an easy way to dive into issues. But if you're interested what's going on, feel free to activate it.
+
+## IP Filter
+
+In case you have ip filter settings made for you HEXONET account, please provide your outgoing ip address as shown in the configuration examples above.
\ No newline at end of file
diff --git a/integrationTest/providers.json b/integrationTest/providers.json
index 3e62ce27d..b3eaf25fe 100644
--- a/integrationTest/providers.json
+++ b/integrationTest/providers.json
@@ -42,6 +42,14 @@
"private_key": "$GCLOUD_PRIVATEKEY",
"project_id": "$GCLOUD_PROJECT"
},
+ "HEXONET": {
+ "apilogin": "$HEXONET_UID",
+ "apipassword": "$HEXONET_PW",
+ "apientity": "$HEXONET_ENTITY",
+ "debugmode": "$HEXONET_DEBUGMODE",
+ "ipaddress": "$HEXONET_IP",
+ "domain": "dnscontrol.com"
+ },
"LINODE": {
"COMMENT": "25: Linode's hostname validation does not allow the target domain TLD",
"token": "$LINODE_TOKEN",
diff --git a/providers/_all/all.go b/providers/_all/all.go
index 16aa27b8a..a6d9870d4 100644
--- a/providers/_all/all.go
+++ b/providers/_all/all.go
@@ -10,6 +10,7 @@ import (
_ "github.com/StackExchange/dnscontrol/providers/dnsimple"
_ "github.com/StackExchange/dnscontrol/providers/gandi"
_ "github.com/StackExchange/dnscontrol/providers/gcloud"
+ _ "github.com/StackExchange/dnscontrol/providers/hexonet"
_ "github.com/StackExchange/dnscontrol/providers/linode"
_ "github.com/StackExchange/dnscontrol/providers/namecheap"
_ "github.com/StackExchange/dnscontrol/providers/namedotcom"
diff --git a/providers/hexonet/domains.go b/providers/hexonet/domains.go
new file mode 100644
index 000000000..23f841cad
--- /dev/null
+++ b/providers/hexonet/domains.go
@@ -0,0 +1,26 @@
+package hexonet
+
+//EnsureDomainExists returns an error
+// * if access to dnszone is not allowed (not authorized) or
+// * if it doesn't exist and creating it fails
+func (n *HXClient) EnsureDomainExists(domain string) error {
+ r := n.client.Request(map[string]string{
+ "COMMAND": "StatusDNSZone",
+ "DNSZONE": domain + ".",
+ })
+ code := r.Code()
+ if code == 545 {
+ r = n.client.Request(map[string]string{
+ "COMMAND": "CreateDNSZone",
+ "DNSZONE": domain + ".",
+ })
+ if !r.IsSuccess() {
+ return n.GetHXApiError("Failed to create not existing zone for domain", domain, r)
+ }
+ } else if code == 531 {
+ return n.GetHXApiError("Not authorized to manage dnszone", domain, r)
+ } else if r.IsError() || r.IsError() {
+ return n.GetHXApiError("Error while checking status of dnszone", domain, r)
+ }
+ return nil
+}
diff --git a/providers/hexonet/error.go b/providers/hexonet/error.go
new file mode 100644
index 000000000..373bebf30
--- /dev/null
+++ b/providers/hexonet/error.go
@@ -0,0 +1,11 @@
+package hexonet
+
+import (
+ lr "github.com/hexonet/go-sdk/response/listresponse"
+ "github.com/pkg/errors"
+)
+
+// GetHXApiError returns an error including API error code and error description.
+func (n *HXClient) GetHXApiError(format string, objectid string, r *lr.ListResponse) error {
+ return errors.Errorf(format+" %s. [%s %s]", objectid, r.Code(), r.Description())
+}
diff --git a/providers/hexonet/hexonetProvider.go b/providers/hexonet/hexonetProvider.go
new file mode 100644
index 000000000..a0629482a
--- /dev/null
+++ b/providers/hexonet/hexonetProvider.go
@@ -0,0 +1,69 @@
+// Package hexonet implements a registrar that uses the hexonet api to set name servers. It will self register it's providers when imported.
+package hexonet
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/StackExchange/dnscontrol/providers"
+ hxcl "github.com/hexonet/go-sdk/client"
+)
+
+// HXClient describes a connection to the hexonet API.
+type HXClient struct {
+ APILogin string
+ APIPassword string
+ APIEntity string
+ client *hxcl.Client
+}
+
+var features = providers.DocumentationNotes{
+ providers.CanUseAlias: providers.Cannot("Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us."),
+ providers.CanUseCAA: providers.Can(),
+ providers.CanUsePTR: providers.Can(),
+ providers.CanUseRoute53Alias: providers.Cannot("Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us."),
+ providers.CanUseSRV: providers.Can(),
+ providers.CanUseTLSA: providers.Can(),
+ providers.CanUseTXTMulti: providers.Can(),
+ providers.CantUseNOPURGE: providers.Can(),
+ providers.DocCreateDomains: providers.Can(),
+ providers.DocDualHost: providers.Can(),
+ providers.DocOfficiallySupported: providers.Cannot("Actively maintained provider module."),
+}
+
+func newProvider(conf map[string]string) (*HXClient, error) {
+ api := &HXClient{
+ client: hxcl.NewClient(),
+ }
+ api.APILogin, api.APIPassword, api.APIEntity = conf["apilogin"], conf["apipassword"], conf["apientity"]
+ if conf["debugmode"] == "1" {
+ api.client.EnableDebugMode()
+ }
+ if len(conf["ipaddress"]) > 0 {
+ api.client.SetIPAddress(conf["ipaddress"])
+ }
+ if api.APIEntity != "OTE" && api.APIEntity != "LIVE" {
+ return nil, fmt.Errorf("wrong api system entity used. use \"OTE\" for OT&E system or \"LIVE\" for Live system")
+ }
+ if api.APIEntity == "OTE" {
+ api.client.UseOTESystem()
+ }
+ if api.APILogin == "" || api.APIPassword == "" {
+ return nil, fmt.Errorf("missing login credentials apilogin or apipassword")
+ }
+ api.client.SetCredentials(api.APILogin, api.APIPassword, "")
+ return api, nil
+}
+
+func newReg(conf map[string]string) (providers.Registrar, error) {
+ return newProvider(conf)
+}
+
+func newDsp(conf map[string]string, meta json.RawMessage) (providers.DNSServiceProvider, error) {
+ return newProvider(conf)
+}
+
+func init() {
+ providers.RegisterRegistrarType("HEXONET", newReg)
+ providers.RegisterDomainServiceProviderType("HEXONET", newDsp, features)
+}
diff --git a/providers/hexonet/nameservers.go b/providers/hexonet/nameservers.go
new file mode 100644
index 000000000..d25d4bf02
--- /dev/null
+++ b/providers/hexonet/nameservers.go
@@ -0,0 +1,100 @@
+package hexonet
+
+import (
+ "fmt"
+ "regexp"
+ "sort"
+ "strings"
+
+ "github.com/StackExchange/dnscontrol/models"
+)
+
+var defaultNameservers = []*models.Nameserver{
+ {Name: "ns1.ispapi.net"},
+ {Name: "ns2.ispapi.net"},
+ {Name: "ns3.ispapi.net"},
+}
+
+var nsRegex = regexp.MustCompile(`ns([1-3]{1})[0-9]+\.ispapi\.net`)
+
+// GetNameservers gets the nameservers set on a domain.
+func (n *HXClient) GetNameservers(domain string) ([]*models.Nameserver, error) {
+ // This is an interesting edge case. hexonet expects you to SET the nameservers to ns[1-3].ispapi.net,
+ // but it will internally set it to (ns1xyz|ns2uvw|ns3asd).ispapi.net, where xyz/uvw/asd is a uniqueish number.
+ // In order to avoid endless loops, we will use the unique nameservers if present, or else the generic ones if not.
+ nss, err := n.getNameserversRaw(domain)
+ if err != nil {
+ return nil, err
+ }
+ toUse := []string{
+ defaultNameservers[0].Name,
+ defaultNameservers[1].Name,
+ defaultNameservers[2].Name,
+ }
+ for _, ns := range nss {
+ if matches := nsRegex.FindStringSubmatch(ns); len(matches) == 2 && len(matches[1]) == 1 {
+ idx := matches[1][0] - '1' // regex ensures proper range
+ toUse[idx] = matches[0]
+ }
+ }
+ return models.StringsToNameservers(toUse), nil
+}
+
+func (n *HXClient) getNameserversRaw(domain string) ([]string, error) {
+ r := n.client.Request(map[string]string{
+ "COMMAND": "StatusDomain",
+ "DOMAIN": domain,
+ })
+ code := r.Code()
+ if code != 200 {
+ return nil, n.GetHXApiError("Could not get status for domain", domain, r)
+ }
+ ns := r.GetColumn("NAMESERVER")
+ sort.Strings(ns)
+ return ns, nil
+}
+
+// GetRegistrarCorrections gathers corrections that would being n to match dc.
+func (n *HXClient) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
+ nss, err := n.getNameserversRaw(dc.Name)
+ if err != nil {
+ return nil, err
+ }
+ foundNameservers := strings.Join(nss, ",")
+
+ expected := []string{}
+ for _, ns := range dc.Nameservers {
+ name := strings.TrimRight(ns.Name, ".")
+ expected = append(expected, name)
+ }
+ sort.Strings(expected)
+ expectedNameservers := strings.Join(expected, ",")
+
+ if foundNameservers != expectedNameservers {
+ return []*models.Correction{
+ {
+ Msg: fmt.Sprintf("Update nameservers %s -> %s", foundNameservers, expectedNameservers),
+ F: n.updateNameservers(expected, dc.Name),
+ },
+ }, nil
+ }
+ return nil, nil
+}
+
+func (n *HXClient) updateNameservers(ns []string, domain string) func() error {
+ return func() error {
+ cmd := map[string]string{
+ "COMMAND": "ModifyDomain",
+ "DOMAIN": domain,
+ }
+ for idx, ns := range ns {
+ cmd[fmt.Sprintf("NAMESERVER%d", idx)] = ns
+ }
+ response := n.client.Request(cmd)
+ code := response.Code()
+ if code != 200 {
+ return fmt.Errorf(fmt.Sprintf("%d %s", code, response.Description()))
+ }
+ return nil
+ }
+}
diff --git a/providers/hexonet/records.go b/providers/hexonet/records.go
new file mode 100644
index 000000000..5a37fec2e
--- /dev/null
+++ b/providers/hexonet/records.go
@@ -0,0 +1,277 @@
+package hexonet
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+
+ "github.com/StackExchange/dnscontrol/models"
+ "github.com/StackExchange/dnscontrol/providers/diff"
+)
+
+// HXRecord covers an individual DNS resource record.
+type HXRecord struct {
+ // Raw api value of that RR
+ Raw string
+ // DomainName is the zone that the record belongs to.
+ DomainName string
+ // Host is the hostname relative to the zone: e.g. for a record for blog.example.org, domain would be "example.org" and host would be "blog".
+ // An apex record would be specified by either an empty host "" or "@".
+ // A SRV record would be specified by "_{service}._{protocal}.{host}": e.g. "_sip._tcp.phone" for _sip._tcp.phone.example.org.
+ Host string
+ // FQDN is the Fully Qualified Domain Name. It is the combination of the host and the domain name. It always ends in a ".". FQDN is ignored in CreateRecord, specify via the Host field instead.
+ Fqdn string
+ // Type is one of the following: A, AAAA, ANAME, CNAME, MX, NS, SRV, or TXT.
+ Type string
+ // Answer is either the IP address for A or AAAA records; the target for ANAME, CNAME, MX, or NS records; the text for TXT records.
+ // For SRV records, answer has the following format: "{weight} {port} {target}" e.g. "1 5061 sip.example.org".
+ Answer string
+ // TTL is the time this record can be cached for in seconds.
+ TTL uint32
+ // Priority is only required for MX and SRV records, it is ignored for all others.
+ Priority uint32
+}
+
+// GetDomainCorrections gathers correctios that would bring n to match dc.
+func (n *HXClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
+ dc.Punycode()
+ records, err := n.getRecords(dc.Name)
+ if err != nil {
+ return nil, err
+ }
+ actual := make([]*models.RecordConfig, len(records))
+ for i, r := range records {
+ actual[i] = toRecord(r, dc.Name)
+ }
+
+ for _, rec := range dc.Records {
+ if rec.Type == "ALIAS" {
+ return nil, fmt.Errorf("We support realtime ALIAS RR over our X-DNS service, please get in touch with us")
+ }
+ }
+
+ //checkNSModifications(dc)
+
+ // Normalize
+ models.PostProcessRecords(actual)
+
+ differ := diff.New(dc)
+ _, create, del, mod := differ.IncrementalDiff(actual)
+ corrections := []*models.Correction{}
+
+ buf := &bytes.Buffer{}
+ // Print a list of changes. Generate an actual change that is the zone
+ changes := false
+ params := map[string]string{}
+ delrridx := 0
+ addrridx := 0
+ for _, cre := range create {
+ changes = true
+ fmt.Fprintln(buf, cre)
+ rec := cre.Desired
+ params[fmt.Sprintf("ADDRR%d", addrridx)] = n.createRecordString(rec, dc.Name)
+ addrridx++
+ }
+ for _, d := range del {
+ changes = true
+ fmt.Fprintln(buf, d)
+ rec := d.Existing.Original.(*HXRecord)
+ params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(rec, dc.Name)
+ delrridx++
+ }
+ for _, chng := range mod {
+ changes = true
+ fmt.Fprintln(buf, chng)
+ old := chng.Existing.Original.(*HXRecord)
+ new := chng.Desired
+ params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(old, dc.Name)
+ params[fmt.Sprintf("ADDRR%d", addrridx)] = n.createRecordString(new, dc.Name)
+ addrridx++
+ delrridx++
+ }
+ msg := fmt.Sprintf("GENERATE_ZONEFILE: %s\n", dc.Name) + buf.String()
+
+ if changes {
+ corrections = append(corrections, &models.Correction{
+ Msg: msg,
+ F: func() error {
+ return n.updateZoneBy(params, dc.Name)
+ },
+ })
+ }
+ return corrections, nil
+}
+
+func toRecord(r *HXRecord, origin string) *models.RecordConfig {
+ rc := &models.RecordConfig{
+ Type: r.Type,
+ TTL: r.TTL,
+ Original: r,
+ }
+ fqdn := r.Fqdn[:len(r.Fqdn)-1]
+ rc.SetLabelFromFQDN(fqdn, origin)
+
+ switch rtype := r.Type; rtype {
+ case "TXT":
+ rc.SetTargetTXTs(decodeTxt(r.Answer))
+ case "MX":
+ if err := rc.SetTargetMX(uint16(r.Priority), r.Answer); err != nil {
+ panic(errors.Wrap(err, "unparsable MX record received from hexonet api"))
+ }
+ case "SRV":
+ if err := rc.SetTargetSRVPriorityString(uint16(r.Priority), r.Answer); err != nil {
+ panic(errors.Wrap(err, "unparsable SRV record received from hexonet api"))
+ }
+ default: // "A", "AAAA", "ANAME", "CNAME", "NS"
+ if err := rc.PopulateFromString(rtype, r.Answer, r.Fqdn); err != nil {
+ panic(errors.Wrap(err, "unparsable record received from hexonet api"))
+ }
+ }
+ return rc
+}
+
+func (n *HXClient) showCommand(cmd map[string]string) {
+ b, err := json.MarshalIndent(cmd, "", " ")
+ if err != nil {
+ fmt.Println("error:", err)
+ }
+ fmt.Print(string(b))
+}
+
+func (n *HXClient) updateZoneBy(params map[string]string, domain string) error {
+ zone := domain + "."
+ cmd := map[string]string{
+ "COMMAND": "UpdateDNSZone",
+ "DNSZONE": zone,
+ "INCSERIAL": "1",
+ }
+ for key, val := range params {
+ cmd[key] = val
+ }
+ // n.showCommand(cmd)
+ r := n.client.Request(cmd)
+ if !r.IsSuccess() {
+ return n.GetHXApiError("Error while updating zone", zone, r)
+ }
+ return nil
+}
+
+func (n *HXClient) getRecords(domain string) ([]*HXRecord, error) {
+ var records []*HXRecord
+ zone := domain + "."
+ cmd := map[string]string{
+ "COMMAND": "QueryDNSZoneRRList",
+ "DNSZONE": zone,
+ "SHORT": "1",
+ "EXTENDED": "0",
+ }
+ r := n.client.Request(cmd)
+ if !r.IsSuccess() {
+ if r.Code() == 545 {
+ return nil, n.GetHXApiError("Use `dnscontrol create-domains` to create not-existing zone", domain, r)
+ }
+ return nil, n.GetHXApiError("Failed loading resource records for zone", domain, r)
+ }
+ rrs := r.GetColumn("RR")
+ for _, rr := range rrs {
+ spl := strings.Split(rr, " ")
+ if spl[3] != "SOA" {
+ record := &HXRecord{
+ Raw: rr,
+ DomainName: domain,
+ Host: spl[0],
+ Fqdn: domain + ".",
+ Type: spl[3],
+ }
+ ttl, _ := strconv.ParseUint(spl[1], 10, 32)
+ record.TTL = uint32(ttl)
+ if record.Host != "@" {
+ record.Fqdn = spl[0] + "." + record.Fqdn
+ }
+ if record.Type == "MX" || record.Type == "SRV" {
+ prio, _ := strconv.ParseUint(spl[4], 10, 32)
+ record.Priority = uint32(prio)
+ record.Answer = strings.Join(spl[5:], " ")
+ } else {
+ record.Answer = strings.Join(spl[4:], " ")
+ }
+ records = append(records, record)
+ }
+ }
+ return records, nil
+}
+
+func (n *HXClient) createRecordString(rc *models.RecordConfig, domain string) string {
+ record := &HXRecord{
+ DomainName: domain,
+ Host: rc.GetLabel(),
+ Type: rc.Type,
+ Answer: rc.GetTargetField(),
+ TTL: rc.TTL,
+ Priority: uint32(rc.MxPreference),
+ }
+ switch rc.Type { // #rtype_variations
+ case "A", "AAAA", "ANAME", "CNAME", "MX", "NS", "PTR":
+ // nothing
+ case "TLSA":
+ record.Answer = fmt.Sprintf(`%v %v %v %s`, rc.TlsaUsage, rc.TlsaSelector, rc.TlsaMatchingType, rc.Target)
+ case "CAA":
+ record.Answer = fmt.Sprintf(`%v %s "%s"`, rc.CaaFlag, rc.CaaTag, record.Answer)
+ case "TXT":
+ record.Answer = encodeTxt(rc.TxtStrings)
+ case "SRV":
+ record.Answer = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, record.Answer)
+ record.Priority = uint32(rc.SrvPriority)
+ default:
+ panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type))
+ // We panic so that we quickly find any switch statements
+ // that have not been updated for a new RR type.
+ }
+
+ str := record.Host + " " + fmt.Sprint(record.TTL) + " IN " + record.Type + " "
+ if record.Type == "MX" || record.Type == "SRV" {
+ str += fmt.Sprint(record.Priority) + " "
+ }
+ str += record.Answer
+ return str
+}
+
+func (n *HXClient) deleteRecordString(record *HXRecord, domain string) string {
+ return record.Raw
+}
+
+// encodeTxt encodes TxtStrings for sending in the CREATE/MODIFY API:
+func encodeTxt(txts []string) string {
+ ans := txts[0]
+
+ if len(txts) > 1 {
+ ans = ""
+ for _, t := range txts {
+ ans += `"` + strings.Replace(t, `"`, `\"`, -1) + `"`
+ }
+ }
+ return ans
+}
+
+// finds a string surrounded by quotes that might contain an escaped quote character.
+var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
+
+// decodeTxt decodes the TXT record as received from hexonet api and
+// returns the list of strings.
+func decodeTxt(s string) []string {
+
+ if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
+ txtStrings := []string{}
+ for _, t := range quotedStringRegexp.FindAllStringSubmatch(s, -1) {
+ txtString := strings.Replace(t[1], `\"`, `"`, -1)
+ txtStrings = append(txtStrings, txtString)
+ }
+ return txtStrings
+ }
+ return []string{s}
+}
diff --git a/providers/hexonet/records_test.go b/providers/hexonet/records_test.go
new file mode 100644
index 000000000..3f9db2b42
--- /dev/null
+++ b/providers/hexonet/records_test.go
@@ -0,0 +1,51 @@
+package hexonet
+
+import (
+ "strings"
+ "testing"
+)
+
+var txtData = []struct {
+ decoded []string
+ encoded string
+}{
+ {[]string{`simple`}, `simple`},
+ {[]string{`changed`}, `changed`},
+ {[]string{`with spaces`}, `with spaces`},
+ {[]string{`with whitespace`}, `with whitespace`},
+ {[]string{"one", "two"}, `"one""two"`},
+ {[]string{"eh", "bee", "cee"}, `"eh""bee""cee"`},
+ {[]string{"o\"ne", "tw\"o"}, `"o\"ne""tw\"o"`},
+ {[]string{"dimple"}, `dimple`},
+ {[]string{"fun", "two"}, `"fun""two"`},
+ {[]string{"eh", "bzz", "cee"}, `"eh""bzz""cee"`},
+}
+
+func TestEncodeTxt(t *testing.T) {
+ // Test encoded the lists of strings into a string:
+ for i, test := range txtData {
+ enc := encodeTxt(test.decoded)
+ if enc != test.encoded {
+ t.Errorf("%v: txt\n data: []string{%v}\nexpected: %s\n got: %s",
+ i, "`"+strings.Join(test.decoded, "`, `")+"`", test.encoded, enc)
+ }
+ }
+}
+
+func TestDecodeTxt(t *testing.T) {
+ // Test decoded a string into the list of strings:
+ for i, test := range txtData {
+ data := test.encoded
+ got := decodeTxt(data)
+ wanted := test.decoded
+ if len(got) != len(wanted) {
+ t.Errorf("%v: txt\n decode: %v\nexpected: `%v`\n got: `%v`\n", i, data, strings.Join(wanted, "`, `"), strings.Join(got, "`, `"))
+ } else {
+ for j := range got {
+ if got[j] != wanted[j] {
+ t.Errorf("%v: txt\n decode: %v\nexpected: `%v`\n got: `%v`\n", i, data, strings.Join(wanted, "`, `"), strings.Join(got, "`, `"))
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/hexonet/go-sdk/CONTRIBUTING.md b/vendor/github.com/hexonet/go-sdk/CONTRIBUTING.md
new file mode 100644
index 000000000..da468d072
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/CONTRIBUTING.md
@@ -0,0 +1,85 @@
+# Contributing
+
+When contributing to this repository, please first discuss the change you wish to make via issue,
+email, or any other method with the owners of this repository before making a change.
+
+Please note we have a code of conduct, please follow it in all your interactions with the project.
+
+## Pull Request Process
+
+Read [here](https://github.com/hexonet/go-sdk/wiki/Development-Guide#pull-request-pr-procedure).
+
+## Code of Conduct
+
+### Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+### Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+### Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+### Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+### Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/vendor/github.com/hexonet/go-sdk/HISTORY.md b/vendor/github.com/hexonet/go-sdk/HISTORY.md
new file mode 100644
index 000000000..5afef6076
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/HISTORY.md
@@ -0,0 +1,48 @@
+### Changelog
+
+All notable changes to this project will be documented in this file. Dates are displayed in UTC.
+
+#### [v1.2.0](https://github.com/hexonet/go-sdk/compare/v1.1.0...v1.2.0) (2 August 2018)
+
+- added debugMode; possibility to enable debug output [`a46d0cf`](https://github.com/hexonet/go-sdk/commit/a46d0cfefaafec10bfaac536be7cbe365d837eaf)
+- updated changelog [`3ae8d48`](https://github.com/hexonet/go-sdk/commit/3ae8d48d1dbf989d714ff206f84dac8c7858a98b)
+- HM-329 change changelog template [`689b56e`](https://github.com/hexonet/go-sdk/commit/689b56ee2a9cf53604e7c835879fb635999da94c)
+- Add missing -tree option to govendor fetch command [`b6b0108`](https://github.com/hexonet/go-sdk/commit/b6b01080ead0752d5f27e8d0e6a7215250cbd13a)
+- Update README.md [`203d8a0`](https://github.com/hexonet/go-sdk/commit/203d8a0abdc88ab5a06aeae8a3a70c9f9489f381)
+
+#### [v1.1.0](https://github.com/hexonet/go-sdk/compare/v1.0.1...v1.1.0) (4 July 2018)
+
+- updated tests; added scripts; changelog [`9415d4e`](https://github.com/hexonet/go-sdk/commit/9415d4e8f9e0db44fc252b43dbac5e021d988943)
+- Update README.md [`285345c`](https://github.com/hexonet/go-sdk/commit/285345cd882f4a1417995930b200fe6d3dbac8f0)
+- Update README.md [`25345f8`](https://github.com/hexonet/go-sdk/commit/25345f80c9d3b71273a28493f6839796cecaa727)
+- Update CONTRIBUTING.md [`cc1cbb2`](https://github.com/hexonet/go-sdk/commit/cc1cbb2e16a8740fa0236b63a1474d9161b04cd2)
+- Update README.md [`731bf85`](https://github.com/hexonet/go-sdk/commit/731bf8584526c7be4962894e29b1e616f3d830a0)
+- Update README.md [`132895a`](https://github.com/hexonet/go-sdk/commit/132895aa28ba90a42ad62a91a8c2855d6d372999)
+- Update README.md [`d74cfc5`](https://github.com/hexonet/go-sdk/commit/d74cfc584c746719566a07ae63bab0f7a7da0d68)
+- Update README.md [`e007900`](https://github.com/hexonet/go-sdk/commit/e007900b3e5c1907ba0682343c9f2253f7742b03)
+- Update README.md [`06806ef`](https://github.com/hexonet/go-sdk/commit/06806ef58699fac27869c7ab70d2a69fa4a79f00)
+- Update README.md [`31f2df2`](https://github.com/hexonet/go-sdk/commit/31f2df2cc9071176f3a9c231ed5715a9e1a31823)
+- Update CONTRIBUTING.md [`59bdfe9`](https://github.com/hexonet/go-sdk/commit/59bdfe989caa5b6788f2e92d3188a866f6ae0917)
+- Update README.md [`792cb7f`](https://github.com/hexonet/go-sdk/commit/792cb7fb6f91583f1fcbfdc2ed8b5295caf5a572)
+
+#### [v1.0.1](https://github.com/hexonet/go-sdk/compare/v1.0.0...v1.0.1) (28 June 2018)
+
+- readme: added some standard go badges [`9d12d6f`](https://github.com/hexonet/go-sdk/commit/9d12d6fda2053258188aa0edefc4b75e458ab159)
+- updated versioning info in readme [`607de6f`](https://github.com/hexonet/go-sdk/commit/607de6f787df016f42216e38dcee5a22f4042093)
+- fix misspelling [`a8a817c`](https://github.com/hexonet/go-sdk/commit/a8a817c729ad118b9598165bb95cf81a919e8124)
+- fix slack badge [`9cc090b`](https://github.com/hexonet/go-sdk/commit/9cc090b845a7c482ebe095f31cee73e1cd6a491e)
+- Update README.md [`efe73fb`](https://github.com/hexonet/go-sdk/commit/efe73fb57039d0d101a96c5171b862493b3a3adf)
+
+#### v1.0.0 (20 June 2018)
+
+- updated readme [`1078244`](https://github.com/hexonet/go-sdk/commit/107824423f630da8978afacbf5cac5c1c31331e4)
+- updated readme [`2d49a83`](https://github.com/hexonet/go-sdk/commit/2d49a83bf9275b2bbd6a42b2aee1b06b0878b230)
+- updated readme [`5215f7a`](https://github.com/hexonet/go-sdk/commit/5215f7a1f6e1e195f692ea9ecffe444f23a47627)
+- updated imports [`c9a184c`](https://github.com/hexonet/go-sdk/commit/c9a184cf865de416e747ac637a0b9d99ea60b0e7)
+- updated imports [`be9f573`](https://github.com/hexonet/go-sdk/commit/be9f57375d5777075f2268e23d70eb47650f1bd1)
+- updated readme [`358ab17`](https://github.com/hexonet/go-sdk/commit/358ab17bdb082a9f3b4c40d37fc07a1c4105ee45)
+- updated readme [`0d98aa2`](https://github.com/hexonet/go-sdk/commit/0d98aa26a89b69d9a4a0a29942e25b6de5979f4a)
+- update readme [`c5f13d8`](https://github.com/hexonet/go-sdk/commit/c5f13d8db64cbf3af6f753cec5d0b9359b439a69)
+- updated readme [`3861e83`](https://github.com/hexonet/go-sdk/commit/3861e83ca2edb12b18e79a601f310c5168b90c08)
+- initial release [`205e1b4`](https://github.com/hexonet/go-sdk/commit/205e1b4db0404d69567970bd9793a255e5470136)
+- Initial commit [`26ff843`](https://github.com/hexonet/go-sdk/commit/26ff843470f09df434dc4b5d1ee32d2e9115858a)
diff --git a/vendor/github.com/hexonet/go-sdk/LICENSE b/vendor/github.com/hexonet/go-sdk/LICENSE
new file mode 100644
index 000000000..c65316d63
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 HEXONET
+
+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.
diff --git a/vendor/github.com/hexonet/go-sdk/README.md b/vendor/github.com/hexonet/go-sdk/README.md
new file mode 100644
index 000000000..c9473a5a0
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/README.md
@@ -0,0 +1,128 @@
+# go-sdk
+
+[](https://godoc.org/github.com/hexonet/go-sdk)
+[](https://goreportcard.com/report/github.com/hexonet/go-sdk)
+[](https://cover.run/go?tag=golang-1.10&repo=github.com%2Fhexonet%2Fgo-sdk)
+[](https://hexonet-sdk.slack.com/messages/CBFHLTL2X)
+
+This module is a connector library for the insanely fast HEXONET Backend API. For further informations visit our [homepage](http://hexonet.net) and do not hesitate to [contact us](https://www.hexonet.net/contact).
+
+## Resources
+
+* [Usage Guide](https://github.com/hexonet/go-sdk/blob/master/README.md#how-to-use-this-module-in-your-project)
+* [SDK Documenation](https://godoc.org/github.com/hexonet/go-sdk)
+* [HEXONET Backend API Documentation](https://github.com/hexonet/hexonet-api-documentation/tree/master/API)
+* [Release Notes](https://github.com/hexonet/go-sdk/releases)
+* [Development Guide](https://github.com/hexonet/go-sdk/wiki/Development-Guide)
+
+## How to use this module in your project
+
+We have also a demo app available showing how to integrate and use our SDK. See [here](https://github.com/hexonet/go-sdk-demo).
+
+### Requirements
+
+* Installed [GO/GOLANG](https://golang.org/doc/install). Restart your machine after installing GO.
+* Installed [govendor](https://github.com/kardianos/govendor).
+
+NOTE: Make sure you add the go binary path to your PATH environment variable. Add the below lines for a standard installation into your profile configuration file (~/.profile).
+
+```bash
+export GOPATH=$HOME/go
+export PATH=$PATH:$GOPATH/bin
+```
+
+Then reload the profile configuration by `source ~/.profile`.
+
+### Using govendor
+
+Use [govendor](https://github.com/kardianos/govendor) for the dependency installation by `govendor fetch -tree github.com/hexonet/go-sdk@` where *tag id* corresponds to a [release version tag](https://github.com/hexonet/go-sdk/releases). You can update this dependency later on by `govendor sync github.com/hexonet/go-sdk@`. The dependencies will be installed in your project's subfolder "vendor". Import the module in your project as shown in the examples below.
+
+For more details on govendor, please read the [CheatSheet](https://github.com/kardianos/govendor/wiki/Govendor-CheatSheet) and also the [developer guide](https://github.com/kardianos/govendor/blob/master/doc/dev-guide.md).
+
+### Usage Examples
+
+Please have an eye on our [HEXONET Backend API documentation](https://github.com/hexonet/hexonet-api-documentation/tree/master/API). Here you can find information on available Commands and their response data.
+
+#### Session based API Communication
+
+```go
+package main
+
+import (
+ "github.com/hexonet/go-sdk/client"
+ "fmt"
+)
+
+func main() {
+ cl := client.NewClient()
+ cl.SetCredentials("test.user", "test.passw0rd", "")//username, password, otp code (2FA)
+ cl.UseOTESystem()
+
+ // use this to provide your outgoing ip address for api communication
+ // to be used in case you have ip filter settings active
+ // cl.SetIPAddress("174.21.132.16");
+
+ // cl.EnableDebugMode() // to activate debug outputs of the API communication
+ r := cl.Login()
+ if r.IsSuccess() {
+ fmt.Println("Login succeeded.")
+ cmd := map[string]string{
+ "COMMAND": "StatusAccount",
+ }
+ r = cl.Request(cmd)
+ if r.IsSuccess() {
+ fmt.Println("Command succeeded.")
+ r = cl.Logout()
+ if r.IsSuccess() {
+ fmt.Println("Logout succeeded.")
+ } else {
+ fmt.Println("Logout failed.")
+ }
+ } else {
+ fmt.Println("Command failed.")
+ }
+ } else {
+ fmt.Println("Login failed.")
+ }
+}
+```
+
+#### Sessionless API Communication
+
+```go
+ package main
+
+import (
+ "github.com/hexonet/go-sdk/client"
+ "fmt"
+)
+
+func main() {
+ cl := client.NewClient()
+ cl.SetCredentials("test.user", "test.passw0rd", "")
+ cl.UseOTESystem()
+ cmd := map[string]string{
+ "COMMAND": "StatusAccount",
+ }
+ r := cl.Request(cmd)
+ if r.IsSuccess() {
+ fmt.Println("Command succeeded.")
+ } else {
+ fmt.Println("Command failed.")
+ }
+}
+```
+
+## Contributing
+
+Please read [our development guide](https://github.com/hexonet/go-sdk/wiki/Development-Guide) for details on our code of conduct, and the process for submitting pull requests to us.
+
+## Authors
+
+* **Kai Schwarz** - *lead development* - [PapaKai](https://github.com/papakai)
+
+See also the list of [contributors](https://github.com/hexonet/go-sdk/graphs/contributors) who participated in this project.
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
diff --git a/vendor/github.com/hexonet/go-sdk/apiconnector.go b/vendor/github.com/hexonet/go-sdk/apiconnector.go
new file mode 100644
index 000000000..ffebb812e
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/apiconnector.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2018 Kai Schwarz (1API GmbH). All rights reserved.
+//
+// Use of this source code is governed by the MIT
+// license that can be found in the LICENSE.md file.
+
+package main
+
+func main() {
+}
diff --git a/vendor/github.com/hexonet/go-sdk/client/client.go b/vendor/github.com/hexonet/go-sdk/client/client.go
new file mode 100644
index 000000000..35fa73a83
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/client/client.go
@@ -0,0 +1,242 @@
+// Copyright (c) 2018 Kai Schwarz (1API GmbH). All rights reserved.
+//
+// Use of this source code is governed by the MIT
+// license that can be found in the LICENSE.md file.
+
+// Package client contains all you need to communicate with the insanely fast 1API backend API.
+package client
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/hexonet/go-sdk/client/socketcfg"
+ "github.com/hexonet/go-sdk/response/hashresponse"
+ "github.com/hexonet/go-sdk/response/listresponse"
+)
+
+// Client is the entry point class for communicating with the insanely fast 1API backend api.
+// It allows two ways of communication:
+// * session based communication
+// * sessionless communication
+//
+// A session based communication makes sense in case you use it to
+// build your own frontend on top. It allows also to use 2FA
+// (2 Factor Auth) by providing "otp" in the config parameter of
+// the login method.
+// A sessionless communication makes sense in case you do not need
+// to care about the above and you have just to request some commands.
+//
+// Possible commands can be found at https://github.com/hexonet/hexonet-api-documentation/tree/master/API
+type Client struct {
+ debugMode bool
+ socketTimeout int
+ apiurl string
+ socketcfg.Socketcfg
+}
+
+// NewClient represents the constructor for struct Client.
+// The client is by default set to communicate with the LIVE system. Use method UseOTESystem to switch to the OT&E system instance.
+func NewClient() *Client {
+ cl := &Client{
+ debugMode: false,
+ socketTimeout: 300000,
+ apiurl: "https://coreapi.1api.net/api/call.cgi",
+ Socketcfg: socketcfg.Socketcfg{},
+ }
+ cl.UseLiveSystem()
+ return cl
+}
+
+// EncodeData method to use to encode provided data (socket configuration and api command) before sending it to the API server
+// It returns the encoded data ready to use within POST request of type "application/x-www-form-urlencoded"
+func (c *Client) EncodeData(cfg *socketcfg.Socketcfg, cmd map[string]string) string {
+ var tmp, data strings.Builder
+ tmp.WriteString(cfg.EncodeData())
+ tmp.WriteString(url.QueryEscape("s_command"))
+ tmp.WriteString("=")
+
+ for k, v := range cmd {
+ re := regexp.MustCompile(`\r?\n`)
+ v = re.ReplaceAllString(v, "")
+ if len(v) > 0 {
+ data.WriteString(k)
+ data.WriteString("=")
+ data.WriteString(v)
+ data.WriteString("\n")
+ }
+ }
+ tmp.WriteString(url.QueryEscape(data.String()))
+ return tmp.String()
+}
+
+// Getapiurl is the getter method for apiurl property
+func (c *Client) Getapiurl() string {
+ return c.apiurl
+}
+
+// Setapiurl is the setter method for apiurl
+func (c *Client) Setapiurl(url string) {
+ c.apiurl = url
+}
+
+// SetCredentials method to set username and password and otp code to use for api communication
+// set otp code to empty string, if you do not use 2FA
+func (c *Client) SetCredentials(username string, password string, otpcode string) {
+ c.Socketcfg.SetCredentials(username, password, otpcode)
+}
+
+// SetIPAddress method to set api client to submit this ip address in api communication
+func (c *Client) SetIPAddress(ip string) {
+ c.Socketcfg.SetIPAddress(ip)
+}
+
+// SetSubuserView method to activate the use of a subuser account as data view
+func (c *Client) SetSubuserView(username string) {
+ c.Socketcfg.SetUser(username)
+}
+
+// ResetSubuserView method to deactivate the use of a subuser account as data view
+func (c *Client) ResetSubuserView() {
+ c.Socketcfg.SetUser("")
+}
+
+// UseLiveSystem method to set api client to communicate with the LIVE backend API
+func (c *Client) UseLiveSystem() {
+ c.Socketcfg.SetEntity("54cd")
+}
+
+// UseOTESystem method to set api client to communicate with the OT&E backend API
+func (c *Client) UseOTESystem() {
+ c.Socketcfg.SetEntity("1234")
+}
+
+// EnableDebugMode method to enable debugMode for debug output
+func (c *Client) EnableDebugMode() {
+ c.debugMode = true
+}
+
+// DisableDebugMode method to disable debugMode for debug output
+func (c *Client) DisableDebugMode() {
+ c.debugMode = false
+}
+
+// Request method requests the given command to the api server and returns the response as ListResponse.
+func (c *Client) Request(cmd map[string]string) *listresponse.ListResponse {
+ if c.Socketcfg == (socketcfg.Socketcfg{}) {
+ return listresponse.NewListResponse(hashresponse.NewTemplates().Get("expired"))
+ }
+ return c.dorequest(cmd, &c.Socketcfg)
+}
+
+// debugRequest method used to trigger debug output in case debugMode is activated
+func (c *Client) debugRequest(cmd map[string]string, data string, r *listresponse.ListResponse) {
+ if c.debugMode {
+ j, _ := json.Marshal(cmd)
+ fmt.Printf("%s\n", j)
+ fmt.Println("POST: " + data)
+ fmt.Println(strconv.Itoa(r.Code()) + " " + r.Description() + "\n")
+ }
+}
+
+// RequestAll method requests ALL entries matching the request criteria by the given command from api server.
+// So useful for client-side lists. Finally it returns the response as ListResponse.
+func (c *Client) RequestAll(cmd map[string]string) *listresponse.ListResponse {
+ if c.Socketcfg == (socketcfg.Socketcfg{}) {
+ return listresponse.NewListResponse(hashresponse.NewTemplates().Get("expired"))
+ }
+ cmd["LIMIT"] = "1"
+ cmd["FIRST"] = "0"
+ r := c.dorequest(cmd, &c.Socketcfg)
+ if r.IsSuccess() {
+ cmd["LIMIT"] = strconv.Itoa(r.Total())
+ cmd["FIRST"] = "0"
+ r = c.dorequest(cmd, &c.Socketcfg)
+ }
+ return r
+}
+
+// request the given command to the api server by using the provided socket configuration and return the response as ListResponse.
+func (c *Client) dorequest(cmd map[string]string, cfg *socketcfg.Socketcfg) *listresponse.ListResponse {
+ data := c.EncodeData(cfg, cmd)
+ client := &http.Client{}
+ req, err := http.NewRequest("POST", c.apiurl, strings.NewReader(data))
+ if err != nil {
+ tpl := hashresponse.NewTemplates().Get("commonerror")
+ tpl = strings.Replace(tpl, "####ERRMSG####", err.Error(), 1)
+ r := listresponse.NewListResponse(tpl)
+ c.debugRequest(cmd, data, r)
+ return r
+ }
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+ req.Header.Add("Expect", "")
+ resp, err2 := client.Do(req)
+ if err2 != nil {
+ tpl := hashresponse.NewTemplates().Get("commonerror")
+ tpl = strings.Replace(tpl, "####ERRMSG####", err2.Error(), 1)
+ r := listresponse.NewListResponse(tpl)
+ c.debugRequest(cmd, data, r)
+ return r
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusOK {
+ response, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ tpl := hashresponse.NewTemplates().Get("commonerror")
+ tpl = strings.Replace(tpl, "####ERRMSG####", err.Error(), 1)
+ r := listresponse.NewListResponse(tpl)
+ c.debugRequest(cmd, data, r)
+ return r
+ }
+ r := listresponse.NewListResponse(string(response))
+ c.debugRequest(cmd, data, r)
+ return r
+ }
+ tpl := hashresponse.NewTemplates().Get("commonerror")
+ tpl = strings.Replace(tpl, "####ERRMSG####", string(resp.StatusCode)+resp.Status, 1)
+ r := listresponse.NewListResponse(tpl)
+ c.debugRequest(cmd, data, r)
+ return r
+}
+
+// Login method to use as entry point for session based communication.
+// Response is returned as ListResponse.
+func (c *Client) Login() *listresponse.ListResponse {
+ return c.dologin(map[string]string{"COMMAND": "StartSession"})
+}
+
+// LoginExtended method to use as entry point for session based communication.
+// This method allows to provide further command parameters for startsession command.
+// Response is returned as ListResponse.
+func (c *Client) LoginExtended(cmdparams map[string]string) *listresponse.ListResponse {
+ cmd := map[string]string{"COMMAND": "StartSession"}
+ for k, v := range cmdparams {
+ cmd[k] = v
+ }
+ return c.dologin(cmd)
+}
+
+// dologin method used internally to perform a login using the given command.
+// Response is returned as ListResponse.
+func (c *Client) dologin(cmd map[string]string) *listresponse.ListResponse {
+ r := c.dorequest(cmd, &c.Socketcfg)
+ if r.Code() == 200 {
+ sessid, _ := r.GetColumnIndex("SESSION", 0)
+ c.Socketcfg.SetSession(sessid)
+ }
+ return r
+}
+
+// Logout method to use for session based communication.
+// This method logs you out and destroys the api session.
+// Response is returned as ListResponse.
+func (c *Client) Logout() *listresponse.ListResponse {
+ cmd := map[string]string{"COMMAND": "EndSession"}
+ return c.dorequest(cmd, &c.Socketcfg)
+}
diff --git a/vendor/github.com/hexonet/go-sdk/client/socketcfg/socketcfg.go b/vendor/github.com/hexonet/go-sdk/client/socketcfg/socketcfg.go
new file mode 100644
index 000000000..963fab36c
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/client/socketcfg/socketcfg.go
@@ -0,0 +1,106 @@
+// Copyright (c) 2018 Kai Schwarz (1API GmbH). All rights reserved.
+//
+// Use of this source code is governed by the MIT
+// license that can be found in the LICENSE.md file.
+
+// Package socketcfg provides apiconnector client connection settings
+package socketcfg
+
+import (
+ "net/url"
+ "strings"
+)
+
+// Socketcfg is a struct representing connection settings used as POST data for http request against the insanely fast 1API backend API.
+type Socketcfg struct {
+ login string
+ pw string
+ remoteaddr string
+ entity string
+ session string
+ user string
+ otp string
+}
+
+// SetIPAddress method to set remote ip address to be submitted to the HEXONET API.
+// This ip address is being considered when you have ip filter settings activated.
+// To reset this, simply provide an empty string as parameter.
+func (s *Socketcfg) SetIPAddress(ip string) {
+ s.remoteaddr = ip
+}
+
+// SetCredentials method to set username and password to use for api communication
+func (s *Socketcfg) SetCredentials(username string, password string, otpcode string) {
+ s.login = username
+ s.pw = password
+ s.otp = otpcode
+}
+
+// SetEntity method to set the system entity id used to communicate with
+// "1234" -> OT&E system, "54cd" -> LIVE system
+func (s *Socketcfg) SetEntity(entityid string) {
+ s.entity = entityid
+}
+
+// SetSession method to set a API session id to use for api communication instead of credentials
+// which is basically required in case you plan to use session based communication or if you want to use 2FA
+func (s *Socketcfg) SetSession(sessionid string) {
+ s.login = ""
+ s.pw = ""
+ s.otp = ""
+ s.session = sessionid
+}
+
+// SetUser method to set an user account (must be subuser account of your login user) to use for API communication
+// use this if you want to make changes on that subuser account or if you want to have his data view
+func (s *Socketcfg) SetUser(username string) {
+ s.user = username
+}
+
+// EncodeData method to return the struct data ready to submit within POST request of type "application/x-www-form-urlencoded"
+func (s *Socketcfg) EncodeData() string {
+ var tmp strings.Builder
+ if len(s.login) > 0 {
+ tmp.WriteString(url.QueryEscape("s_login"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.login))
+ tmp.WriteString("&")
+ }
+ if len(s.pw) > 0 {
+ tmp.WriteString(url.QueryEscape("s_pw"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.pw))
+ tmp.WriteString("&")
+ }
+ if len(s.remoteaddr) > 0 {
+ tmp.WriteString(url.QueryEscape("s_remoteaddr"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.remoteaddr))
+ tmp.WriteString("&")
+ }
+ if len(s.entity) > 0 {
+ tmp.WriteString(url.QueryEscape("s_entity"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.entity))
+ tmp.WriteString("&")
+ }
+ if len(s.session) > 0 {
+ tmp.WriteString(url.QueryEscape("s_session"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.session))
+ tmp.WriteString("&")
+ }
+ if len(s.user) > 0 {
+ tmp.WriteString(url.QueryEscape("s_user"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.user))
+ tmp.WriteString("&")
+ }
+ if len(s.otp) > 0 {
+ tmp.WriteString(url.QueryEscape("s_otp"))
+ tmp.WriteString("=")
+ tmp.WriteString(url.QueryEscape(s.otp))
+ tmp.WriteString("&")
+ }
+ return tmp.String()
+}
diff --git a/vendor/github.com/hexonet/go-sdk/response/hashresponse/hashresponse.go b/vendor/github.com/hexonet/go-sdk/response/hashresponse/hashresponse.go
new file mode 100644
index 000000000..14d23838a
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/response/hashresponse/hashresponse.go
@@ -0,0 +1,366 @@
+// Copyright (c) 2018 Kai Schwarz (1API GmbH). All rights reserved.
+//
+// Use of this source code is governed by the MIT
+// license that can be found in the LICENSE.md file.
+
+// Package hashresponse covers all functionality to handle an API response in hash format and provides access to a response template manager
+// to cover http error cases etc. with API response format.
+package hashresponse
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// HashResponse class provides basic functionality to work with API responses.
+type HashResponse struct {
+ // represents the parsed API response data
+ hash map[string]interface{}
+ // represents the raw API response data
+ raw string
+ // represents the pattern to match columns used for pagination
+ pagerRegexp regexp.Regexp
+ // represents the column filter pattern
+ columnFilterRegexp regexp.Regexp
+ // represents an flag to turn column filter on/off
+ columnFilterActive bool
+}
+
+// NewHashResponse represents the constructor for struct HashResponse.
+// Provide the raw api response string as parameter.
+func NewHashResponse(r string) *HashResponse {
+ res := r
+ if len(res) == 0 {
+ res = NewTemplates().Get("empty")
+ }
+ hr := &HashResponse{
+ raw: res,
+ columnFilterActive: false,
+ pagerRegexp: *regexp.MustCompile("^(TOTAL|FIRST|LAST|LIMIT|COUNT)$"),
+ }
+ hr.hash = hr.Parse(hr.raw)
+ return hr
+}
+
+// GetRaw method to return the api raw (but filtered - in case of useColRegexp) response data
+func (hr *HashResponse) GetRaw() string {
+ return hr.GetRawByFilter(false)
+}
+
+// GetRawByFilter method to return the api raw response data.
+// Use noColumnFilter parameter to explicitly suppress a current active column filter.
+func (hr *HashResponse) GetRawByFilter(noColumnFilter bool) string {
+ if noColumnFilter || !hr.columnFilterActive {
+ return hr.raw
+ }
+ return hr.Serialize(hr.GetHash())
+}
+
+// GetHash method to return the parsed api response
+func (hr *HashResponse) GetHash() map[string]interface{} {
+ if hr.columnFilterActive {
+ var h = make(map[string]interface{})
+ for k, v := range hr.hash {
+ h[k] = v
+ }
+ properties := hr.hash["PROPERTY"]
+ if properties != nil {
+ d := make(map[string][]string)
+ for k, v := range properties.(map[string][]string) {
+ if hr.columnFilterRegexp.MatchString(k) {
+ d[k] = v
+ }
+ }
+ h["PROPERTY"] = d
+ }
+ return h
+ }
+ return hr.hash
+}
+
+// DisableColumnFilter method to turn of column filter
+func (hr *HashResponse) DisableColumnFilter() {
+ hr.columnFilterActive = false
+ // hr.columnFilterRegexp = nil
+}
+
+// EnableColumnFilter method to set a column filter
+func (hr *HashResponse) EnableColumnFilter(pattern string) {
+ hr.columnFilterActive = true
+ hr.columnFilterRegexp = *regexp.MustCompile(pattern)
+}
+
+// Code method to access the api response code
+func (hr *HashResponse) Code() int {
+ var x int
+ fmt.Sscanf(hr.hash["CODE"].(string), "%d", &x)
+ return x
+}
+
+// Description method to access the api response description
+func (hr *HashResponse) Description() string {
+ return hr.hash["DESCRIPTION"].(string)
+}
+
+// Runtime method to access the api response runtime
+func (hr *HashResponse) Runtime() float64 {
+ s, _ := strconv.ParseFloat(hr.hash["RUNTIME"].(string), 64)
+ return s
+}
+
+// Queuetime method to access the api response queuetime
+func (hr *HashResponse) Queuetime() float64 {
+ s, _ := strconv.ParseFloat(hr.hash["QUEUETIME"].(string), 64)
+ return s
+}
+
+// First method to access the pagination data "first".
+// Represents the row index of 1st row of the current response of the whole result set
+func (hr *HashResponse) First() int {
+ val, _ := hr.GetColumnIndex("FIRST", 0)
+ if len(val) == 0 {
+ return 0
+ }
+ var x int
+ fmt.Sscanf(val, "%d", &x)
+ return x
+}
+
+// Count method to access the pagination data "count"
+// Represents the count of rows returned in the current response
+func (hr *HashResponse) Count() int {
+ val, _ := hr.GetColumnIndex("COUNT", 0)
+ if len(val) != 0 {
+ var x int
+ fmt.Sscanf(val, "%d", &x)
+ return x
+ }
+ c := 0
+ max := 0
+ cols := hr.GetColumnKeys()
+ for _, el := range cols {
+ col := hr.GetColumn(el)
+ c = len(col)
+ if c > max {
+ max = c
+ }
+ }
+ return c
+}
+
+// Last method to access the pagination data "last"
+// Represents the row index of last row of the current response of the whole result set
+func (hr *HashResponse) Last() int {
+ val, _ := hr.GetColumnIndex("LAST", 0)
+ if len(val) == 0 {
+ return hr.Count() - 1
+ }
+ var x int
+ fmt.Sscanf(val, "%d", &x)
+ return x
+}
+
+// Limit method to access the pagination data "limit"
+// represents the limited amount of rows requested to be returned
+func (hr *HashResponse) Limit() int {
+ val, _ := hr.GetColumnIndex("LIMIT", 0)
+ if len(val) == 0 {
+ return hr.Count()
+ }
+ var x int
+ fmt.Sscanf(val, "%d", &x)
+ return x
+}
+
+// Total method to access the pagination data "total"
+// represents the total amount of rows available in the whole result set
+func (hr *HashResponse) Total() int {
+ val, _ := hr.GetColumnIndex("TOTAL", 0)
+ if len(val) == 0 {
+ return hr.Count()
+ }
+ var x int
+ fmt.Sscanf(val, "%d", &x)
+ return x
+}
+
+// Pages method to return the amount of pages of the current result set
+func (hr *HashResponse) Pages() int {
+ t := hr.Total()
+ if t > 0 {
+ return int(math.Ceil(float64(t) / float64(hr.Limit())))
+ }
+ return 1
+}
+
+// Page method to return the number of the current page
+func (hr *HashResponse) Page() int {
+ if hr.Count() > 0 {
+ // limit cannot be 0 as this.count() will cover this, no worries
+ d := float64(hr.First()) / float64(hr.Limit())
+ return int(math.Floor(d)) + 1
+ }
+ return 1
+}
+
+// Prevpage method to get the previous page number
+func (hr *HashResponse) Prevpage() int {
+ p := hr.Page() - 1
+ if p > 0 {
+ return p
+ }
+ return 1
+}
+
+// Nextpage method to get the next page number
+func (hr *HashResponse) Nextpage() int {
+ p := hr.Page() + 1
+ pages := hr.Pages()
+ if p <= pages {
+ return p
+ }
+ return pages
+}
+
+// GetPagination method to return all pagination data at once
+func (hr *HashResponse) GetPagination() map[string]int {
+ pagination := make(map[string]int)
+ pagination["FIRST"] = hr.First()
+ pagination["LAST"] = hr.Last()
+ pagination["COUNT"] = hr.Count()
+ pagination["TOTAL"] = hr.Total()
+ pagination["LIMIT"] = hr.Limit()
+ pagination["PAGES"] = hr.Pages()
+ pagination["PAGE"] = hr.Page()
+ pagination["PAGENEXT"] = hr.Nextpage()
+ pagination["PAGEPREV"] = hr.Prevpage()
+ return pagination
+}
+
+// IsSuccess method to check if the api response represents a success case
+func (hr *HashResponse) IsSuccess() bool {
+ code := hr.Code()
+ return (code >= 200 && code < 300)
+}
+
+// IsTmpError method to check if the api response represents a temporary error case
+func (hr *HashResponse) IsTmpError() bool {
+ code := hr.Code()
+ return (code >= 400 && code < 500)
+}
+
+// IsError method to check if the api response represents an error case
+func (hr *HashResponse) IsError() bool {
+ code := hr.Code()
+ return (code >= 500 && code <= 600)
+}
+
+// GetColumnKeys method to get a full list available columns in api response
+func (hr *HashResponse) GetColumnKeys() []string {
+ var columns []string
+ if hr.hash == nil {
+ return columns
+ }
+ property := hr.hash["PROPERTY"]
+ if property == nil {
+ return columns
+ }
+ for k := range property.(map[string][]string) {
+ if !hr.pagerRegexp.MatchString(k) {
+ columns = append(columns, k)
+ }
+ }
+ return columns
+}
+
+// GetColumn method to get the full column data for the given column id
+func (hr *HashResponse) GetColumn(columnid string) []string {
+ if hr.hash == nil || hr.hash["PROPERTY"] == nil {
+ return nil
+ }
+ return hr.hash["PROPERTY"].(map[string][]string)[columnid]
+}
+
+// GetColumnIndex method to get a response data field by column id and index
+func (hr *HashResponse) GetColumnIndex(columnid string, index int) (string, error) {
+ if hr.hash == nil || hr.hash["PROPERTY"] == nil {
+ return "", errors.New("column not found")
+ }
+ column := hr.hash["PROPERTY"].(map[string][]string)[columnid]
+ if column == nil || len(column) <= index {
+ return "", errors.New("index not found")
+ }
+ return column[index], nil
+}
+
+// Serialize method to stringify a parsed api response
+func (hr *HashResponse) Serialize(hash map[string]interface{}) string {
+ var plain strings.Builder
+ plain.WriteString("[RESPONSE]")
+ for k := range hash {
+ if strings.Compare(k, "PROPERTY") == 0 {
+ for k2, v2 := range hash[k].(map[string][]string) {
+ for i, v3 := range v2 {
+ plain.WriteString("\r\nPROPERTY[")
+ plain.WriteString(k2)
+ plain.WriteString("][")
+ plain.WriteString(fmt.Sprintf("%d", i))
+ plain.WriteString("]=")
+ plain.WriteString(v3)
+ }
+ }
+ } else {
+ tmp := hash[k].(string)
+ if len(tmp) > 0 {
+ plain.WriteString("\r\n")
+ plain.WriteString(k)
+ plain.WriteString("=")
+ plain.WriteString(tmp)
+ }
+ }
+ }
+ plain.WriteString("\r\nEOF\r\n")
+ return plain.String()
+}
+
+// Parse method to parse the given raw api response
+func (hr *HashResponse) Parse(r string) map[string]interface{} {
+ hash := make(map[string]interface{})
+ tmp := strings.Split(strings.Replace(r, "\r", "", -1), "\n")
+ p1 := regexp.MustCompile("^([^\\=]*[^\\t\\= ])[\\t ]*=[\\t ]*(.*)$")
+ p2 := regexp.MustCompile("(?i)^property\\[([^\\]]*)\\]\\[([0-9]+)\\]")
+ properties := make(map[string][]string)
+ for _, row := range tmp {
+ m := p1.MatchString(row)
+ if m {
+ groups := p1.FindStringSubmatch(row)
+ property := strings.ToUpper(groups[1])
+ mm := p2.MatchString(property)
+ if mm {
+ groups2 := p2.FindStringSubmatch(property)
+ key := strings.Replace(strings.ToUpper(groups2[1]), "\\s", "", -1)
+ // idx2 := strconv.Atoi(groups2[2])
+ list := make([]string, len(properties[key]))
+ copy(list, properties[key])
+ pat := regexp.MustCompile("[\\t ]*$")
+ rep1 := "${1}$2"
+ list = append(list, pat.ReplaceAllString(groups[2], rep1))
+ properties[key] = list
+ } else {
+ val := groups[2]
+ if len(val) > 0 {
+ pat := regexp.MustCompile("[\\t ]*$")
+ hash[property] = pat.ReplaceAllString(val, "")
+ }
+ }
+ }
+ }
+ if len(properties) > 0 {
+ hash["PROPERTY"] = properties
+ }
+ return hash
+}
diff --git a/vendor/github.com/hexonet/go-sdk/response/hashresponse/templates.go b/vendor/github.com/hexonet/go-sdk/response/hashresponse/templates.go
new file mode 100644
index 000000000..6c9c60faa
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/response/hashresponse/templates.go
@@ -0,0 +1,74 @@
+// Copyright (c) 2018 Kai Schwarz (1API GmbH). All rights reserved.
+//
+// Use of this source code is governed by the MIT
+// license that can be found in the LICENSE.md file.
+
+package hashresponse
+
+import (
+ "strings"
+)
+
+// Templates class manages default api response templates to be used for different reasons.
+// It also provides functionality to compare a response against a template.
+//
+// Basically used to provide custom response templates that are used in error cases to have a useful way to responds to the client.
+type Templates struct {
+ // represents the template container
+ templates map[string]string
+}
+
+// NewTemplates represents the constructor for struct Templates.
+func NewTemplates() *Templates {
+ tpls := make(map[string]string)
+ tpls["empty"] = "[RESPONSE]\r\ncode=423\r\ndescription=Empty API response\r\nEOF\r\n"
+ tpls["error"] = "[RESPONSE]\r\ncode=421\r\ndescription=Command failed due to server error. Client should try again\r\nEOF\r\n"
+ tpls["expired"] = "[RESPONSE]\r\ncode=530\r\ndescription=SESSION NOT FOUND\r\nEOF\r\n"
+ tpls["commonerror"] = "[RESPONSE]\r\nDESCRIPTION=Command failed;####ERRMSG####;\r\nCODE=500\r\nQUEUETIME=0\r\nRUNTIME=0\r\nEOF"
+ return &Templates{
+ templates: tpls,
+ }
+}
+
+// GetAll method to get all available response templates
+func (dr *Templates) GetAll() map[string]string {
+ return dr.templates
+}
+
+// GetParsed method to get a parsed response template by given template id.
+func (dr *Templates) GetParsed(templateid string) map[string]interface{} {
+ hr := NewHashResponse(dr.Get(templateid))
+ return hr.GetHash()
+}
+
+// Get method to get a raw response template by given template id.
+func (dr *Templates) Get(templateid string) string {
+ return dr.templates[templateid]
+}
+
+// Set method to set a response template by given template id and content
+func (dr *Templates) Set(templateid string, templatecontent string) {
+ dr.templates[templateid] = templatecontent
+}
+
+// SetParsed method to set a response template by given template id and parsed content
+func (dr *Templates) SetParsed(templateid string, templatecontent map[string]interface{}) {
+ hr := NewHashResponse("")
+ dr.templates[templateid] = hr.Serialize(templatecontent)
+}
+
+// Match method to compare a given raw api response with a response template identfied by id.
+// It compares CODE and DESCRIPTION.
+func (dr *Templates) Match(r string, templateid string) bool {
+ tpl := NewHashResponse(dr.Get(templateid))
+ rr := NewHashResponse(r)
+ return (tpl.Code() == rr.Code() && strings.Compare(tpl.Description(), rr.Description()) == 0)
+}
+
+// MatchParsed method to compare a given parsed api response with a response template identified by id.
+// It compares CODE and DESCRIPTION.
+func (dr *Templates) MatchParsed(r map[string]interface{}, templateid string) bool {
+ tpl := dr.GetParsed(templateid)
+ return (strings.Compare(tpl["CODE"].(string), r["CODE"].(string)) == 0 &&
+ strings.Compare(tpl["DESCRIPTION"].(string), r["DESCRIPTION"].(string)) == 0)
+}
diff --git a/vendor/github.com/hexonet/go-sdk/response/listresponse/listresponse.go b/vendor/github.com/hexonet/go-sdk/response/listresponse/listresponse.go
new file mode 100644
index 000000000..c047e87df
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/response/listresponse/listresponse.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2018 Kai Schwarz (1API GmbH). All rights reserved.
+//
+// Use of this source code is governed by the MIT
+// license that can be found in the LICENSE.md file.
+
+// Package listresponse covers all functionality to handle an API response in list format, but as well provides access to the hash format
+package listresponse
+
+import (
+ "github.com/hexonet/go-sdk/response/hashresponse"
+)
+
+// ListResponse class provides extra functionality to work with API responses.
+// It provides methods that are useful for data representation in table format.
+// In general the apiconnector Client always returns this type of response to be as flexible as possible.
+type ListResponse struct {
+ *hashresponse.HashResponse
+ currentIndex int
+ rows [][]string
+}
+
+// NewListResponse represents the constructor for struct ListResponse
+func NewListResponse(r string) *ListResponse {
+ lr := &ListResponse{
+ rows: [][]string{},
+ currentIndex: 0,
+ }
+ lr.HashResponse = hashresponse.NewHashResponse(r)
+ rows := lr.rows
+ h := lr.GetHash()
+ cols := lr.GetColumnKeys()
+ if lr.IsSuccess() && h["PROPERTY"] != nil {
+ size := len(cols)
+ cc := lr.Count()
+ for i := 0; i < cc; i++ { //loop over amount of rows/indexes
+ var row []string
+ for c := 0; c < size; c++ { //loop over all columns
+ colkey := cols[c]
+ values := lr.GetColumn(colkey)
+ if values != nil && len(values) > i {
+ row = append(row, values[i])
+ }
+ }
+ rows = append(rows, row)
+ }
+ }
+ lr.rows = rows
+ return lr
+}
+
+// GetList method to return the list of available rows
+func (lr *ListResponse) GetList() [][]string {
+ return lr.rows
+}
+
+// HasNext method to check if there's a further row after current row
+func (lr *ListResponse) HasNext() bool {
+ len := len(lr.rows)
+ if len == 0 || lr.currentIndex+1 >= len {
+ return false
+ }
+ return true
+}
+
+// Next method to access next row.
+// Use HasNext method before.
+func (lr *ListResponse) Next() []string {
+ lr.currentIndex++
+ return lr.rows[lr.currentIndex]
+}
+
+// HasPrevious method to check if there is a row available before current row.
+func (lr *ListResponse) HasPrevious() bool {
+ if lr.currentIndex == 0 {
+ return false
+ }
+ return true
+}
+
+// Previous method to access previous row.
+// Use HasPrevious method before.
+func (lr *ListResponse) Previous() []string {
+ lr.currentIndex--
+ return lr.rows[lr.currentIndex]
+}
+
+// Current method to return current row
+func (lr *ListResponse) Current() []string {
+ if len(lr.rows) == 0 {
+ return nil
+ }
+ return lr.rows[lr.currentIndex]
+}
+
+// Rewind method to reset the iterator index
+func (lr *ListResponse) Rewind() {
+ lr.currentIndex = 0
+}
diff --git a/vendor/github.com/hexonet/go-sdk/scripts/changelog.sh b/vendor/github.com/hexonet/go-sdk/scripts/changelog.sh
new file mode 100755
index 000000000..ae7f2e762
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/scripts/changelog.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+auto-changelog --template https://raw.githubusercontent.com/hexonet/auto-changelog-templates/master/compact.hts --commit-limit false --output HISTORY.md
diff --git a/vendor/github.com/hexonet/go-sdk/scripts/test-go.sh b/vendor/github.com/hexonet/go-sdk/scripts/test-go.sh
new file mode 100755
index 000000000..f1100610d
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/scripts/test-go.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+echo
+echo "==> Running automated tests <=="
+cd test
+go test
+cd .. || exit
+exit
diff --git a/vendor/github.com/hexonet/go-sdk/scripts/validate-go.sh b/vendor/github.com/hexonet/go-sdk/scripts/validate-go.sh
new file mode 100755
index 000000000..2ecf5dfb3
--- /dev/null
+++ b/vendor/github.com/hexonet/go-sdk/scripts/validate-go.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+# Copyright 2016 The Kubernetes Authors All rights reserved.
+#
+# 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.
+set -euo pipefail
+
+exit_code=0
+
+if ! hash gometalinter.v1 2>/dev/null ; then
+ go get -u gopkg.in/alecthomas/gometalinter.v1
+ gometalinter.v1 --install
+fi
+
+echo
+echo "==> Running static validations <=="
+# Run linters that should return errors
+gometalinter.v1 \
+ --disable-all \
+ --enable deadcode \
+ --severity deadcode:error \
+ --enable gofmt \
+ --enable ineffassign \
+ --enable misspell \
+ --enable vet \
+ --tests \
+ --vendor \
+ --deadline 60s \
+ ./... || exit_code=1
+
+echo
+echo "==> Running linters <=="
+# Run linters that should return warnings
+gometalinter.v1 \
+ --disable-all \
+ --enable golint \
+ --vendor \
+ --skip proto \
+ --deadline 60s \
+ ./... || :
+
+exit $exit_code
diff --git a/vendor/vendor.json b/vendor/vendor.json
index a088a37e6..3b38e6929 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -253,6 +253,15 @@
"revision": "dec09d789f3dba190787f8b4454c7d3c936fed9e",
"revisionTime": "2017-11-29T19:10:14Z"
},
+ {
+ "checksumSHA1": "8NVOwlGdEBlSFIata3vQABKA0Xs=",
+ "path": "github.com/hexonet/go-sdk",
+ "revision": "e12cede7032b5fb069cbeb6df5d5e86e95cfc654",
+ "revisionTime": "2018-08-03T10:21:36Z",
+ "tree": true,
+ "version": "v1.2.1",
+ "versionExact": "v1.2.1"
+ },
{
"checksumSHA1": "blwbl9vPvRLtL5QlZgfpLvsFiZ4=",
"path": "github.com/jmespath/go-jmespath",